@kaelio/ktx 0.13.1 → 0.14.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 (241) hide show
  1. package/assets/python/{kaelio_ktx-0.13.1-py3-none-any.whl → kaelio_ktx-0.14.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 +1 -1
  5. package/dist/commands/ingest-commands.d.ts +9 -0
  6. package/dist/commands/ingest-commands.js +37 -1
  7. package/dist/commands/knowledge-commands.js +3 -0
  8. package/dist/commands/setup-commands.js +14 -1
  9. package/dist/connection-drivers.d.ts +2 -0
  10. package/dist/connection-drivers.js +7 -3
  11. package/dist/connection.d.ts +3 -0
  12. package/dist/connection.js +27 -0
  13. package/dist/connectors/bigquery/connector.d.ts +17 -6
  14. package/dist/connectors/bigquery/connector.js +152 -76
  15. package/dist/connectors/bigquery/dialect.d.ts +2 -2
  16. package/dist/connectors/clickhouse/connector.d.ts +1 -0
  17. package/dist/connectors/clickhouse/connector.js +45 -16
  18. package/dist/connectors/clickhouse/dialect.d.ts +2 -2
  19. package/dist/connectors/mongodb/connector.d.ts +69 -0
  20. package/dist/connectors/mongodb/connector.js +307 -0
  21. package/dist/connectors/mongodb/dialect.d.ts +17 -0
  22. package/dist/connectors/mongodb/dialect.js +50 -0
  23. package/dist/connectors/mongodb/live-database-introspection.d.ts +10 -0
  24. package/dist/connectors/mongodb/live-database-introspection.js +24 -0
  25. package/dist/connectors/mongodb/schema-inference.d.ts +20 -0
  26. package/dist/connectors/mongodb/schema-inference.js +110 -0
  27. package/dist/connectors/mysql/connector.d.ts +1 -0
  28. package/dist/connectors/mysql/connector.js +18 -2
  29. package/dist/connectors/mysql/dialect.d.ts +2 -2
  30. package/dist/connectors/postgres/connector.d.ts +1 -0
  31. package/dist/connectors/postgres/connector.js +20 -3
  32. package/dist/connectors/postgres/dialect.d.ts +2 -2
  33. package/dist/connectors/snowflake/connector.d.ts +1 -0
  34. package/dist/connectors/snowflake/connector.js +27 -3
  35. package/dist/connectors/snowflake/dialect.d.ts +2 -2
  36. package/dist/connectors/sqlite/connector.d.ts +11 -1
  37. package/dist/connectors/sqlite/connector.js +120 -17
  38. package/dist/connectors/sqlite/dialect.d.ts +2 -2
  39. package/dist/connectors/sqlite/read-query-child.d.ts +1 -0
  40. package/dist/connectors/sqlite/read-query-child.js +23 -0
  41. package/dist/connectors/sqlserver/connector.d.ts +2 -0
  42. package/dist/connectors/sqlserver/connector.js +20 -3
  43. package/dist/connectors/sqlserver/dialect.d.ts +2 -2
  44. package/dist/context/cache/content-result-cache.d.ts +36 -0
  45. package/dist/context/cache/content-result-cache.js +14 -0
  46. package/dist/context/cache/sqlite-content-result-cache.d.ts +18 -0
  47. package/dist/context/cache/sqlite-content-result-cache.js +223 -0
  48. package/dist/context/connections/bigquery-identifiers.d.ts +1 -0
  49. package/dist/context/connections/bigquery-identifiers.js +7 -0
  50. package/dist/context/connections/configured-connections.d.ts +9 -0
  51. package/dist/context/connections/configured-connections.js +18 -0
  52. package/dist/context/connections/dialects.d.ts +30 -4
  53. package/dist/context/connections/dialects.js +38 -11
  54. package/dist/context/connections/drivers.js +21 -0
  55. package/dist/context/connections/gdrive-config.d.ts +19 -0
  56. package/dist/context/connections/gdrive-config.js +57 -0
  57. package/dist/context/connections/query-deadline.d.ts +31 -0
  58. package/dist/context/connections/query-deadline.js +37 -0
  59. package/dist/context/ingest/adapters/gdrive/chunk.d.ts +3 -0
  60. package/dist/context/ingest/adapters/gdrive/chunk.js +74 -0
  61. package/dist/context/ingest/adapters/gdrive/detect.d.ts +1 -0
  62. package/dist/context/ingest/adapters/gdrive/detect.js +20 -0
  63. package/dist/context/ingest/adapters/gdrive/fetch.d.ts +6 -0
  64. package/dist/context/ingest/adapters/gdrive/fetch.js +95 -0
  65. package/dist/context/ingest/adapters/gdrive/gdrive-client.d.ts +30 -0
  66. package/dist/context/ingest/adapters/gdrive/gdrive-client.js +132 -0
  67. package/dist/context/ingest/adapters/gdrive/gdrive.adapter.d.ts +11 -0
  68. package/dist/context/ingest/adapters/gdrive/gdrive.adapter.js +27 -0
  69. package/dist/context/ingest/adapters/gdrive/normalize.d.ts +2 -0
  70. package/dist/context/ingest/adapters/gdrive/normalize.js +256 -0
  71. package/dist/context/ingest/adapters/gdrive/types.d.ts +153 -0
  72. package/dist/context/ingest/adapters/gdrive/types.js +36 -0
  73. package/dist/context/ingest/adapters/live-database/daemon-introspection.js +24 -0
  74. package/dist/context/ingest/adapters/live-database/fetch-report.d.ts +8 -0
  75. package/dist/context/ingest/adapters/live-database/fetch-report.js +37 -0
  76. package/dist/context/ingest/adapters/live-database/live-database.adapter.d.ts +2 -1
  77. package/dist/context/ingest/adapters/live-database/live-database.adapter.js +9 -2
  78. package/dist/context/ingest/adapters/live-database/manifest.d.ts +2 -0
  79. package/dist/context/ingest/adapters/live-database/manifest.js +8 -4
  80. package/dist/context/ingest/adapters/live-database/scan-outcome.d.ts +17 -0
  81. package/dist/context/ingest/adapters/live-database/scan-outcome.js +40 -0
  82. package/dist/context/ingest/adapters/live-database/stage.js +5 -5
  83. package/dist/context/ingest/adapters/metabase/types.d.ts +1 -1
  84. package/dist/context/ingest/adapters/metabase/types.js +1 -1
  85. package/dist/context/ingest/artifact-gates.d.ts +32 -1
  86. package/dist/context/ingest/artifact-gates.js +85 -18
  87. package/dist/context/ingest/final-gate-prune.d.ts +35 -0
  88. package/dist/context/ingest/final-gate-prune.js +229 -0
  89. package/dist/context/ingest/ingest-bundle.runner.d.ts +3 -0
  90. package/dist/context/ingest/ingest-bundle.runner.js +459 -240
  91. package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +0 -11
  92. package/dist/context/ingest/isolated-diff/patch-integrator.js +0 -44
  93. package/dist/context/ingest/isolated-diff/work-unit-executor.d.ts +1 -0
  94. package/dist/context/ingest/isolated-diff/work-unit-executor.js +13 -4
  95. package/dist/context/ingest/local-adapters.js +6 -0
  96. package/dist/context/ingest/local-bundle-runtime.js +7 -2
  97. package/dist/context/ingest/local-ingest.js +1 -1
  98. package/dist/context/ingest/local-stage-ingest.d.ts +3 -1
  99. package/dist/context/ingest/local-stage-ingest.js +3 -1
  100. package/dist/context/ingest/ports.d.ts +3 -0
  101. package/dist/context/ingest/report-snapshot.js +13 -3
  102. package/dist/context/ingest/reports.d.ts +3 -3
  103. package/dist/context/ingest/reports.js +3 -1
  104. package/dist/context/ingest/stages/build-wu-context.d.ts +1 -0
  105. package/dist/context/ingest/stages/build-wu-context.js +2 -1
  106. package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -1
  107. package/dist/context/ingest/stages/stage-3-work-units.js +4 -10
  108. package/dist/context/ingest/stages/validate-wu-sources.d.ts +12 -0
  109. package/dist/context/ingest/stages/validate-wu-sources.js +23 -8
  110. package/dist/context/ingest/tools/read-raw-file.tool.js +10 -5
  111. package/dist/context/ingest/tools/read-raw-span.tool.js +10 -5
  112. package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +1 -1
  113. package/dist/context/ingest/tools/warehouse-verification/entity-details.tool.js +1 -1
  114. package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +1 -1
  115. package/dist/context/ingest/types.d.ts +3 -0
  116. package/dist/context/ingest/wiki-body-refs.d.ts +28 -0
  117. package/dist/context/ingest/wiki-body-refs.js +37 -8
  118. package/dist/context/ingest/wiki-sl-ref-repair.d.ts +1 -0
  119. package/dist/context/ingest/wiki-sl-ref-repair.js +4 -0
  120. package/dist/context/ingest/work-unit-cache.d.ts +67 -0
  121. package/dist/context/ingest/work-unit-cache.js +230 -0
  122. package/dist/context/llm/ai-sdk-runtime.d.ts +1 -0
  123. package/dist/context/llm/ai-sdk-runtime.js +5 -0
  124. package/dist/context/llm/claude-code-runtime.d.ts +4 -1
  125. package/dist/context/llm/claude-code-runtime.js +38 -8
  126. package/dist/context/llm/codex-runtime.d.ts +4 -1
  127. package/dist/context/llm/codex-runtime.js +48 -35
  128. package/dist/context/llm/runtime-port.d.ts +26 -0
  129. package/dist/context/llm/subprocess-generate-object-child.d.ts +1 -0
  130. package/dist/context/llm/subprocess-generate-object-child.js +34 -0
  131. package/dist/context/llm/subprocess-generate-object.d.ts +42 -0
  132. package/dist/context/llm/subprocess-generate-object.js +122 -0
  133. package/dist/context/mcp/context-tools.d.ts +2 -0
  134. package/dist/context/mcp/context-tools.js +111 -17
  135. package/dist/context/mcp/local-project-ports.d.ts +7 -0
  136. package/dist/context/mcp/local-project-ports.js +29 -0
  137. package/dist/context/mcp/logger.d.ts +24 -0
  138. package/dist/context/mcp/logger.js +49 -0
  139. package/dist/context/mcp/server.js +2 -0
  140. package/dist/context/mcp/types.d.ts +17 -0
  141. package/dist/context/memory/memory-agent.service.js +1 -1
  142. package/dist/context/project/config.d.ts +42 -0
  143. package/dist/context/project/config.js +5 -0
  144. package/dist/context/project/driver-schemas.d.ts +20 -0
  145. package/dist/context/project/driver-schemas.js +50 -1
  146. package/dist/context/project/project.js +1 -1
  147. package/dist/context/project/setup-config.js +1 -0
  148. package/dist/context/scan/description-generation.d.ts +4 -0
  149. package/dist/context/scan/description-generation.js +100 -13
  150. package/dist/context/scan/enabled-tables.d.ts +5 -0
  151. package/dist/context/scan/enabled-tables.js +13 -1
  152. package/dist/context/scan/enrichment-state.d.ts +50 -7
  153. package/dist/context/scan/enrichment-state.js +30 -13
  154. package/dist/context/scan/local-enrichment-artifacts.d.ts +41 -0
  155. package/dist/context/scan/local-enrichment-artifacts.js +155 -32
  156. package/dist/context/scan/local-enrichment.d.ts +39 -4
  157. package/dist/context/scan/local-enrichment.js +345 -92
  158. package/dist/context/scan/local-scan.d.ts +4 -1
  159. package/dist/context/scan/local-scan.js +54 -13
  160. package/dist/context/scan/local-structural-artifacts.d.ts +3 -1
  161. package/dist/context/scan/local-structural-artifacts.js +5 -0
  162. package/dist/context/scan/object-introspection.d.ts +21 -0
  163. package/dist/context/scan/object-introspection.js +35 -0
  164. package/dist/context/scan/relationship-benchmarks.js +5 -3
  165. package/dist/context/scan/relationship-composite-candidates.d.ts +6 -3
  166. package/dist/context/scan/relationship-composite-candidates.js +13 -1
  167. package/dist/context/scan/relationship-detection-budget.d.ts +35 -0
  168. package/dist/context/scan/relationship-detection-budget.js +53 -0
  169. package/dist/context/scan/relationship-diagnostics.d.ts +5 -0
  170. package/dist/context/scan/relationship-diagnostics.js +2 -0
  171. package/dist/context/scan/relationship-discovery.d.ts +9 -3
  172. package/dist/context/scan/relationship-discovery.js +27 -4
  173. package/dist/context/scan/relationship-llm-proposal.js +36 -27
  174. package/dist/context/scan/relationship-profiling.d.ts +7 -3
  175. package/dist/context/scan/relationship-profiling.js +53 -41
  176. package/dist/context/scan/relationship-validation.d.ts +6 -4
  177. package/dist/context/scan/relationship-validation.js +46 -32
  178. package/dist/context/scan/sqlite-local-enrichment-state-store.d.ts +8 -1
  179. package/dist/context/scan/sqlite-local-enrichment-state-store.js +71 -159
  180. package/dist/context/scan/types.d.ts +5 -3
  181. package/dist/context/scan/types.js +2 -0
  182. package/dist/context/sl/local-query.js +5 -0
  183. package/dist/context/sl/pglite-sl-search-prototype.js +1 -1
  184. package/dist/context/sl/semantic-layer.service.js +1 -1
  185. package/dist/context/sl/source-files.d.ts +5 -0
  186. package/dist/context/sl/source-files.js +17 -11
  187. package/dist/context/sl/tools/connection-id-schema.js +1 -1
  188. package/dist/context/sql-analysis/dialect-notes.d.ts +9 -0
  189. package/dist/context/sql-analysis/dialect-notes.js +40 -0
  190. package/dist/context/sql-analysis/dialects/bigquery.md +13 -0
  191. package/dist/context/sql-analysis/dialects/clickhouse.md +9 -0
  192. package/dist/context/sql-analysis/dialects/mysql.md +9 -0
  193. package/dist/context/sql-analysis/dialects/postgres.md +10 -0
  194. package/dist/context/sql-analysis/dialects/snowflake.md +10 -0
  195. package/dist/context/sql-analysis/dialects/sqlite.md +11 -0
  196. package/dist/context/sql-analysis/dialects/tsql.md +10 -0
  197. package/dist/context/wiki/keys.js +1 -1
  198. package/dist/context/wiki/knowledge-wiki.service.d.ts +4 -4
  199. package/dist/context/wiki/knowledge-wiki.service.js +2 -1
  200. package/dist/context/wiki/local-knowledge.d.ts +13 -0
  201. package/dist/context/wiki/local-knowledge.js +50 -6
  202. package/dist/context/wiki/sqlite-knowledge-index.d.ts +2 -0
  203. package/dist/context/wiki/sqlite-knowledge-index.js +21 -5
  204. package/dist/context/wiki/tools/wiki-write.tool.d.ts +2 -0
  205. package/dist/context/wiki/tools/wiki-write.tool.js +27 -0
  206. package/dist/context/wiki/types.d.ts +6 -0
  207. package/dist/context-build-view.d.ts +3 -0
  208. package/dist/context-build-view.js +1 -0
  209. package/dist/knowledge.d.ts +2 -0
  210. package/dist/knowledge.js +12 -1
  211. package/dist/local-adapters.js +11 -0
  212. package/dist/mcp-http-server.js +15 -1
  213. package/dist/mcp-server-factory.d.ts +2 -0
  214. package/dist/mcp-server-factory.js +15 -1
  215. package/dist/mcp-stdio-server.js +10 -2
  216. package/dist/notion-page-picker.js +1 -1
  217. package/dist/prompts/memory_agent_external_ingest.md +2 -0
  218. package/dist/public-ingest.d.ts +3 -1
  219. package/dist/public-ingest.js +3 -0
  220. package/dist/scan.d.ts +3 -1
  221. package/dist/scan.js +7 -0
  222. package/dist/setup-databases.d.ts +1 -1
  223. package/dist/setup-databases.js +43 -1
  224. package/dist/setup-sources.d.ts +5 -1
  225. package/dist/setup-sources.js +124 -48
  226. package/dist/setup.d.ts +3 -0
  227. package/dist/setup.js +6 -1
  228. package/dist/skills/analytics/SKILL.md +200 -2
  229. package/dist/skills/gdrive_synthesize/SKILL.md +97 -0
  230. package/dist/skills/wiki_capture/SKILL.md +24 -0
  231. package/dist/sql.js +4 -0
  232. package/dist/status-project.d.ts +4 -0
  233. package/dist/status-project.js +54 -2
  234. package/dist/telemetry/events.d.ts +2 -2
  235. package/dist/text-ingest.d.ts +4 -0
  236. package/dist/text-ingest.js +58 -29
  237. package/dist/verbatim-ingest.d.ts +46 -0
  238. package/dist/verbatim-ingest.js +193 -0
  239. package/package.json +5 -1
  240. package/dist/context/ingest/final-gate-repair.d.ts +0 -28
  241. package/dist/context/ingest/final-gate-repair.js +0 -91
@@ -0,0 +1,223 @@
1
+ import { mkdirSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import Database from 'better-sqlite3';
4
+ const RESULTS_TABLE = 'local_content_results';
5
+ const RESULTS_PRIMARY_KEY = ['namespace', 'scope_key', 'input_hash'];
6
+ function isSafeRunId(runId) {
7
+ return /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(runId);
8
+ }
9
+ function parseResultRow(row) {
10
+ const base = {
11
+ runId: row.run_id,
12
+ namespace: row.namespace,
13
+ scopeKey: row.scope_key,
14
+ inputHash: row.input_hash,
15
+ metadata: JSON.parse(row.metadata_json || '{}'),
16
+ updatedAt: row.updated_at,
17
+ };
18
+ if (row.status === 'completed') {
19
+ return {
20
+ ...base,
21
+ status: 'completed',
22
+ output: JSON.parse(row.output_json ?? 'null'),
23
+ errorMessage: null,
24
+ };
25
+ }
26
+ return {
27
+ ...base,
28
+ status: 'failed',
29
+ output: null,
30
+ errorMessage: row.error_message ?? 'Unknown content result failure',
31
+ };
32
+ }
33
+ export class SqliteContentResultCache {
34
+ db;
35
+ constructor(options) {
36
+ mkdirSync(dirname(options.dbPath), { recursive: true });
37
+ this.db = new Database(options.dbPath);
38
+ this.db.pragma('journal_mode = WAL');
39
+ this.db.exec('DROP TABLE IF EXISTS local_scan_enrichment_stages');
40
+ this.dropResultsTableIfPrimaryKeyDiffers();
41
+ this.db.exec(`
42
+ CREATE TABLE IF NOT EXISTS local_content_results (
43
+ run_id TEXT NOT NULL,
44
+ namespace TEXT NOT NULL,
45
+ scope_key TEXT NOT NULL,
46
+ input_hash TEXT NOT NULL,
47
+ status TEXT NOT NULL,
48
+ output_json TEXT,
49
+ error_message TEXT,
50
+ metadata_json TEXT NOT NULL,
51
+ updated_at TEXT NOT NULL,
52
+ PRIMARY KEY (namespace, scope_key, input_hash)
53
+ );
54
+
55
+ CREATE INDEX IF NOT EXISTS local_content_results_lookup_idx
56
+ ON local_content_results (namespace, scope_key, input_hash, updated_at);
57
+ CREATE INDEX IF NOT EXISTS local_content_results_run_idx
58
+ ON local_content_results (run_id, updated_at, namespace);
59
+ `);
60
+ }
61
+ dropResultsTableIfPrimaryKeyDiffers() {
62
+ const columns = this.db.prepare(`PRAGMA table_info(${RESULTS_TABLE})`).all();
63
+ if (columns.length === 0) {
64
+ return;
65
+ }
66
+ const primaryKey = columns
67
+ .filter((column) => column.pk > 0)
68
+ .sort((left, right) => left.pk - right.pk)
69
+ .map((column) => column.name);
70
+ const matches = primaryKey.length === RESULTS_PRIMARY_KEY.length &&
71
+ primaryKey.every((name, index) => name === RESULTS_PRIMARY_KEY[index]);
72
+ if (!matches) {
73
+ this.db.exec(`DROP TABLE ${RESULTS_TABLE}`);
74
+ }
75
+ }
76
+ async findCompletedResult(input) {
77
+ const row = this.db
78
+ .prepare(`
79
+ SELECT *
80
+ FROM local_content_results
81
+ WHERE namespace = ?
82
+ AND scope_key = ?
83
+ AND input_hash = ?
84
+ AND status = 'completed'
85
+ ORDER BY updated_at DESC
86
+ LIMIT 1
87
+ `)
88
+ .get(input.namespace, input.scopeKey, input.inputHash);
89
+ if (!row) {
90
+ return null;
91
+ }
92
+ const parsed = parseResultRow(row);
93
+ return parsed.status === 'completed' ? parsed : null;
94
+ }
95
+ async findLatestCompletedResult(input) {
96
+ const row = this.db
97
+ .prepare(`
98
+ SELECT *
99
+ FROM local_content_results
100
+ WHERE namespace = ?
101
+ AND scope_key = ?
102
+ AND status = 'completed'
103
+ ORDER BY updated_at DESC
104
+ LIMIT 1
105
+ `)
106
+ .get(input.namespace, input.scopeKey);
107
+ if (!row) {
108
+ return null;
109
+ }
110
+ const parsed = parseResultRow(row);
111
+ return parsed.status === 'completed' ? parsed : null;
112
+ }
113
+ async saveCompletedResult(input) {
114
+ this.db
115
+ .prepare(`
116
+ INSERT INTO local_content_results (
117
+ run_id,
118
+ namespace,
119
+ scope_key,
120
+ input_hash,
121
+ status,
122
+ output_json,
123
+ error_message,
124
+ metadata_json,
125
+ updated_at
126
+ )
127
+ VALUES (
128
+ @runId,
129
+ @namespace,
130
+ @scopeKey,
131
+ @inputHash,
132
+ 'completed',
133
+ @outputJson,
134
+ NULL,
135
+ @metadataJson,
136
+ @updatedAt
137
+ )
138
+ ON CONFLICT(namespace, scope_key, input_hash) DO UPDATE SET
139
+ run_id = excluded.run_id,
140
+ status = excluded.status,
141
+ output_json = excluded.output_json,
142
+ error_message = excluded.error_message,
143
+ metadata_json = excluded.metadata_json,
144
+ updated_at = excluded.updated_at
145
+ `)
146
+ .run({
147
+ runId: input.runId,
148
+ namespace: input.namespace,
149
+ scopeKey: input.scopeKey,
150
+ inputHash: input.inputHash,
151
+ outputJson: JSON.stringify(input.output),
152
+ metadataJson: JSON.stringify(input.metadata),
153
+ updatedAt: input.updatedAt,
154
+ });
155
+ }
156
+ async saveFailedResult(input) {
157
+ this.db
158
+ .prepare(`
159
+ INSERT INTO local_content_results (
160
+ run_id,
161
+ namespace,
162
+ scope_key,
163
+ input_hash,
164
+ status,
165
+ output_json,
166
+ error_message,
167
+ metadata_json,
168
+ updated_at
169
+ )
170
+ VALUES (
171
+ @runId,
172
+ @namespace,
173
+ @scopeKey,
174
+ @inputHash,
175
+ 'failed',
176
+ NULL,
177
+ @errorMessage,
178
+ @metadataJson,
179
+ @updatedAt
180
+ )
181
+ ON CONFLICT(namespace, scope_key, input_hash) DO UPDATE SET
182
+ run_id = excluded.run_id,
183
+ status = excluded.status,
184
+ output_json = excluded.output_json,
185
+ error_message = excluded.error_message,
186
+ metadata_json = excluded.metadata_json,
187
+ updated_at = excluded.updated_at
188
+ `)
189
+ .run({
190
+ runId: input.runId,
191
+ namespace: input.namespace,
192
+ scopeKey: input.scopeKey,
193
+ inputHash: input.inputHash,
194
+ errorMessage: input.errorMessage,
195
+ metadataJson: JSON.stringify(input.metadata),
196
+ updatedAt: input.updatedAt,
197
+ });
198
+ }
199
+ async deleteResult(input) {
200
+ this.db
201
+ .prepare(`
202
+ DELETE FROM local_content_results
203
+ WHERE namespace = ?
204
+ AND scope_key = ?
205
+ AND input_hash = ?
206
+ `)
207
+ .run(input.namespace, input.scopeKey, input.inputHash);
208
+ }
209
+ async listRunResults(runId) {
210
+ if (!isSafeRunId(runId)) {
211
+ return [];
212
+ }
213
+ const rows = this.db
214
+ .prepare(`
215
+ SELECT *
216
+ FROM local_content_results
217
+ WHERE run_id = ?
218
+ ORDER BY updated_at ASC, namespace ASC
219
+ `)
220
+ .all(runId);
221
+ return rows.map((row) => parseResultRow(row));
222
+ }
223
+ }
@@ -1,2 +1,3 @@
1
1
  export declare function normalizeBigQueryProjectId(value: string, context?: string): string;
2
+ export declare function normalizeBigQueryDatasetId(value: string, context?: string): string;
2
3
  export declare function normalizeBigQueryRegion(value: string, context?: string): string;
@@ -1,4 +1,5 @@
1
1
  const BIGQUERY_PROJECT_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
2
+ const BIGQUERY_DATASET_ID_PATTERN = /^[A-Za-z0-9_]+$/;
2
3
  const BIGQUERY_REGION_PATTERN = /^[a-z0-9-]+$/;
3
4
  export function normalizeBigQueryProjectId(value, context = 'historic-SQL ingest') {
4
5
  if (!BIGQUERY_PROJECT_ID_PATTERN.test(value)) {
@@ -6,6 +7,12 @@ export function normalizeBigQueryProjectId(value, context = 'historic-SQL ingest
6
7
  }
7
8
  return value;
8
9
  }
10
+ export function normalizeBigQueryDatasetId(value, context = 'historic-SQL ingest') {
11
+ if (!BIGQUERY_DATASET_ID_PATTERN.test(value)) {
12
+ throw new Error(`Invalid BigQuery dataset id for ${context}: ${value}`);
13
+ }
14
+ return value;
15
+ }
9
16
  export function normalizeBigQueryRegion(value, context = 'historic-SQL ingest') {
10
17
  const normalized = value.trim().toLowerCase().replace(/^region-/, '');
11
18
  if (!BIGQUERY_REGION_PATTERN.test(normalized)) {
@@ -0,0 +1,9 @@
1
+ import type { KtxProjectConnectionConfig } from '../project/config.js';
2
+ /**
3
+ * Validate a connection id supplied as an explicit command/tool argument against
4
+ * the canonical `ktx.yaml` connections map. Returns the id when configured;
5
+ * otherwise throws an error that lists the configured ids so the caller can fix
6
+ * the typo. Use for explicit arguments only — persisted page frontmatter that
7
+ * references a since-removed connection must warn, not fail.
8
+ */
9
+ export declare function assertConfiguredConnectionId(connections: Record<string, KtxProjectConnectionConfig>, connectionId: string): string;
@@ -0,0 +1,18 @@
1
+ function listConfiguredConnectionIds(connections) {
2
+ return Object.keys(connections).sort();
3
+ }
4
+ /**
5
+ * Validate a connection id supplied as an explicit command/tool argument against
6
+ * the canonical `ktx.yaml` connections map. Returns the id when configured;
7
+ * otherwise throws an error that lists the configured ids so the caller can fix
8
+ * the typo. Use for explicit arguments only — persisted page frontmatter that
9
+ * references a since-removed connection must warn, not fail.
10
+ */
11
+ export function assertConfiguredConnectionId(connections, connectionId) {
12
+ if (Object.hasOwn(connections, connectionId)) {
13
+ return connectionId;
14
+ }
15
+ const ids = listConfiguredConnectionIds(connections);
16
+ const configured = ids.length > 0 ? ids.join(', ') : '(none configured)';
17
+ throw new Error(`Unknown connection "${connectionId}". Configured connections: ${configured}.`);
18
+ }
@@ -1,12 +1,27 @@
1
1
  import type { KtxConnectionDriver, KtxSchemaDimensionType, KtxTableRef } from '../scan/types.js';
2
2
  import type { KtxDialectTableRef } from './dialect-helpers.js';
3
+ /**
4
+ * Driver-agnostic dialect surface every connection implements, including
5
+ * non-SQL sources like MongoDB: display/ref formatting and type mapping. The
6
+ * catalog and entity-details paths resolve this for any snapshot driver, so it
7
+ * must stay free of SQL generation.
8
+ */
3
9
  export interface KtxDialect {
4
10
  readonly type: KtxConnectionDriver;
5
- quoteIdentifier(identifier: string): string;
6
- formatTableName(table: KtxDialectTableRef): string;
7
11
  formatDisplayRef(table: KtxDialectTableRef): string;
8
12
  parseDisplayRef(display: string): KtxTableRef | null;
9
13
  columnDisplayTablePartCount(): 1 | 2 | 3;
14
+ mapToDimensionType(nativeType: string): KtxSchemaDimensionType;
15
+ mapDataType(nativeType: string): string;
16
+ }
17
+ /**
18
+ * SQL query generation, implemented only by SQL warehouse drivers. The relationship
19
+ * profiling/validation pipeline is the sole caller and is gated on the
20
+ * `readOnlySql` capability, so these methods are unreachable for a non-SQL source.
21
+ */
22
+ export interface KtxSqlDialect extends KtxDialect {
23
+ quoteIdentifier(identifier: string): string;
24
+ formatTableName(table: KtxDialectTableRef): string;
10
25
  getLimitOffsetClause(limit: number, offset?: number): string;
11
26
  getTopClause(limit: number): string;
12
27
  getRandomSampleFilter(samplePct: number): string;
@@ -22,7 +37,18 @@ export interface KtxDialect {
22
37
  getDistinctCountExpression(column: string): string;
23
38
  textLengthExpression(columnSql: string): string;
24
39
  castToText(columnSql: string): string;
25
- mapToDimensionType(nativeType: string): KtxSchemaDimensionType;
26
- mapDataType(nativeType: string): string;
27
40
  }
28
41
  export declare function getDialectForDriver(driver: string): KtxDialect;
42
+ export declare function getSqlDialectForDriver(driver: string): KtxSqlDialect;
43
+ /**
44
+ * Whether a driver can generate and execute SQL. Single source of truth for the
45
+ * SQL/non-SQL boundary: a driver is SQL-queryable iff it has a SQL dialect, so
46
+ * non-SQL sources (e.g. mongodb) are excluded without a hand-maintained list.
47
+ */
48
+ export declare function isSqlQueryableDriver(driver: string | undefined): boolean;
49
+ /**
50
+ * Refuse a non-SQL connection (e.g. mongodb) at a read-only-SQL entry point before
51
+ * any dialect selection or parser/daemon work, so it is never validated as Postgres.
52
+ * The federated `duckdb` connection has no driver — callers skip this guard for it.
53
+ */
54
+ export declare function assertSqlQueryableConnection(connectionId: string, driver: string | undefined): void;
@@ -1,20 +1,13 @@
1
1
  import { KtxBigQueryDialect } from '../../connectors/bigquery/dialect.js';
2
2
  import { KtxClickHouseDialect } from '../../connectors/clickhouse/dialect.js';
3
+ import { KtxMongoDbDialect } from '../../connectors/mongodb/dialect.js';
3
4
  import { KtxMysqlDialect } from '../../connectors/mysql/dialect.js';
4
5
  import { KtxPostgresDialect } from '../../connectors/postgres/dialect.js';
5
6
  import { KtxSqliteDialect } from '../../connectors/sqlite/dialect.js';
6
7
  import { KtxSnowflakeDialect } from '../../connectors/snowflake/dialect.js';
7
8
  import { KtxSqlServerDialect } from '../../connectors/sqlserver/dialect.js';
8
- const supportedDrivers = [
9
- 'bigquery',
10
- 'clickhouse',
11
- 'mysql',
12
- 'postgres',
13
- 'sqlite',
14
- 'snowflake',
15
- 'sqlserver',
16
- ];
17
- const dialectFactories = {
9
+ import { KtxExpectedError } from '../../errors.js';
10
+ const sqlDialectFactories = {
18
11
  bigquery: () => new KtxBigQueryDialect(),
19
12
  clickhouse: () => new KtxClickHouseDialect(),
20
13
  mysql: () => new KtxMysqlDialect(),
@@ -23,11 +16,45 @@ const dialectFactories = {
23
16
  snowflake: () => new KtxSnowflakeDialect(),
24
17
  sqlserver: () => new KtxSqlServerDialect(),
25
18
  };
19
+ const dialectFactories = {
20
+ ...sqlDialectFactories,
21
+ mongodb: () => new KtxMongoDbDialect(),
22
+ };
23
+ const supportedSqlDrivers = Object.keys(sqlDialectFactories).sort();
26
24
  export function getDialectForDriver(driver) {
27
25
  const normalized = driver.toLowerCase().trim();
28
26
  const factory = dialectFactories[normalized];
29
27
  if (factory) {
30
28
  return factory();
31
29
  }
32
- throw new Error(`Unsupported warehouse driver "${driver}". Supported drivers: ${supportedDrivers.join(', ')}`);
30
+ throw new Error(`Unsupported driver "${driver}". Supported drivers: ${Object.keys(dialectFactories).sort().join(', ')}`);
31
+ }
32
+ export function getSqlDialectForDriver(driver) {
33
+ const normalized = driver.toLowerCase().trim();
34
+ const factory = sqlDialectFactories[normalized];
35
+ if (factory) {
36
+ return factory();
37
+ }
38
+ throw new Error(`Driver "${driver}" has no SQL dialect. SQL drivers: ${supportedSqlDrivers.join(', ')}`);
39
+ }
40
+ /**
41
+ * Whether a driver can generate and execute SQL. Single source of truth for the
42
+ * SQL/non-SQL boundary: a driver is SQL-queryable iff it has a SQL dialect, so
43
+ * non-SQL sources (e.g. mongodb) are excluded without a hand-maintained list.
44
+ */
45
+ export function isSqlQueryableDriver(driver) {
46
+ const normalized = (driver ?? '').toLowerCase().trim();
47
+ return Object.prototype.hasOwnProperty.call(sqlDialectFactories, normalized);
48
+ }
49
+ /**
50
+ * Refuse a non-SQL connection (e.g. mongodb) at a read-only-SQL entry point before
51
+ * any dialect selection or parser/daemon work, so it is never validated as Postgres.
52
+ * The federated `duckdb` connection has no driver — callers skip this guard for it.
53
+ */
54
+ export function assertSqlQueryableConnection(connectionId, driver) {
55
+ if (!isSqlQueryableDriver(driver)) {
56
+ throw new KtxExpectedError(`Connection '${connectionId}' uses the non-SQL driver '${driver ?? 'unknown'}'. ` +
57
+ 'Read-only SQL (ktx sql, the sql_execution tool) requires a SQL warehouse connection; ' +
58
+ 'MongoDB and other context-only sources are searchable and ingestable, not SQL-queryable.');
59
+ }
33
60
  }
@@ -45,6 +45,27 @@ export const driverRegistrations = {
45
45
  };
46
46
  },
47
47
  },
48
+ mongodb: {
49
+ driver: 'mongodb',
50
+ scopeConfigKey: 'databases',
51
+ hasHistoricSqlReader: false,
52
+ load: async () => {
53
+ const m = await import('../../connectors/mongodb/connector.js');
54
+ return {
55
+ isConnectionConfig: (connection) => {
56
+ const typedConnection = connection;
57
+ return m.isKtxMongoDbConnectionConfig(typedConnection);
58
+ },
59
+ createScanConnector: ({ connectionId, connection }) => {
60
+ const typedConnection = connection;
61
+ if (!m.isKtxMongoDbConnectionConfig(typedConnection)) {
62
+ throw invalidConnectionConfig('mongodb');
63
+ }
64
+ return new m.KtxMongoDbScanConnector({ connectionId, connection: typedConnection });
65
+ },
66
+ };
67
+ },
68
+ },
48
69
  mysql: {
49
70
  driver: 'mysql',
50
71
  scopeConfigKey: 'schemas',
@@ -0,0 +1,19 @@
1
+ import type { KtxProjectConnectionConfig } from '../project/config.js';
2
+ import type { GdrivePullConfig } from '../ingest/adapters/gdrive/types.js';
3
+ type RawKtxGdriveConnectionConfig = Extract<KtxProjectConnectionConfig, {
4
+ driver: 'gdrive';
5
+ }>;
6
+ export type KtxGdriveConnectionConfig = Omit<RawKtxGdriveConnectionConfig, 'service_account_key_ref' | 'folder_id' | 'recursive'> & {
7
+ driver: 'gdrive';
8
+ service_account_key_ref: string;
9
+ folder_id: string;
10
+ recursive: boolean;
11
+ };
12
+ interface ResolveKeyOptions {
13
+ readTextFile?: (path: string) => Promise<string>;
14
+ }
15
+ export declare function parseGdriveConnectionConfig(raw: unknown): KtxGdriveConnectionConfig;
16
+ /** @internal */
17
+ export declare function resolveGdriveServiceAccountKey(serviceAccountKeyRef: string, options?: ResolveKeyOptions): Promise<string>;
18
+ export declare function gdriveConnectionToPullConfig(config: KtxGdriveConnectionConfig, options?: ResolveKeyOptions): Promise<GdrivePullConfig>;
19
+ export {};
@@ -0,0 +1,57 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { resolve } from 'node:path';
4
+ import { gdrivePullConfigSchema } from '../ingest/adapters/gdrive/types.js';
5
+ function isRecord(value) {
6
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
7
+ }
8
+ function expandHome(path) {
9
+ return path === '~' || path.startsWith('~/') ? resolve(homedir(), path.slice(2)) : path;
10
+ }
11
+ export function parseGdriveConnectionConfig(raw) {
12
+ if (!isRecord(raw)) {
13
+ throw new Error('gdrive connection config must be an object');
14
+ }
15
+ if (raw.driver !== 'gdrive') {
16
+ throw new Error('gdrive connection config requires driver: gdrive');
17
+ }
18
+ const keyRef = typeof raw.service_account_key_ref === 'string' && raw.service_account_key_ref.trim().length > 0 // pragma: allowlist secret
19
+ ? raw.service_account_key_ref.trim()
20
+ : null;
21
+ if (!keyRef) {
22
+ throw new Error('gdrive connection config requires service_account_key_ref');
23
+ }
24
+ if (!keyRef.startsWith('file:')) {
25
+ throw new Error('gdrive service_account_key_ref must use file:/path/to/key.json');
26
+ }
27
+ const folderId = typeof raw.folder_id === 'string' && raw.folder_id.trim().length > 0 ? raw.folder_id.trim() : null;
28
+ if (!folderId) {
29
+ throw new Error('gdrive connection config requires folder_id');
30
+ }
31
+ return {
32
+ driver: 'gdrive',
33
+ service_account_key_ref: keyRef,
34
+ folder_id: folderId,
35
+ recursive: raw.recursive === true,
36
+ };
37
+ }
38
+ /** @internal */
39
+ export async function resolveGdriveServiceAccountKey(serviceAccountKeyRef, options = {}) {
40
+ if (!serviceAccountKeyRef.startsWith('file:')) {
41
+ throw new Error('gdrive service_account_key_ref must use file:/path/to/key.json');
42
+ }
43
+ const path = expandHome(serviceAccountKeyRef.slice('file:'.length));
44
+ const readTextFile = options.readTextFile ?? ((filePath) => readFile(filePath, 'utf-8'));
45
+ const value = (await readTextFile(path)).trim();
46
+ if (!value) {
47
+ throw new Error(`gdrive service account key file is empty: ${path}`);
48
+ }
49
+ return value;
50
+ }
51
+ export async function gdriveConnectionToPullConfig(config, options = {}) {
52
+ return gdrivePullConfigSchema.parse({
53
+ serviceAccountKey: await resolveGdriveServiceAccountKey(config.service_account_key_ref, options),
54
+ folderId: config.folder_id,
55
+ recursive: config.recursive,
56
+ });
57
+ }
@@ -0,0 +1,31 @@
1
+ import { KtxQueryError } from '../../errors.js';
2
+ /**
3
+ * Canonical default bound on read-query execution time. Generous headroom over
4
+ * any indexed aggregate or normal profiling probe; a pathological nested-loop
5
+ * scan blows past it immediately. Overridable per-connection via
6
+ * `query_timeout_ms`. Production reads it through {@link resolveQueryDeadlineMs};
7
+ * exported for the resolver's own unit tests.
8
+ * @internal
9
+ */
10
+ export declare const DEFAULT_QUERY_TIMEOUT_MS = 30000;
11
+ interface QueryTimeoutConnectionConfig {
12
+ query_timeout_ms?: unknown;
13
+ [key: string]: unknown;
14
+ }
15
+ /**
16
+ * Single source of truth for the read-query deadline: the per-connection
17
+ * `query_timeout_ms` override (milliseconds) when present, else the default.
18
+ * Every connector resolves through here so the default and override precedence
19
+ * live in exactly one place. A malformed override (zero, negative, non-integer,
20
+ * non-number) is a config error — surfaced here even though `ktx.yaml`
21
+ * validation also rejects it, so programmatically-built connectors cannot
22
+ * silently run unbounded.
23
+ */
24
+ export declare function resolveQueryDeadlineMs(connection: QueryTimeoutConnectionConfig | undefined): number;
25
+ /**
26
+ * The canonical, driver-independent timeout error an agent sees regardless of
27
+ * which connector enforced the deadline. Reads in whole seconds. Remote
28
+ * connectors pass the driver's own timeout error as `cause`.
29
+ */
30
+ export declare function queryDeadlineExceededError(deadlineMs: number, options?: ErrorOptions): KtxQueryError;
31
+ export {};
@@ -0,0 +1,37 @@
1
+ import { KtxQueryError } from '../../errors.js';
2
+ /**
3
+ * Canonical default bound on read-query execution time. Generous headroom over
4
+ * any indexed aggregate or normal profiling probe; a pathological nested-loop
5
+ * scan blows past it immediately. Overridable per-connection via
6
+ * `query_timeout_ms`. Production reads it through {@link resolveQueryDeadlineMs};
7
+ * exported for the resolver's own unit tests.
8
+ * @internal
9
+ */
10
+ export const DEFAULT_QUERY_TIMEOUT_MS = 30_000;
11
+ /**
12
+ * Single source of truth for the read-query deadline: the per-connection
13
+ * `query_timeout_ms` override (milliseconds) when present, else the default.
14
+ * Every connector resolves through here so the default and override precedence
15
+ * live in exactly one place. A malformed override (zero, negative, non-integer,
16
+ * non-number) is a config error — surfaced here even though `ktx.yaml`
17
+ * validation also rejects it, so programmatically-built connectors cannot
18
+ * silently run unbounded.
19
+ */
20
+ export function resolveQueryDeadlineMs(connection) {
21
+ const raw = connection?.query_timeout_ms;
22
+ if (raw === undefined || raw === null) {
23
+ return DEFAULT_QUERY_TIMEOUT_MS;
24
+ }
25
+ if (typeof raw !== 'number' || !Number.isInteger(raw) || raw <= 0) {
26
+ throw new Error(`query_timeout_ms must be a positive integer in milliseconds, received ${JSON.stringify(raw)}.`);
27
+ }
28
+ return raw;
29
+ }
30
+ /**
31
+ * The canonical, driver-independent timeout error an agent sees regardless of
32
+ * which connector enforced the deadline. Reads in whole seconds. Remote
33
+ * connectors pass the driver's own timeout error as `cause`.
34
+ */
35
+ export function queryDeadlineExceededError(deadlineMs, options) {
36
+ return new KtxQueryError(`query exceeded ${Math.round(deadlineMs / 1000)}s`, options);
37
+ }
@@ -0,0 +1,3 @@
1
+ import type { ChunkResult, DiffSet, ScopeDescriptor } from '../../types.js';
2
+ export declare function chunkGdriveStagedDir(stagedDir: string, diffSet?: DiffSet): Promise<ChunkResult>;
3
+ export declare function describeGdriveScope(stagedDir: string): Promise<ScopeDescriptor>;
@@ -0,0 +1,74 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readdir, readFile } from 'node:fs/promises';
3
+ import { join, relative } from 'node:path';
4
+ import { gdriveManifestSchema, gdriveMetadataSchema } from './types.js';
5
+ const GDRIVE_RECONCILE_GUIDANCE = 'Synthesize durable wiki knowledge from this Google Doc. Preserve product definitions, process documentation, and operating rules as wiki pages. Do not create semantic-layer sources from gdrive content in v1.';
6
+ function normalizeRawPath(path) {
7
+ return path.replace(/\\/g, '/');
8
+ }
9
+ async function walk(root) {
10
+ const entries = await readdir(root, { withFileTypes: true, recursive: true });
11
+ return entries
12
+ .filter((entry) => entry.isFile())
13
+ .map((entry) => normalizeRawPath(relative(root, join(entry.parentPath, entry.name))))
14
+ .sort();
15
+ }
16
+ function safeUnitKey(path) {
17
+ return `gdrive-${path.replace(/^docs\//, '').replace(/\/page\.md$/, '').replace(/[^a-zA-Z0-9]+/g, '-').replace(/^-+|-+$/g, '')}`;
18
+ }
19
+ async function readManifest(stagedDir) {
20
+ try {
21
+ return gdriveManifestSchema.parse(JSON.parse(await readFile(join(stagedDir, 'manifest.json'), 'utf-8')));
22
+ }
23
+ catch (error) {
24
+ throw new Error(`Invalid gdrive manifest: ${error instanceof Error ? error.message : String(error)}`);
25
+ }
26
+ }
27
+ export async function chunkGdriveStagedDir(stagedDir, diffSet) {
28
+ const files = await walk(stagedDir);
29
+ const manifest = await readManifest(stagedDir);
30
+ const touched = diffSet
31
+ ? new Set([...diffSet.added, ...diffSet.modified].map((path) => normalizeRawPath(path)))
32
+ : null;
33
+ const workUnits = [];
34
+ for (const pagePath of files.filter((path) => path.endsWith('/page.md'))) {
35
+ const metadataPath = pagePath.replace(/\/page\.md$/, '/metadata.json');
36
+ const primary = [metadataPath, pagePath].filter((path) => files.includes(path));
37
+ if (touched && !primary.some((path) => touched.has(path))) {
38
+ continue;
39
+ }
40
+ const metadata = gdriveMetadataSchema.parse(JSON.parse(await readFile(join(stagedDir, metadataPath), 'utf-8')));
41
+ const rawFiles = touched ? primary.filter((path) => touched.has(path)).sort() : primary.sort();
42
+ const dependencyPaths = ['manifest.json'].filter((path) => !rawFiles.includes(path));
43
+ const excluded = new Set([...rawFiles, ...dependencyPaths]);
44
+ const peerFileIndex = files.filter((path) => !excluded.has(path)).sort();
45
+ workUnits.push({
46
+ unitKey: safeUnitKey(pagePath),
47
+ displayLabel: metadata.path,
48
+ rawFiles,
49
+ dependencyPaths,
50
+ peerFileIndex,
51
+ notes: GDRIVE_RECONCILE_GUIDANCE,
52
+ });
53
+ }
54
+ return {
55
+ workUnits,
56
+ eviction: diffSet && diffSet.deleted.length > 0
57
+ ? { deletedRawPaths: diffSet.deleted.map((path) => normalizeRawPath(path)).sort() }
58
+ : undefined,
59
+ reconcileNotes: ['Google Drive docs are knowledge-only in v1; keep output in wiki pages unless later follow-up work expands scope.'],
60
+ contextReport: { capped: false, warnings: manifest.warnings },
61
+ };
62
+ }
63
+ export async function describeGdriveScope(stagedDir) {
64
+ const manifest = await readManifest(stagedDir);
65
+ const scopeKey = JSON.stringify({
66
+ folderId: manifest.folderId,
67
+ recursive: manifest.recursive,
68
+ });
69
+ const fingerprint = createHash('sha256').update(scopeKey).digest('hex');
70
+ return {
71
+ fingerprint,
72
+ isPathInScope: (rawPath) => rawPath === 'manifest.json' || rawPath.startsWith('docs/'),
73
+ };
74
+ }
@@ -0,0 +1 @@
1
+ export declare function detectGdriveStagedDir(stagedDir: string): Promise<boolean>;