yamchart 0.9.5 → 0.10.1

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 (92) hide show
  1. package/dist/{advisor-SC64RTZO.js → advisor-5TJDNAY7.js} +24 -14
  2. package/dist/advisor-5TJDNAY7.js.map +1 -0
  3. package/dist/agent-TZZHNDPK.js +1 -0
  4. package/dist/{chunk-NXQ6ZO3V.js → chunk-5FHV22X2.js} +7 -6
  5. package/dist/chunk-5FHV22X2.js.map +1 -0
  6. package/dist/{dist-MNXSMGV6.js → chunk-64CI3HSY.js} +184 -15
  7. package/dist/chunk-64CI3HSY.js.map +1 -0
  8. package/dist/chunk-7D4SUZUM.js +1 -0
  9. package/dist/chunk-DZVT6PHW.js +1 -0
  10. package/dist/chunk-EAQXUGP6.js +1 -0
  11. package/dist/chunk-HJVVHYVN.js +1 -0
  12. package/dist/chunk-IVD4OP3K.js +1 -0
  13. package/dist/{chunk-S7YQXEKM.js → chunk-QUIDZO5G.js} +104 -275
  14. package/dist/chunk-QUIDZO5G.js.map +1 -0
  15. package/dist/{chunk-RMIDEBHD.js → chunk-S2CH4HUZ.js} +7 -6
  16. package/dist/chunk-TZKNU5TD.js +1 -0
  17. package/dist/chunk-UFDQ3C7Q.js +919 -0
  18. package/dist/chunk-UFDQ3C7Q.js.map +1 -0
  19. package/dist/chunk-VJC24RKT.js +1 -0
  20. package/dist/chunk-WYS4ULBE.js +1 -0
  21. package/dist/{chunk-H4L3FNLS.js → chunk-ZBCQNWVN.js} +3 -2
  22. package/dist/{chunk-RM6MNDVF.js → chunk-ZIY22VO7.js} +193 -11
  23. package/dist/chunk-ZIY22VO7.js.map +1 -0
  24. package/dist/chunk-ZMJPRNOA.js +1 -0
  25. package/dist/compare-RQFCEZIK.js +1 -0
  26. package/dist/connection-utils-FEUWER5I.js +20 -0
  27. package/dist/{describe-X75C2VDU.js → describe-MEP72B56.js} +7 -6
  28. package/dist/{dev-GWXHBBWB.js → dev-H6SJZ5UA.js} +1315 -73
  29. package/dist/dev-H6SJZ5UA.js.map +1 -0
  30. package/dist/dist-5DQO6L2S.js +48 -0
  31. package/dist/dist-MIKFZKSD.js +1 -0
  32. package/dist/dist-PPAD6KOM.js +99 -0
  33. package/dist/{dist-JMLAZUL7.js → dist-XNCED7JW.js} +30 -12
  34. package/dist/generate-B7FAWVIQ.js +1 -0
  35. package/dist/index.js +25 -24
  36. package/dist/init-SECXKD5G.js +1 -0
  37. package/dist/lineage-HYO4RKZT.js +1 -0
  38. package/dist/public/assets/DataView-DUCz_96y.js +9 -0
  39. package/dist/public/assets/{EventManagement-CTuAJ0eF.js → EventManagement-BnmeJDQl.js} +1 -1
  40. package/dist/public/assets/{ExplorePage-ZJ3zNjNQ.js → ExplorePage-kk4z9ldZ.js} +1 -1
  41. package/dist/public/assets/{LoginPage-wygea4BI.js → LoginPage-CzaFkkjg.js} +1 -1
  42. package/dist/public/assets/{PublicViewer-8YqGrVVB.js → PublicViewer-irjxqH6a.js} +1 -1
  43. package/dist/public/assets/{SetupWizard-DqDBwHrF.js → SetupWizard-ConWIcmy.js} +1 -1
  44. package/dist/public/assets/{ShareManagement-TAAdI_gY.js → ShareManagement-CP4wdwLR.js} +1 -1
  45. package/dist/public/assets/SourceDetailView-DZS5518E.js +1 -0
  46. package/dist/public/assets/{UserManagement-B4kZHyri.js → UserManagement-AubGd9hl.js} +1 -1
  47. package/dist/public/assets/data-3vtzSuAZ.js +1 -0
  48. package/dist/public/assets/{index-CfyF2Wf-.css → index-C1X8RW4Z.css} +1 -1
  49. package/dist/public/assets/{index-BgzSjgIu.js → index-jlfTO7f5.js} +44 -44
  50. package/dist/public/assets/{index.es-AB-GdGyc.js → index.es-CgnvEWi5.js} +1 -1
  51. package/dist/public/assets/{jspdf.es.min-ChRx2mOQ.js → jspdf.es.min-Cw5WefMt.js} +3 -3
  52. package/dist/public/index.html +2 -2
  53. package/dist/{query-QNRDS74I.js → query-2H3YOPI2.js} +6 -5
  54. package/dist/reset-password-YVCZKZPC.js +1 -0
  55. package/dist/rewrite-database-BOA4QPUR.js +1 -0
  56. package/dist/{sample-SKLHBZBU.js → sample-ODUGGSFA.js} +6 -5
  57. package/dist/{search-4KMETZVX.js → search-IPE4ISFB.js} +7 -6
  58. package/dist/{semantic-6WKELH5V.js → semantic-K3MYXXJI.js} +4 -2
  59. package/dist/{semantic-6WKELH5V.js.map → semantic-K3MYXXJI.js.map} +1 -1
  60. package/dist/source-resolver-HZQLOODU.js +19 -0
  61. package/dist/source-resolver-HZQLOODU.js.map +1 -0
  62. package/dist/sync-dbt-72GVO75P.js +1 -0
  63. package/dist/{sync-warehouse-UWRNUXE7.js → sync-warehouse-TUNULDUY.js} +6 -5
  64. package/dist/{tables-V65QUGHK.js → tables-K5NAN2WK.js} +7 -6
  65. package/dist/templates/default/docs/yamchart-reference.md +42 -0
  66. package/dist/{test-UE5OWG3E.js → test-SRHVOXZB.js} +9 -7
  67. package/dist/{test-UE5OWG3E.js.map → test-SRHVOXZB.js.map} +1 -1
  68. package/dist/update-HWCJNQRP.js +1 -0
  69. package/package.json +8 -6
  70. package/dist/advisor-SC64RTZO.js.map +0 -1
  71. package/dist/chunk-NXQ6ZO3V.js.map +0 -1
  72. package/dist/chunk-RM6MNDVF.js.map +0 -1
  73. package/dist/chunk-S7YQXEKM.js.map +0 -1
  74. package/dist/chunk-UND73EOB.js +0 -449
  75. package/dist/chunk-UND73EOB.js.map +0 -1
  76. package/dist/connection-utils-C4FQGBW6.js +0 -19
  77. package/dist/dev-GWXHBBWB.js.map +0 -1
  78. package/dist/dist-MNXSMGV6.js.map +0 -1
  79. package/dist/dist-MX5K2ABB.js +0 -56
  80. package/dist/source-resolver-R7WBIL7M.js +0 -18
  81. /package/dist/{chunk-RMIDEBHD.js.map → chunk-S2CH4HUZ.js.map} +0 -0
  82. /package/dist/{chunk-H4L3FNLS.js.map → chunk-ZBCQNWVN.js.map} +0 -0
  83. /package/dist/{connection-utils-C4FQGBW6.js.map → connection-utils-FEUWER5I.js.map} +0 -0
  84. /package/dist/{describe-X75C2VDU.js.map → describe-MEP72B56.js.map} +0 -0
  85. /package/dist/{dist-JMLAZUL7.js.map → dist-5DQO6L2S.js.map} +0 -0
  86. /package/dist/{dist-MX5K2ABB.js.map → dist-PPAD6KOM.js.map} +0 -0
  87. /package/dist/{source-resolver-R7WBIL7M.js.map → dist-XNCED7JW.js.map} +0 -0
  88. /package/dist/{query-QNRDS74I.js.map → query-2H3YOPI2.js.map} +0 -0
  89. /package/dist/{sample-SKLHBZBU.js.map → sample-ODUGGSFA.js.map} +0 -0
  90. /package/dist/{search-4KMETZVX.js.map → search-IPE4ISFB.js.map} +0 -0
  91. /package/dist/{sync-warehouse-UWRNUXE7.js.map → sync-warehouse-TUNULDUY.js.map} +0 -0
  92. /package/dist/{tables-V65QUGHK.js.map → tables-K5NAN2WK.js.map} +0 -0
@@ -1,7 +1,8 @@
1
+ import { createRequire as __yamchartCreateRequire } from 'module'; if (!globalThis.require) globalThis.require = __yamchartCreateRequire(import.meta.url);
1
2
  import {
2
3
  loadEnvFile,
3
4
  validateProject
4
- } from "./chunk-RMIDEBHD.js";
5
+ } from "./chunk-S2CH4HUZ.js";
5
6
  import {
6
7
  box,
7
8
  detail,
@@ -12,31 +13,14 @@ import {
12
13
  spinner,
13
14
  success
14
15
  } from "./chunk-HJVVHYVN.js";
15
- import {
16
- ChartSchema,
17
- ConnectionSchema,
18
- DashboardSchema,
19
- EventSchema,
20
- EventsFileSchema,
21
- ProjectSchema,
22
- ScheduleSchema,
23
- SemanticQuerySchema,
24
- deepMerge,
25
- resolveProjectConfig
26
- } from "./chunk-RM6MNDVF.js";
27
16
  import {
28
17
  StreamingChatAgent,
29
18
  filterCatalogEntries
30
19
  } from "./chunk-ZMJPRNOA.js";
31
20
  import {
32
- AuthDatabase,
33
- generateOpaqueToken,
34
- generateSessionToken,
35
- hashPassword,
36
- hashToken,
37
- parseTtl,
38
- verifyPassword
39
- } from "./chunk-DZVT6PHW.js";
21
+ createEmbedder,
22
+ createProviderForTask
23
+ } from "./chunk-64CI3HSY.js";
40
24
  import {
41
25
  BigQueryConnector,
42
26
  DuckDBConnector,
@@ -46,28 +30,55 @@ import {
46
30
  ReconnectingConnector,
47
31
  SQLiteConnector,
48
32
  SnowflakeConnector,
49
- computePreviousPeriod,
33
+ buildSourceProfile,
50
34
  createTemplateContext,
51
- expandCustomDateRange,
52
- expandDatePreset,
53
- expandRelativeDateRange,
54
- formatPeriodLabel,
55
- isCustomDateRange,
56
- isDatePreset,
57
- isRelativeDateRange,
35
+ isReadOnlySql,
58
36
  parseModelMetadata,
59
37
  renderTemplate,
60
38
  resolveBigQueryAuth,
61
39
  resolveMySQLAuth,
62
40
  resolvePostgresAuth,
63
41
  resolveSnowflakeAuth
64
- } from "./chunk-S7YQXEKM.js";
42
+ } from "./chunk-QUIDZO5G.js";
65
43
  import {
66
44
  SemanticModelBuilder,
67
45
  SemanticQueryCompiler,
68
46
  SemanticValidationError,
47
+ buildSemanticLayer,
48
+ compileMetricQuery,
49
+ computePreviousPeriod,
50
+ expandCustomDateRange,
51
+ expandDatePreset,
52
+ expandRelativeDateRange,
53
+ formatPeriodLabel,
54
+ isCustomDateRange,
55
+ isDatePreset,
56
+ isRelativeDateRange,
57
+ loadSemanticDefinitions,
69
58
  validateSemanticQuery
70
- } from "./chunk-UND73EOB.js";
59
+ } from "./chunk-UFDQ3C7Q.js";
60
+ import {
61
+ ChartSchema,
62
+ ConnectionSchema,
63
+ DashboardSchema,
64
+ EventSchema,
65
+ EventsFileSchema,
66
+ NewBlindspotSchema,
67
+ ProjectSchema,
68
+ ScheduleSchema,
69
+ SemanticQuerySchema,
70
+ deepMerge,
71
+ resolveProjectConfig
72
+ } from "./chunk-ZIY22VO7.js";
73
+ import {
74
+ AuthDatabase,
75
+ generateOpaqueToken,
76
+ generateSessionToken,
77
+ hashPassword,
78
+ hashToken,
79
+ parseTtl,
80
+ verifyPassword
81
+ } from "./chunk-DZVT6PHW.js";
71
82
  import {
72
83
  __commonJS,
73
84
  __toESM
@@ -2230,14 +2241,14 @@ var require_resolve = __commonJS({
2230
2241
  "enum",
2231
2242
  "const"
2232
2243
  ]);
2233
- function inlineRef(schema, limit = true) {
2244
+ function inlineRef(schema, limit2 = true) {
2234
2245
  if (typeof schema == "boolean")
2235
2246
  return true;
2236
- if (limit === true)
2247
+ if (limit2 === true)
2237
2248
  return !hasRef(schema);
2238
- if (!limit)
2249
+ if (!limit2)
2239
2250
  return false;
2240
- return countKeys(schema) <= limit;
2251
+ return countKeys(schema) <= limit2;
2241
2252
  }
2242
2253
  exports.inlineRef = inlineRef;
2243
2254
  var REF_KEYWORDS = /* @__PURE__ */ new Set([
@@ -7762,7 +7773,7 @@ var QueryService = class {
7762
7773
  async executeRawSql(sql) {
7763
7774
  return this.connector.execute(sql);
7764
7775
  }
7765
- async executeModel(modelName, params, userContext, limit = 1e3, filters) {
7776
+ async executeModel(modelName, params, userContext, limit2 = 1e3, filters) {
7766
7777
  const model = this.models.get(modelName);
7767
7778
  if (!model) {
7768
7779
  throw new Error(`Unknown model: ${modelName}`);
@@ -7771,9 +7782,9 @@ var QueryService = class {
7771
7782
  let sql = renderTemplate(model.sql, context);
7772
7783
  if (filters && Object.keys(filters).length > 0) {
7773
7784
  const clauses = Object.entries(filters).map(([field, value]) => `${field} = '${value.replace(/'/g, "''")}'`);
7774
- sql = `SELECT * FROM (${sql}) _detail WHERE ${clauses.join(" AND ")} LIMIT ${limit}`;
7785
+ sql = `SELECT * FROM (${sql}) _detail WHERE ${clauses.join(" AND ")} LIMIT ${limit2}`;
7775
7786
  } else {
7776
- sql = `SELECT * FROM (${sql}) _detail LIMIT ${limit}`;
7787
+ sql = `SELECT * FROM (${sql}) _detail LIMIT ${limit2}`;
7777
7788
  }
7778
7789
  return this.connector.execute(sql);
7779
7790
  }
@@ -8820,8 +8831,8 @@ async function semanticRoutes(fastify, options) {
8820
8831
  });
8821
8832
  }
8822
8833
  const { writeFileSync, mkdirSync } = await import("fs");
8823
- const { join: join11, dirname: dirname3 } = await import("path");
8824
- const fullPath = join11(projectDir, filePath);
8834
+ const { join: join12, dirname: dirname3 } = await import("path");
8835
+ const fullPath = join12(projectDir, filePath);
8825
8836
  mkdirSync(dirname3(fullPath), { recursive: true });
8826
8837
  writeFileSync(fullPath, sql, "utf-8");
8827
8838
  return { saved: true, path: filePath };
@@ -9179,6 +9190,292 @@ var GitService = class {
9179
9190
  }
9180
9191
  };
9181
9192
 
9193
+ // ../../packages/knowledge/dist/memory-store.js
9194
+ import { randomUUID } from "crypto";
9195
+
9196
+ // ../../packages/knowledge/dist/postgres-store.js
9197
+ import { randomUUID as randomUUID2 } from "crypto";
9198
+ import pg from "pg";
9199
+
9200
+ // ../../packages/knowledge/dist/migrations.js
9201
+ var MIGRATIONS = [
9202
+ `CREATE TABLE IF NOT EXISTS source_profiles (
9203
+ connection TEXT PRIMARY KEY,
9204
+ profiled_at TIMESTAMPTZ NOT NULL,
9205
+ profile JSONB NOT NULL
9206
+ );`,
9207
+ `CREATE TABLE IF NOT EXISTS hunches (
9208
+ id TEXT PRIMARY KEY,
9209
+ connection TEXT NOT NULL,
9210
+ question TEXT NOT NULL,
9211
+ sql TEXT NOT NULL,
9212
+ generated_sql TEXT NOT NULL,
9213
+ status TEXT NOT NULL DEFAULT 'pending',
9214
+ tables_used JSONB NOT NULL DEFAULT '[]',
9215
+ rationale TEXT,
9216
+ confidence DOUBLE PRECISION,
9217
+ last_run_sample JSONB,
9218
+ created_at TIMESTAMPTZ NOT NULL,
9219
+ updated_at TIMESTAMPTZ NOT NULL
9220
+ );`,
9221
+ `CREATE INDEX IF NOT EXISTS hunches_conn_status ON hunches (connection, status);`,
9222
+ `CREATE TABLE IF NOT EXISTS blindspots (
9223
+ id TEXT PRIMARY KEY, connection TEXT NOT NULL, kind TEXT NOT NULL,
9224
+ title TEXT NOT NULL, detail TEXT NOT NULL, subject TEXT,
9225
+ suggested_model JSONB, confidence DOUBLE PRECISION,
9226
+ status TEXT NOT NULL DEFAULT 'open', answer TEXT,
9227
+ created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL
9228
+ );`,
9229
+ `CREATE INDEX IF NOT EXISTS blindspots_conn_status ON blindspots (connection, status);`,
9230
+ `CREATE TABLE IF NOT EXISTS knowledge_docs (
9231
+ id TEXT PRIMARY KEY, connection TEXT NOT NULL, title TEXT NOT NULL, content TEXT NOT NULL,
9232
+ source TEXT NOT NULL DEFAULT 'ui', created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL
9233
+ );`,
9234
+ `CREATE INDEX IF NOT EXISTS knowledge_docs_conn ON knowledge_docs (connection);`,
9235
+ `CREATE TABLE IF NOT EXISTS knowledge_embeddings (
9236
+ id TEXT PRIMARY KEY, connection TEXT NOT NULL, source_kind TEXT NOT NULL, source_id TEXT NOT NULL,
9237
+ text TEXT NOT NULL, vector JSONB NOT NULL, updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
9238
+ UNIQUE (source_kind, source_id)
9239
+ );`,
9240
+ `CREATE INDEX IF NOT EXISTS knowledge_embeddings_conn ON knowledge_embeddings (connection);`
9241
+ ];
9242
+
9243
+ // ../../packages/knowledge/dist/postgres-store.js
9244
+ function rowToBlindspot(r) {
9245
+ return {
9246
+ id: r.id,
9247
+ connection: r.connection,
9248
+ kind: r.kind,
9249
+ title: r.title,
9250
+ detail: r.detail,
9251
+ subject: r.subject ?? void 0,
9252
+ suggestedModel: r.suggested_model ?? void 0,
9253
+ confidence: r.confidence ?? void 0,
9254
+ status: r.status,
9255
+ answer: r.answer ?? void 0,
9256
+ createdAt: new Date(r.created_at).toISOString(),
9257
+ updatedAt: new Date(r.updated_at).toISOString()
9258
+ };
9259
+ }
9260
+ function rowToKnowledgeDoc(r) {
9261
+ return {
9262
+ id: r.id,
9263
+ connection: r.connection,
9264
+ title: r.title,
9265
+ content: r.content,
9266
+ source: r.source,
9267
+ createdAt: new Date(r.created_at).toISOString(),
9268
+ updatedAt: new Date(r.updated_at).toISOString()
9269
+ };
9270
+ }
9271
+ function rowToHunch(r) {
9272
+ return {
9273
+ id: r.id,
9274
+ connection: r.connection,
9275
+ question: r.question,
9276
+ sql: r.sql,
9277
+ generatedSql: r.generated_sql,
9278
+ status: r.status,
9279
+ tablesUsed: r.tables_used ?? [],
9280
+ rationale: r.rationale ?? void 0,
9281
+ confidence: r.confidence ?? void 0,
9282
+ lastRunSample: r.last_run_sample ?? void 0,
9283
+ createdAt: new Date(r.created_at).toISOString(),
9284
+ updatedAt: new Date(r.updated_at).toISOString()
9285
+ };
9286
+ }
9287
+ var PostgresKnowledgeStore = class {
9288
+ pool;
9289
+ constructor(connectionString) {
9290
+ this.pool = new pg.Pool({ connectionString });
9291
+ }
9292
+ async init() {
9293
+ for (const m of MIGRATIONS)
9294
+ await this.pool.query(m);
9295
+ }
9296
+ async saveProfile(connection, profile) {
9297
+ await this.pool.query(`INSERT INTO source_profiles (connection, profiled_at, profile) VALUES ($1,$2,$3)
9298
+ ON CONFLICT (connection) DO UPDATE SET profiled_at = EXCLUDED.profiled_at, profile = EXCLUDED.profile`, [connection, profile.profiledAt, JSON.stringify(profile)]);
9299
+ }
9300
+ async getProfile(connection) {
9301
+ const { rows } = await this.pool.query(`SELECT profile FROM source_profiles WHERE connection = $1`, [connection]);
9302
+ return rows[0]?.profile;
9303
+ }
9304
+ async createHunches(items) {
9305
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9306
+ const out = [];
9307
+ for (const it of items) {
9308
+ const id = randomUUID2();
9309
+ const { rows } = await this.pool.query(`INSERT INTO hunches (id, connection, question, sql, generated_sql, status, tables_used, rationale, confidence, created_at, updated_at)
9310
+ VALUES ($1,$2,$3,$4,$5,'pending',$6,$7,$8,$9,$9) RETURNING *`, [id, it.connection, it.question, it.sql, it.generatedSql, JSON.stringify(it.tablesUsed ?? []), it.rationale ?? null, it.confidence ?? null, now]);
9311
+ out.push(rowToHunch(rows[0]));
9312
+ }
9313
+ return out;
9314
+ }
9315
+ async listHunches(filter) {
9316
+ const clauses = [];
9317
+ const params = [];
9318
+ if (filter.connection) {
9319
+ params.push(filter.connection);
9320
+ clauses.push(`connection = $${params.length}`);
9321
+ }
9322
+ if (filter.status) {
9323
+ params.push(filter.status);
9324
+ clauses.push(`status = $${params.length}`);
9325
+ }
9326
+ const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
9327
+ const { rows } = await this.pool.query(`SELECT * FROM hunches ${where} ORDER BY created_at DESC`, params);
9328
+ return rows.map(rowToHunch);
9329
+ }
9330
+ async getHunch(id) {
9331
+ const { rows } = await this.pool.query(`SELECT * FROM hunches WHERE id = $1`, [id]);
9332
+ return rows[0] ? rowToHunch(rows[0]) : void 0;
9333
+ }
9334
+ async updateHunch(id, patch) {
9335
+ const sets = [];
9336
+ const params = [];
9337
+ if (patch.sql !== void 0) {
9338
+ params.push(patch.sql);
9339
+ sets.push(`sql = $${params.length}`);
9340
+ }
9341
+ if (patch.status !== void 0) {
9342
+ params.push(patch.status);
9343
+ sets.push(`status = $${params.length}`);
9344
+ }
9345
+ if (patch.lastRunSample !== void 0) {
9346
+ params.push(JSON.stringify(patch.lastRunSample));
9347
+ sets.push(`last_run_sample = $${params.length}`);
9348
+ }
9349
+ params.push((/* @__PURE__ */ new Date()).toISOString());
9350
+ sets.push(`updated_at = $${params.length}`);
9351
+ params.push(id);
9352
+ const { rows } = await this.pool.query(`UPDATE hunches SET ${sets.join(", ")} WHERE id = $${params.length} RETURNING *`, params);
9353
+ if (!rows[0])
9354
+ throw new Error(`Hunch "${id}" not found.`);
9355
+ return rowToHunch(rows[0]);
9356
+ }
9357
+ async createBlindspots(items) {
9358
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9359
+ const out = [];
9360
+ for (const it of items) {
9361
+ const id = randomUUID2();
9362
+ const { rows } = await this.pool.query(`INSERT INTO blindspots (id, connection, kind, title, detail, subject, suggested_model, confidence, status, created_at, updated_at)
9363
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,'open',$9,$9) RETURNING *`, [id, it.connection, it.kind, it.title, it.detail, it.subject ?? null, JSON.stringify(it.suggestedModel ?? null), it.confidence ?? null, now]);
9364
+ out.push(rowToBlindspot(rows[0]));
9365
+ }
9366
+ return out;
9367
+ }
9368
+ async listBlindspots(filter) {
9369
+ const clauses = [];
9370
+ const params = [];
9371
+ if (filter.connection) {
9372
+ params.push(filter.connection);
9373
+ clauses.push(`connection = $${params.length}`);
9374
+ }
9375
+ if (filter.status) {
9376
+ params.push(filter.status);
9377
+ clauses.push(`status = $${params.length}`);
9378
+ }
9379
+ if (filter.kind) {
9380
+ params.push(filter.kind);
9381
+ clauses.push(`kind = $${params.length}`);
9382
+ }
9383
+ const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
9384
+ const { rows } = await this.pool.query(`SELECT * FROM blindspots ${where} ORDER BY created_at DESC`, params);
9385
+ return rows.map(rowToBlindspot);
9386
+ }
9387
+ async getBlindspot(id) {
9388
+ const { rows } = await this.pool.query(`SELECT * FROM blindspots WHERE id = $1`, [id]);
9389
+ return rows[0] ? rowToBlindspot(rows[0]) : void 0;
9390
+ }
9391
+ async updateBlindspot(id, patch) {
9392
+ const sets = [];
9393
+ const params = [];
9394
+ if (patch.status !== void 0) {
9395
+ params.push(patch.status);
9396
+ sets.push(`status = $${params.length}`);
9397
+ }
9398
+ if (patch.answer !== void 0) {
9399
+ params.push(patch.answer);
9400
+ sets.push(`answer = $${params.length}`);
9401
+ }
9402
+ params.push((/* @__PURE__ */ new Date()).toISOString());
9403
+ sets.push(`updated_at = $${params.length}`);
9404
+ params.push(id);
9405
+ const { rows } = await this.pool.query(`UPDATE blindspots SET ${sets.join(", ")} WHERE id = $${params.length} RETURNING *`, params);
9406
+ if (!rows[0])
9407
+ throw new Error(`Blindspot "${id}" not found.`);
9408
+ return rowToBlindspot(rows[0]);
9409
+ }
9410
+ async deleteOpenBlindspots(connection) {
9411
+ await this.pool.query(`DELETE FROM blindspots WHERE connection = $1 AND status = 'open'`, [connection]);
9412
+ }
9413
+ async createKnowledgeDoc(doc) {
9414
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9415
+ const id = randomUUID2();
9416
+ const { rows } = await this.pool.query(`INSERT INTO knowledge_docs (id, connection, title, content, source, created_at, updated_at)
9417
+ VALUES ($1,$2,$3,$4,$5,$6,$6) RETURNING *`, [id, doc.connection, doc.title, doc.content, doc.source, now]);
9418
+ return [rowToKnowledgeDoc(rows[0])];
9419
+ }
9420
+ async listKnowledgeDocs(filter) {
9421
+ const clauses = [];
9422
+ const params = [];
9423
+ if (filter.connection) {
9424
+ params.push(filter.connection);
9425
+ clauses.push(`connection = $${params.length}`);
9426
+ }
9427
+ const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
9428
+ const { rows } = await this.pool.query(`SELECT * FROM knowledge_docs ${where} ORDER BY created_at DESC`, params);
9429
+ return rows.map(rowToKnowledgeDoc);
9430
+ }
9431
+ async updateKnowledgeDoc(id, patch) {
9432
+ const sets = [];
9433
+ const params = [];
9434
+ if (patch.title !== void 0) {
9435
+ params.push(patch.title);
9436
+ sets.push(`title = $${params.length}`);
9437
+ }
9438
+ if (patch.content !== void 0) {
9439
+ params.push(patch.content);
9440
+ sets.push(`content = $${params.length}`);
9441
+ }
9442
+ params.push((/* @__PURE__ */ new Date()).toISOString());
9443
+ sets.push(`updated_at = $${params.length}`);
9444
+ params.push(id);
9445
+ const { rows } = await this.pool.query(`UPDATE knowledge_docs SET ${sets.join(", ")} WHERE id = $${params.length} RETURNING *`, params);
9446
+ if (!rows[0])
9447
+ throw new Error(`KnowledgeDoc "${id}" not found.`);
9448
+ return rowToKnowledgeDoc(rows[0]);
9449
+ }
9450
+ async deleteKnowledgeDoc(id) {
9451
+ await this.pool.query(`DELETE FROM knowledge_docs WHERE id = $1`, [id]);
9452
+ }
9453
+ async upsertEmbedding(e) {
9454
+ const id = `${e.source_kind}:${e.source_id}`;
9455
+ await this.pool.query(`INSERT INTO knowledge_embeddings (id, connection, source_kind, source_id, text, vector, updated_at)
9456
+ VALUES ($1,$2,$3,$4,$5,$6,now())
9457
+ ON CONFLICT (source_kind, source_id) DO UPDATE SET text=EXCLUDED.text, vector=EXCLUDED.vector, updated_at=now()`, [id, e.connection, e.source_kind, e.source_id, e.text, JSON.stringify(e.vector)]);
9458
+ }
9459
+ async listEmbeddings(filter) {
9460
+ const clauses = [];
9461
+ const params = [];
9462
+ if (filter.connection) {
9463
+ params.push(filter.connection);
9464
+ clauses.push(`connection = $${params.length}`);
9465
+ }
9466
+ const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
9467
+ const { rows } = await this.pool.query(`SELECT * FROM knowledge_embeddings ${where}`, params);
9468
+ return rows.map((r) => ({ id: r.id, connection: r.connection, source_kind: r.source_kind, source_id: r.source_id, text: r.text, vector: r.vector }));
9469
+ }
9470
+ async getEmbedding(source_kind, source_id) {
9471
+ const { rows } = await this.pool.query(`SELECT * FROM knowledge_embeddings WHERE source_kind = $1 AND source_id = $2`, [source_kind, source_id]);
9472
+ if (!rows[0])
9473
+ return void 0;
9474
+ const r = rows[0];
9475
+ return { id: r.id, connection: r.connection, source_kind: r.source_kind, source_id: r.source_id, text: r.text, vector: r.vector };
9476
+ }
9477
+ };
9478
+
9182
9479
  // ../../node_modules/.pnpm/tslib@2.8.1/node_modules/tslib/tslib.es6.mjs
9183
9480
  function __rest(s, e) {
9184
9481
  var t = {};
@@ -11868,8 +12165,8 @@ var RealtimeChannel = class _RealtimeChannel {
11868
12165
  _trigger(type, payload, ref) {
11869
12166
  var _a, _b;
11870
12167
  const typeLower = type.toLocaleLowerCase();
11871
- const { close, error: error2, leave, join: join11 } = CHANNEL_EVENTS;
11872
- const events = [close, error2, leave, join11];
12168
+ const { close, error: error2, leave, join: join12 } = CHANNEL_EVENTS;
12169
+ const events = [close, error2, leave, join12];
11873
12170
  if (ref && events.indexOf(typeLower) >= 0 && ref !== this._joinRef()) {
11874
12171
  return;
11875
12172
  }
@@ -21429,7 +21726,7 @@ async function chatRoutes(fastify, options) {
21429
21726
  systemParts.push("Output ONLY the new text to insert. Do NOT repeat the existing content.");
21430
21727
  }
21431
21728
  const systemPrompt = systemParts.join("\n");
21432
- const { AnthropicProvider } = await import("./dist-MNXSMGV6.js");
21729
+ const { AnthropicProvider } = await import("./dist-5DQO6L2S.js");
21433
21730
  const provider = new AnthropicProvider(apiKey);
21434
21731
  const response = await provider.chat({
21435
21732
  system: systemPrompt,
@@ -28527,8 +28824,8 @@ async function runSql(deps, args) {
28527
28824
  if (DESTRUCTIVE.test(sql) || /^\s*WITH\b/i.test(sql) && WRITABLE_CTE.test(sql)) {
28528
28825
  return "Error: Destructive SQL statements are not allowed through the MCP.";
28529
28826
  }
28530
- const limit = args.limit ?? DEFAULT_LIMIT;
28531
- const limited = /\blimit\b/i.test(sql) ? sql : `${sql.replace(/;\s*$/, "")} LIMIT ${limit}`;
28827
+ const limit2 = args.limit ?? DEFAULT_LIMIT;
28828
+ const limited = /\blimit\b/i.test(sql) ? sql : `${sql.replace(/;\s*$/, "")} LIMIT ${limit2}`;
28532
28829
  const result = await deps.queryService.executeRawSql(limited);
28533
28830
  return JSON.stringify({ columns: result.columns, rows: result.rows });
28534
28831
  }
@@ -28584,8 +28881,9 @@ async function listTables(deps) {
28584
28881
  })));
28585
28882
  }
28586
28883
  async function describeTable(deps, args) {
28587
- const parts = args.table.split(".");
28588
- const tableName = parts[parts.length - 1] ?? args.table;
28884
+ const normalizedTable = args.schema != null && !args.table.includes(".") ? `${args.schema}.${args.table}` : args.table;
28885
+ const parts = normalizedTable.split(".");
28886
+ const tableName = parts[parts.length - 1] ?? normalizedTable;
28589
28887
  const schema = parts.length > 1 ? parts.slice(0, -1).join(".") : void 0;
28590
28888
  const tables = await readCatalogTables(deps.projectDir, includeFilter(deps));
28591
28889
  const match = tables.find((t) => t.name === tableName && (schema === void 0 || t.schema === schema));
@@ -28603,14 +28901,14 @@ async function sampleTable(deps, args) {
28603
28901
  if (!TABLE_IDENT.test(args.table)) {
28604
28902
  return `Error: Invalid table identifier "${args.table}".`;
28605
28903
  }
28606
- const limit = Math.min(Math.max(args.limit ?? 5, 1), 100);
28904
+ const limit2 = Math.min(Math.max(args.limit ?? 5, 1), 100);
28607
28905
  const parts = args.table.split(".");
28608
28906
  const tableName = parts[parts.length - 1] ?? args.table;
28609
28907
  const providedSchema = parts.length > 1 ? parts.slice(0, -1).join(".") : void 0;
28610
28908
  const tables = await readCatalogTables(deps.projectDir, includeFilter(deps));
28611
28909
  const match = tables.find((t) => t.name === tableName && (providedSchema === void 0 || t.schema === providedSchema));
28612
28910
  const from = providedSchema === void 0 && match?.schema ? `${match.schema}.${args.table}` : args.table;
28613
- const result = await deps.queryService.executeRawSql(`SELECT * FROM ${from} LIMIT ${limit}`);
28911
+ const result = await deps.queryService.executeRawSql(`SELECT * FROM ${from} LIMIT ${limit2}`);
28614
28912
  return JSON.stringify({ columns: result.columns, rows: result.rows });
28615
28913
  }
28616
28914
 
@@ -28691,11 +28989,40 @@ function renderChartToPng(option, opts = {}) {
28691
28989
  return Buffer.from(resvg.render().asPng());
28692
28990
  }
28693
28991
 
28992
+ // ../../packages/mcp-core/dist/chart-errors.js
28993
+ function flattenIssues(issues) {
28994
+ const flat = [];
28995
+ for (const issue of issues) {
28996
+ if (issue.code === "invalid_union" && issue.unionErrors?.length) {
28997
+ const typeMatchMember = issue.unionErrors.find((e) => !e.issues.some((i) => i.path.length >= 2 && i.path[i.path.length - 1] === "type" && i.code === "invalid_literal"));
28998
+ const best = typeMatchMember ?? issue.unionErrors.reduce((a, b) => a.issues.length <= b.issues.length ? a : b);
28999
+ flat.push(...flattenIssues(best.issues));
29000
+ } else {
29001
+ const path = issue.path.length ? issue.path.join(".") : "(root)";
29002
+ let detail2 = issue.message;
29003
+ if (/expected object, received string/i.test(issue.message)) {
29004
+ detail2 = `expected an object like { field: "<column>", type: "temporal|quantitative|ordinal|nominal" }, received a string`;
29005
+ }
29006
+ flat.push({ path, message: detail2 });
29007
+ }
29008
+ }
29009
+ return flat;
29010
+ }
29011
+ function formatChartErrors(error2) {
29012
+ const issues = flattenIssues(error2.issues);
29013
+ const lines = issues.map((i) => ` \u2022 ${i.path}: ${i.message}`);
29014
+ return [
29015
+ "Invalid chart spec:",
29016
+ ...lines,
29017
+ "Tip: call get_chart_schema (optionally with { type }) for the exact shape and a working example."
29018
+ ].join("\n");
29019
+ }
29020
+
28694
29021
  // ../../packages/mcp-core/dist/tools/visualize.js
28695
29022
  async function visualize(_deps, args) {
28696
29023
  const parsed = ChartSchema.safeParse(args.spec);
28697
29024
  if (!parsed.success) {
28698
- return { error: `Invalid chart spec: ${parsed.error.issues.map((i) => i.message).join("; ")}` };
29025
+ return { error: formatChartErrors(parsed.error) };
28699
29026
  }
28700
29027
  try {
28701
29028
  const option = buildEChartsOption(parsed.data, args.data ?? []);
@@ -28722,7 +29049,7 @@ async function saveChart(deps, args) {
28722
29049
  }
28723
29050
  const parsed = ChartSchema.safeParse(args.spec);
28724
29051
  if (!parsed.success) {
28725
- return `Error: Invalid chart spec: ${parsed.error.issues.map((i) => i.message).join("; ")}`;
29052
+ return `Error: ${formatChartErrors(parsed.error)}`;
28726
29053
  }
28727
29054
  const name = parsed.data.name;
28728
29055
  if (!SAFE_NAME.test(name)) {
@@ -28738,6 +29065,450 @@ async function saveChart(deps, args) {
28738
29065
  return `Chart "${name}" saved to charts/${name}.yaml`;
28739
29066
  }
28740
29067
 
29068
+ // ../../packages/mcp-core/dist/suggested-chart.js
29069
+ function suggestChart(input) {
29070
+ const { name, title, metrics, timeDimension, dimensions } = input;
29071
+ const primary = metrics[0] ?? { name: "value", format: void 0 };
29072
+ if (dimensions.length === 0) {
29073
+ return {
29074
+ name,
29075
+ title,
29076
+ chart: {
29077
+ type: "kpi",
29078
+ value: { field: primary.name },
29079
+ // KpiFormatSchema requires `type` — always include it.
29080
+ format: primary.format?.includes("$") ? { type: "currency", currency: "USD", decimals: 0 } : { type: "number" }
29081
+ }
29082
+ };
29083
+ }
29084
+ const groupField = dimensions[0] ?? "dimension";
29085
+ const isTime = timeDimension && dimensions.includes(timeDimension);
29086
+ return {
29087
+ name,
29088
+ title,
29089
+ chart: {
29090
+ type: "bar",
29091
+ x: { field: groupField, type: isTime ? "temporal" : "nominal" },
29092
+ y: {
29093
+ field: primary.name,
29094
+ type: "quantitative",
29095
+ label: primary.name,
29096
+ ...primary.format ? { format: primary.format } : {}
29097
+ }
29098
+ }
29099
+ };
29100
+ }
29101
+
29102
+ // ../../packages/mcp-core/dist/tools/semantic.js
29103
+ function listMetrics(deps) {
29104
+ return JSON.stringify(deps.semanticLayer.listMetrics(), null, 2);
29105
+ }
29106
+ function getMetric(deps, args) {
29107
+ const resolved = deps.semanticLayer.resolveMetric(args.name);
29108
+ if (!resolved) {
29109
+ return JSON.stringify({ error: `Unknown metric "${args.name}". Call list_metrics to see governed metrics.` });
29110
+ }
29111
+ const { metric, entity } = resolved;
29112
+ return JSON.stringify({
29113
+ name: metric.name,
29114
+ label: metric.label,
29115
+ description: metric.description,
29116
+ entity: entity.name,
29117
+ source: entity.sourceRef,
29118
+ agg: metric.agg,
29119
+ field: metric.field,
29120
+ sql: metric.sql,
29121
+ filter: metric.filter,
29122
+ filter_sql: metric.filter ? entity.filters[metric.filter]?.sql : void 0,
29123
+ synonyms: metric.synonyms ?? [],
29124
+ format: metric.format,
29125
+ dimensions: entity.dimensions,
29126
+ time_dimension: entity.timeDimension
29127
+ }, null, 2);
29128
+ }
29129
+ async function queryMetric(deps, args) {
29130
+ const resolved = args.metrics.map((m) => {
29131
+ const r = deps.semanticLayer.resolveMetric(m);
29132
+ if (!r)
29133
+ throw new Error(`Unknown metric "${m}". Call list_metrics.`);
29134
+ return r;
29135
+ });
29136
+ if (resolved.length === 0) {
29137
+ return JSON.stringify({ error: "At least one metric is required." });
29138
+ }
29139
+ const entity = resolved[0].entity;
29140
+ if (resolved.some((r) => r.entity.name !== entity.name)) {
29141
+ return JSON.stringify({
29142
+ error: `Metrics span multiple entities (${[...new Set(resolved.map((r) => r.entity.name))].join(", ")}). Query one entity at a time in Phase 1.`
29143
+ });
29144
+ }
29145
+ const canonicalMetrics = resolved.map((r) => r.metric.name);
29146
+ const { sql, metrics } = compileMetricQuery(entity, { ...args, metrics: canonicalMetrics }, deps.connectionType);
29147
+ const result = await deps.queryService.executeRawSql(sql);
29148
+ const explicitDimensions = args.dimensions ?? [];
29149
+ const effectiveDimensions = args.grain && entity.timeDimension && !explicitDimensions.includes(entity.timeDimension) ? [entity.timeDimension, ...explicitDimensions] : explicitDimensions;
29150
+ const suggested_chart = suggestChart({
29151
+ name: `metric_${canonicalMetrics.join("_")}`.slice(0, 60),
29152
+ title: metrics.map((m) => m.label ?? m.name).join(", "),
29153
+ metrics: metrics.map((m) => ({ name: m.name, format: m.format })),
29154
+ timeDimension: entity.timeDimension,
29155
+ dimensions: effectiveDimensions
29156
+ });
29157
+ return JSON.stringify({ columns: result.columns, rows: result.rows, sql, suggested_chart }, null, 2);
29158
+ }
29159
+
29160
+ // ../../packages/mcp-core/dist/tools/chart-dx.js
29161
+ var EXAMPLES = {
29162
+ bar: {
29163
+ name: "revenue_by_month",
29164
+ title: "Revenue by Month",
29165
+ source: { model: "monthly_revenue" },
29166
+ chart: {
29167
+ type: "bar",
29168
+ x: { field: "month", type: "temporal" },
29169
+ y: { field: "invoiced_revenue", type: "quantitative", format: "$,.0f" }
29170
+ }
29171
+ },
29172
+ line: {
29173
+ name: "revenue_trend",
29174
+ title: "Revenue Trend",
29175
+ source: { model: "monthly_revenue" },
29176
+ chart: {
29177
+ type: "line",
29178
+ x: { field: "month", type: "temporal" },
29179
+ y: { field: "invoiced_revenue", type: "quantitative" }
29180
+ }
29181
+ },
29182
+ area: {
29183
+ name: "revenue_area",
29184
+ title: "Revenue Area",
29185
+ source: { model: "monthly_revenue" },
29186
+ chart: {
29187
+ type: "area",
29188
+ x: { field: "month", type: "temporal" },
29189
+ y: { field: "invoiced_revenue", type: "quantitative" }
29190
+ }
29191
+ },
29192
+ kpi: {
29193
+ name: "total_revenue",
29194
+ title: "Total Revenue",
29195
+ source: { model: "total_revenue" },
29196
+ chart: {
29197
+ type: "kpi",
29198
+ value: { field: "total_revenue" },
29199
+ format: { type: "currency", currency: "USD", decimals: 0 }
29200
+ }
29201
+ },
29202
+ pie: {
29203
+ name: "revenue_by_category",
29204
+ title: "Revenue by Category",
29205
+ source: { model: "category_revenue" },
29206
+ chart: {
29207
+ type: "pie",
29208
+ x: { field: "category", type: "nominal" },
29209
+ y: { field: "revenue", type: "quantitative" }
29210
+ }
29211
+ },
29212
+ scatter: {
29213
+ name: "jobs_scatter",
29214
+ title: "Jobs Scatter",
29215
+ source: { model: "jobs" },
29216
+ chart: {
29217
+ type: "scatter",
29218
+ x: { field: "cost", type: "quantitative" },
29219
+ y: { field: "revenue", type: "quantitative" }
29220
+ }
29221
+ },
29222
+ table: {
29223
+ name: "jobs_table",
29224
+ title: "Jobs Table",
29225
+ source: { model: "jobs" },
29226
+ chart: {
29227
+ type: "table",
29228
+ columns: [
29229
+ { field: "job_id", label: "Job ID" },
29230
+ { field: "revenue", label: "Revenue", format: "$,.0f" }
29231
+ ]
29232
+ }
29233
+ }
29234
+ };
29235
+ function getChartSchema(args) {
29236
+ const type = args.type != null && EXAMPLES[args.type] != null ? args.type : "bar";
29237
+ return JSON.stringify({
29238
+ note: "Axes are objects: { field, type: temporal|quantitative|ordinal|nominal }. source is { model } or { sql }.",
29239
+ available_examples: Object.keys(EXAMPLES),
29240
+ example: EXAMPLES[type]
29241
+ }, null, 2);
29242
+ }
29243
+ function validateChart(args) {
29244
+ const parsed = ChartSchema.safeParse(args.spec);
29245
+ if (parsed.success)
29246
+ return JSON.stringify({ valid: true });
29247
+ return JSON.stringify({ valid: false, errors: formatChartErrors(parsed.error) });
29248
+ }
29249
+ function getChartSpec(deps, args) {
29250
+ const spec = deps.configLoader.getChartByName(args.name);
29251
+ if (spec == null) {
29252
+ return JSON.stringify({ error: `Chart "${args.name}" not found. Call list_charts to see available charts.` });
29253
+ }
29254
+ return JSON.stringify(spec, null, 2);
29255
+ }
29256
+
29257
+ // ../../packages/learning/dist/prompt.js
29258
+ function buildGenerationPrompt(profile, metricNames, count, contextBlock = "") {
29259
+ const tables = profile.tables.map((t) => {
29260
+ const cols = t.columns.map((c) => {
29261
+ const top = c.topValues?.length ? ` top:[${c.topValues.slice(0, 6).map((v) => JSON.stringify(v.value)).join(", ")}]` : "";
29262
+ const dist = c.distinctCount != null ? ` distinct:${c.distinctCount}` : "";
29263
+ return ` - ${c.name} (${c.type})${dist}${top}`;
29264
+ }).join("\n");
29265
+ return ` ${t.table} (${t.rowCount ?? "?"} rows):
29266
+ ${cols}`;
29267
+ }).join("\n");
29268
+ return [
29269
+ `You are profiling a data warehouse to propose ${count} useful business questions a user would ask, each with correct read-only SQL.`,
29270
+ contextBlock ? `Known business context:
29271
+ ${contextBlock}` : "",
29272
+ metricNames.length ? `Prefer these governed metrics when relevant: ${metricNames.join(", ")}.` : "",
29273
+ `Use the ACTUAL column values shown (e.g. status values) \u2014 do not invent values.`,
29274
+ `Tables and profiled columns:`,
29275
+ tables,
29276
+ `Respond with ONLY JSON: {"hunches":[{"question":string,"sql":string,"tablesUsed":string[],"confidence":number,"rationale":string}]}. SQL must be a single read-only SELECT.`
29277
+ ].filter(Boolean).join("\n");
29278
+ }
29279
+
29280
+ // ../../packages/learning/dist/json.js
29281
+ function extractJson(text2) {
29282
+ const start = text2.indexOf("{");
29283
+ if (start === -1)
29284
+ return {};
29285
+ let depth = 0;
29286
+ for (let i = start; i < text2.length; i++) {
29287
+ if (text2[i] === "{")
29288
+ depth++;
29289
+ else if (text2[i] === "}") {
29290
+ depth--;
29291
+ if (depth === 0) {
29292
+ try {
29293
+ return JSON.parse(text2.slice(start, i + 1));
29294
+ } catch {
29295
+ return {};
29296
+ }
29297
+ }
29298
+ }
29299
+ }
29300
+ return {};
29301
+ }
29302
+
29303
+ // ../../packages/learning/dist/generator.js
29304
+ async function generateHunches(input) {
29305
+ const count = input.count ?? 20;
29306
+ const prompt = buildGenerationPrompt(input.profile, input.metricNames, count, input.contextBlock ?? "");
29307
+ const res = await input.provider.chat({ system: "You generate governed analytics questions and SQL.", messages: [{ role: "user", content: prompt }], tools: [], maxTokens: 4e3 });
29308
+ const text2 = res.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
29309
+ const parsed = extractJson(text2);
29310
+ const raw = Array.isArray(parsed.hunches) ? parsed.hunches : [];
29311
+ const hunches = [];
29312
+ for (const r of raw) {
29313
+ const sql = typeof r.sql === "string" ? r.sql : "";
29314
+ const question = typeof r.question === "string" ? r.question : "";
29315
+ if (!sql || !question || !isReadOnlySql(sql))
29316
+ continue;
29317
+ hunches.push({
29318
+ connection: input.connection,
29319
+ question,
29320
+ sql,
29321
+ generatedSql: sql,
29322
+ tablesUsed: Array.isArray(r.tablesUsed) ? r.tablesUsed : [],
29323
+ rationale: typeof r.rationale === "string" ? r.rationale : void 0,
29324
+ confidence: typeof r.confidence === "number" ? r.confidence : void 0
29325
+ });
29326
+ }
29327
+ return hunches;
29328
+ }
29329
+
29330
+ // ../../packages/learning/dist/blindspot.js
29331
+ function buildBlindspotPrompt(profile, metricNames, contextBlock, modelGraph) {
29332
+ const tables = profile.tables.map((t) => {
29333
+ const cols = t.columns.map((c) => {
29334
+ const top = c.topValues?.length ? ` values:[${c.topValues.slice(0, 6).map((v) => JSON.stringify(v.value)).join(", ")}]` : "";
29335
+ return ` - ${c.name} (${c.type})${top}`;
29336
+ }).join("\n");
29337
+ return ` ${t.table}:
29338
+ ${cols}`;
29339
+ }).join("\n");
29340
+ return [
29341
+ `You are auditing a data warehouse to find where you LACK business context and where useful marts are missing.`,
29342
+ contextBlock ? `Known business context:
29343
+ ${contextBlock}` : "",
29344
+ metricNames.length ? `Governed metrics: ${metricNames.join(", ")}.` : "",
29345
+ modelGraph ? `Model lineage (raw \u2192 staging \u2192 marts):
29346
+ ${modelGraph}` : "",
29347
+ `Profiled tables:
29348
+ ${tables}`,
29349
+ `Return ONLY JSON: {"blindspots":[{"kind":"context","title":string,"detail":string,"subject":string,"confidence":number} | {"kind":"opportunity","title":string,"detail":string,"suggestedModel":{"name":string,"description":string,"rationale":string,"columns":[{"name":string,"description":string}]},"confidence":number}]}.`,
29350
+ `"context" = an ambiguity you need explained to write correct SQL (use the ACTUAL values shown). "opportunity" = a useful mart that does not yet exist.`
29351
+ ].filter(Boolean).join("\n\n");
29352
+ }
29353
+ async function scanBlindspots(input) {
29354
+ const prompt = buildBlindspotPrompt(input.profile, input.metricNames, input.contextBlock, input.modelGraph);
29355
+ const res = await input.provider.chat({ system: "You audit data warehouses for missing business context and missing marts.", messages: [{ role: "user", content: prompt }], tools: [], maxTokens: 4e3 });
29356
+ const text2 = res.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
29357
+ const parsed = extractJson(text2);
29358
+ const raw = Array.isArray(parsed.blindspots) ? parsed.blindspots : [];
29359
+ const items = [];
29360
+ for (const r of raw) {
29361
+ const candidate = { ...r, connection: input.connection };
29362
+ const result = NewBlindspotSchema.safeParse(candidate);
29363
+ if (result.success)
29364
+ items.push(result.data);
29365
+ }
29366
+ return items;
29367
+ }
29368
+
29369
+ // ../../packages/learning/dist/context.js
29370
+ import { readFileSync as readFileSync2, existsSync as existsSync2, readdirSync } from "fs";
29371
+ import { join as join9 } from "path";
29372
+ var MAX_DOC = 6e3;
29373
+ var MAX_TOTAL = 16e3;
29374
+ function loadKnowledgeDocs(projectDir) {
29375
+ const dir = join9(projectDir, "knowledge");
29376
+ if (!existsSync2(dir))
29377
+ return [];
29378
+ return readdirSync(dir).filter((f) => /\.md$/i.test(f)).map((f) => readFileSync2(join9(dir, f), "utf8"));
29379
+ }
29380
+ function buildContextBlock(input) {
29381
+ const parts = [];
29382
+ for (const doc of input.docs) {
29383
+ const d = doc.length > MAX_DOC ? doc.slice(0, MAX_DOC) + "\n\u2026(truncated)" : doc;
29384
+ parts.push(d);
29385
+ }
29386
+ if (input.answeredBlindspots.length) {
29387
+ parts.push("Confirmed business context (from answered questions):");
29388
+ for (const a of input.answeredBlindspots)
29389
+ parts.push(`- ${a.title}: ${a.answer}`);
29390
+ }
29391
+ let block = parts.join("\n\n").trim();
29392
+ if (block.length > MAX_TOTAL)
29393
+ block = block.slice(0, MAX_TOTAL) + "\n\u2026(truncated)";
29394
+ return block;
29395
+ }
29396
+
29397
+ // ../../packages/learning/dist/retrieval.js
29398
+ function cosineSimilarity(a, b) {
29399
+ let dot = 0, na = 0, nb = 0;
29400
+ for (let i = 0; i < a.length; i++) {
29401
+ dot += a[i] * b[i];
29402
+ na += a[i] * a[i];
29403
+ nb += b[i] * b[i];
29404
+ }
29405
+ if (na === 0 || nb === 0)
29406
+ return 0;
29407
+ return dot / (Math.sqrt(na) * Math.sqrt(nb));
29408
+ }
29409
+ async function retrieveGrounding(input) {
29410
+ const { question, connection, store, embedder } = input;
29411
+ const topK = input.topK ?? 5;
29412
+ const corpus = [];
29413
+ for (const h of await store.listHunches({ connection, status: "confirmed" }))
29414
+ corpus.push({ kind: "hunch", id: h.id, text: h.question, payload: { sql: h.sql, title: h.question } });
29415
+ for (const b of await store.listBlindspots({ connection, status: "answered" }))
29416
+ if (b.answer)
29417
+ corpus.push({ kind: "blindspot", id: b.id, text: `${b.title}: ${b.answer}`, payload: { title: b.title } });
29418
+ for (const d of await store.listKnowledgeDocs({ connection }))
29419
+ corpus.push({ kind: "knowledge", id: d.id, text: `${d.title}
29420
+ ${d.content}`, payload: { title: d.title } });
29421
+ if (corpus.length === 0)
29422
+ return [];
29423
+ const toEmbed = [];
29424
+ const vectors = /* @__PURE__ */ new Map();
29425
+ for (const item of corpus) {
29426
+ const cached = await store.getEmbedding(item.kind, item.id);
29427
+ if (cached && cached.text === item.text)
29428
+ vectors.set(item.id, cached.vector);
29429
+ else
29430
+ toEmbed.push(item);
29431
+ }
29432
+ if (toEmbed.length) {
29433
+ const embs = await embedder.embed(toEmbed.map((i) => i.text));
29434
+ for (let i = 0; i < toEmbed.length; i++) {
29435
+ const item = toEmbed[i];
29436
+ const vec = embs[i];
29437
+ vectors.set(item.id, vec);
29438
+ await store.upsertEmbedding({ connection, source_kind: item.kind, source_id: item.id, text: item.text, vector: vec });
29439
+ }
29440
+ }
29441
+ const [qVec] = await embedder.embed([question]);
29442
+ const ranked = corpus.map((item) => ({ ...item, score: cosineSimilarity(qVec, vectors.get(item.id)) })).sort((a, b) => b.score - a.score).slice(0, topK);
29443
+ return ranked.map((r) => ({ kind: r.kind, id: r.id, text: r.text, score: r.score, payload: r.payload }));
29444
+ }
29445
+
29446
+ // ../../packages/learning/dist/answer.js
29447
+ function extractSql(text2) {
29448
+ const fence = text2.match(/```(?:sql)?\s*([\s\S]*?)```/i);
29449
+ return (fence ? fence[1] : text2).trim();
29450
+ }
29451
+ async function answerQuestion(input) {
29452
+ const { question, connection, store, embedder, provider, executeRawSql } = input;
29453
+ const reuseThreshold = input.reuseThreshold ?? 0.9;
29454
+ const top = await retrieveGrounding({ question, connection, store, embedder, topK: 5 });
29455
+ if (top.length === 0) {
29456
+ return { question, mode: "none", message: "No governed knowledge yet for this source \u2014 confirm some Hunches first." };
29457
+ }
29458
+ const groundedOn = top.map((t) => ({ kind: t.kind, title: t.payload.title, score: t.score }));
29459
+ const topHunch = top.find((t) => t.kind === "hunch");
29460
+ if (topHunch && topHunch === top[0] && topHunch.score >= reuseThreshold && topHunch.payload.sql) {
29461
+ try {
29462
+ const res = await executeRawSql(limit(topHunch.payload.sql));
29463
+ return { question, mode: "reused", sql: topHunch.payload.sql, columns: res.columns, rows: res.rows, groundedOn };
29464
+ } catch (e) {
29465
+ return { question, mode: "reused", sql: topHunch.payload.sql, groundedOn, message: `Query failed: ${e.message}` };
29466
+ }
29467
+ }
29468
+ const examples = top.filter((t) => t.kind === "hunch" && t.payload.sql).map((t) => `Q: ${t.payload.title}
29469
+ SQL: ${t.payload.sql}`).join("\n\n");
29470
+ const context = top.filter((t) => t.kind !== "hunch").map((t) => `- ${t.text}`).join("\n");
29471
+ const prompt = [
29472
+ `Write a single read-only SQL query that answers the question, using ONLY these trusted examples and business context.`,
29473
+ examples ? `Trusted examples:
29474
+ ${examples}` : "",
29475
+ context ? `Business context:
29476
+ ${context}` : "",
29477
+ `Question: ${question}`,
29478
+ `Respond with ONLY the SQL (a single SELECT).`
29479
+ ].filter(Boolean).join("\n\n");
29480
+ const resp = await provider.chat({ system: "You write governed, read-only analytics SQL grounded in trusted examples.", messages: [{ role: "user", content: prompt }], tools: [], maxTokens: 1e3 });
29481
+ const text2 = resp.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
29482
+ const sql = extractSql(text2);
29483
+ if (!isReadOnlySql(sql)) {
29484
+ return { question, mode: "generated", sql, groundedOn, message: "Generated SQL was not read-only; refusing to run it." };
29485
+ }
29486
+ try {
29487
+ const res = await executeRawSql(limit(sql));
29488
+ return { question, mode: "generated", sql, columns: res.columns, rows: res.rows, groundedOn };
29489
+ } catch (e) {
29490
+ return { question, mode: "generated", sql, groundedOn, message: `Query failed: ${e.message}` };
29491
+ }
29492
+ }
29493
+ function limit(sql) {
29494
+ return /\blimit\b/i.test(sql) ? sql : `${sql.replace(/;\s*$/, "")} LIMIT 1000`;
29495
+ }
29496
+
29497
+ // ../../packages/mcp-core/dist/tools/answer.js
29498
+ async function answerQuestionTool(deps, args) {
29499
+ const connection = args.connection ?? deps.defaultConnectionName ?? "default";
29500
+ const result = await answerQuestion({
29501
+ question: args.question,
29502
+ connection,
29503
+ store: deps.knowledgeStore,
29504
+ embedder: deps.embedder,
29505
+ provider: deps.provider,
29506
+ executeRawSql: (sql) => deps.queryService.executeRawSql(sql),
29507
+ connectionType: deps.connectionType
29508
+ });
29509
+ return JSON.stringify(result, null, 2);
29510
+ }
29511
+
28741
29512
  // ../../packages/mcp-core/dist/render/embedResource.js
28742
29513
  import { createHash } from "crypto";
28743
29514
  import { createUIResource } from "@mcp-ui/server";
@@ -28845,7 +29616,10 @@ function createMcpServer(deps) {
28845
29616
  inputSchema: { sql: z2.string(), limit: z2.number().optional() }
28846
29617
  }, async (args) => text(await runSql(deps, args)));
28847
29618
  server.registerTool("list_tables", { description: "List warehouse tables from the cached catalog (name, schema, connection, column count).", inputSchema: {} }, async () => text(await listTables(deps)));
28848
- server.registerTool("describe_table", { description: "Show columns and types for a catalog table.", inputSchema: { table: z2.string() } }, async (args) => text(await describeTable(deps, args)));
29619
+ server.registerTool("describe_table", {
29620
+ description: 'Show columns and types for a catalog table. Accepts { table } (with optional dot-notation schema: "schema.table") or { table, schema } separately.',
29621
+ inputSchema: { table: z2.string(), schema: z2.string().optional() }
29622
+ }, async (args) => text(await describeTable(deps, args)));
28849
29623
  server.registerTool("sample_table", { description: "Return a few sample rows from a table.", inputSchema: { table: z2.string(), limit: z2.number().optional() } }, async (args) => text(await sampleTable(deps, args)));
28850
29624
  server.registerTool("visualize", {
28851
29625
  description: "Render a yamchart chart spec + data rows to an interactive chart (with a static image fallback). Provide a full chart spec (name, title, source.model, chart) and the data array.",
@@ -28873,6 +29647,66 @@ function createMcpServer(deps) {
28873
29647
  description: "Save a yamchart chart spec to charts/<name>.yaml (requires editor/admin).",
28874
29648
  inputSchema: { spec: z2.record(z2.unknown()) }
28875
29649
  }, async (args) => text(await saveChart(deps, args)));
29650
+ if (deps.semanticLayer) {
29651
+ const semanticLayer = deps.semanticLayer;
29652
+ server.registerTool("list_metrics", {
29653
+ description: "List governed business metrics (the semantic glossary): name, synonyms, the entity they belong to, and which dimensions you can slice them by. Prefer querying these by name over writing raw SQL.",
29654
+ inputSchema: {}
29655
+ }, async () => text(listMetrics({ semanticLayer })));
29656
+ server.registerTool("get_metric", {
29657
+ description: "Get the full governed definition of a metric (by name or synonym): how it is computed (aggregation, field/sql, governed filter), its source table, format, and the dimensions/time dimension you can group it by.",
29658
+ inputSchema: { name: z2.string() }
29659
+ }, async (args) => text(getMetric({ semanticLayer }, args)));
29660
+ server.registerTool("query_metric", {
29661
+ description: "Query governed metrics by name (or synonym), optionally sliced by dimensions and a time grain, and filtered by a time range. Returns the data rows, the generated SQL (for transparency), and a ready-to-use suggested_chart spec. Prefer this over run_sql for business questions about governed metrics \u2014 it guarantees correct, governed definitions.",
29662
+ inputSchema: {
29663
+ metrics: z2.array(z2.string()).min(1),
29664
+ dimensions: z2.array(z2.string()).optional(),
29665
+ grain: z2.enum(["day", "week", "month", "quarter", "year"]).optional(),
29666
+ time_range: z2.union([
29667
+ z2.object({ preset: z2.string() }),
29668
+ z2.object({ start: z2.string(), end: z2.string() })
29669
+ ]).optional(),
29670
+ filters: z2.array(z2.object({ dimension: z2.string(), operator: z2.string(), value: z2.any() })).optional(),
29671
+ order_by: z2.object({ field: z2.string(), direction: z2.enum(["asc", "desc"]).optional() }).optional(),
29672
+ limit: z2.number().int().positive().max(1e4).optional()
29673
+ }
29674
+ }, async (args) => {
29675
+ try {
29676
+ return text(await queryMetric({ semanticLayer, queryService: deps.queryService, connectionType: deps.connectionType }, args));
29677
+ } catch (err) {
29678
+ return text(`Error: ${err instanceof Error ? err.message : String(err)}`);
29679
+ }
29680
+ });
29681
+ }
29682
+ if (deps.knowledgeStore && deps.embedder && deps.answerProvider) {
29683
+ const knowledgeStore = deps.knowledgeStore;
29684
+ const embedder = deps.embedder;
29685
+ const answerProvider = deps.answerProvider;
29686
+ server.registerTool("answer_question", {
29687
+ description: 'Answer a business question using governed knowledge: retrieves confirmed Hunches + business context for the source and either reuses a matching SQL or generates SQL grounded in trusted examples. Prefer this for "how many / how much / which" questions.',
29688
+ inputSchema: { question: z2.string(), connection: z2.string().optional() }
29689
+ }, async (args) => text(await answerQuestionTool({
29690
+ knowledgeStore,
29691
+ embedder,
29692
+ provider: answerProvider,
29693
+ queryService: deps.queryService,
29694
+ connectionType: deps.connectionType,
29695
+ defaultConnectionName: deps.aiConnectionName
29696
+ }, args)));
29697
+ }
29698
+ server.registerTool("get_chart_schema", {
29699
+ description: "Get the exact yamchart chart spec shape and a working example for a chart type (bar, line, area, kpi, pie, scatter, table). Call this before building a chart. Axes are objects: { field, type: temporal|quantitative|ordinal|nominal }.",
29700
+ inputSchema: { type: z2.string().optional() }
29701
+ }, async (args) => text(getChartSchema(args)));
29702
+ server.registerTool("validate_chart", {
29703
+ description: "Validate a yamchart chart spec without saving it. Returns { valid: true } or field-level errors with hints so you can self-correct before calling save_chart or visualize.",
29704
+ inputSchema: { spec: z2.record(z2.unknown()) }
29705
+ }, async (args) => text(validateChart(args)));
29706
+ server.registerTool("get_chart_spec", {
29707
+ description: "Get the full saved spec of an existing governed chart by name (so you can copy or adapt it). Call list_charts to discover names.",
29708
+ inputSchema: { name: z2.string() }
29709
+ }, async (args) => text(getChartSpec({ configLoader: deps.configLoader }, args)));
28876
29710
  return server;
28877
29711
  }
28878
29712
 
@@ -28884,13 +29718,19 @@ function getUser(request) {
28884
29718
  return { id: user.id, email: user.email, role: user.role, attributes: user.attributes };
28885
29719
  }
28886
29720
  async function mcpRoutes(fastify, options) {
28887
- const { configLoader, queryService, projectDir } = options;
29721
+ const { configLoader, queryService, projectDir, semanticLayer, connectionType, knowledgeStore, embedder, answerProvider, aiConnectionName } = options;
28888
29722
  fastify.post("/mcp", async (request, reply) => {
28889
29723
  const server = createMcpServer({
28890
29724
  configLoader,
28891
29725
  queryService,
28892
29726
  projectDir,
28893
- user: getUser(request)
29727
+ user: getUser(request),
29728
+ semanticLayer,
29729
+ connectionType,
29730
+ knowledgeStore,
29731
+ embedder,
29732
+ answerProvider,
29733
+ aiConnectionName
28894
29734
  });
28895
29735
  const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
28896
29736
  reply.raw.on("close", () => {
@@ -28914,7 +29754,7 @@ async function mcpRoutes(fastify, options) {
28914
29754
  }
28915
29755
 
28916
29756
  // ../server/dist/routes/catalog.js
28917
- import { join as join9 } from "path";
29757
+ import { join as join10 } from "path";
28918
29758
  import { readFile as readFile5 } from "fs/promises";
28919
29759
  async function catalogRoutes(fastify, options) {
28920
29760
  const { configLoader, projectDir } = options;
@@ -28927,7 +29767,7 @@ async function catalogRoutes(fastify, options) {
28927
29767
  name: m.metadata.name,
28928
29768
  description: m.metadata.description
28929
29769
  }));
28930
- const catalogPath = join9(projectDir, ".yamchart", "catalog.json");
29770
+ const catalogPath = join10(projectDir, ".yamchart", "catalog.json");
28931
29771
  let tables = [];
28932
29772
  try {
28933
29773
  const catalogData = JSON.parse(await readFile5(catalogPath, "utf-8"));
@@ -28963,8 +29803,310 @@ async function catalogRoutes(fastify, options) {
28963
29803
  });
28964
29804
  }
28965
29805
 
29806
+ // ../server/dist/routes/learning.js
29807
+ var ReadOnlySqlError = class extends Error {
29808
+ constructor() {
29809
+ super("Only read-only SELECT queries can be confirmed.");
29810
+ this.name = "ReadOnlySqlError";
29811
+ }
29812
+ };
29813
+ async function confirmHunch(deps, id, body) {
29814
+ if (body.sql !== void 0 && !isReadOnlySql(body.sql)) {
29815
+ throw new ReadOnlySqlError();
29816
+ }
29817
+ return deps.store.updateHunch(id, { status: "confirmed", ...body.sql ? { sql: body.sql } : {} });
29818
+ }
29819
+ async function rejectHunch(deps, id) {
29820
+ return deps.store.updateHunch(id, { status: "rejected" });
29821
+ }
29822
+ async function runHunch(deps, id) {
29823
+ const hunch = await deps.store.getHunch(id);
29824
+ if (!hunch)
29825
+ return { error: "Hunch not found." };
29826
+ if (!isReadOnlySql(hunch.sql))
29827
+ return { error: "Only read-only SELECT queries can be run." };
29828
+ const limited = /\blimit\b/i.test(hunch.sql) ? hunch.sql : `${hunch.sql.replace(/;\s*$/, "")} LIMIT 50`;
29829
+ const res = await deps.queryService.executeRawSql(limited);
29830
+ const sample = { columns: res.columns, rows: res.rows, ranAt: (/* @__PURE__ */ new Date()).toISOString() };
29831
+ await deps.store.updateHunch(id, { lastRunSample: sample });
29832
+ return { sample };
29833
+ }
29834
+ async function answerBlindspot(deps, id, body) {
29835
+ return deps.store.updateBlindspot(id, { status: "answered", answer: body.answer });
29836
+ }
29837
+ async function dismissBlindspot(deps, id) {
29838
+ return deps.store.updateBlindspot(id, { status: "dismissed" });
29839
+ }
29840
+ async function loadContextBlock(opts, store, connection) {
29841
+ const fileDocs = loadKnowledgeDocs(opts.projectDir);
29842
+ const dbDocs = (await store.listKnowledgeDocs({ connection })).map((d) => `${d.title}
29843
+ ${d.content}`);
29844
+ const answered = (await store.listBlindspots({ connection, status: "answered" })).filter((b) => b.answer).map((b) => ({ title: b.title, answer: b.answer }));
29845
+ return buildContextBlock({ docs: [...fileDocs, ...dbDocs], answeredBlindspots: answered });
29846
+ }
29847
+ async function learningRoutes(fastify, opts) {
29848
+ const { store, queryService } = opts;
29849
+ const requireStore = (reply) => {
29850
+ if (!store) {
29851
+ reply.code(503).send({ error: "Knowledge features require a configured database (set KNOWLEDGE_DATABASE_URL)." });
29852
+ return false;
29853
+ }
29854
+ return true;
29855
+ };
29856
+ fastify.post("/api/learn", async (request, reply) => {
29857
+ if (!requireStore(reply))
29858
+ return;
29859
+ if (!opts.canWrite(request)) {
29860
+ reply.code(403).send({ error: "Requires editor or admin role." });
29861
+ return;
29862
+ }
29863
+ const connection = request.body?.connection ?? opts.defaultConnectionName;
29864
+ reply.raw.writeHead(200, {
29865
+ "Content-Type": "text/event-stream",
29866
+ "Cache-Control": "no-cache",
29867
+ Connection: "keep-alive"
29868
+ });
29869
+ const send = (type, data) => reply.raw.write(formatSSEEvent({ type, data }));
29870
+ try {
29871
+ send("status", { phase: "profiling" });
29872
+ const tables = await opts.getConnectionTables(connection);
29873
+ const profile = await buildSourceProfile({
29874
+ connection,
29875
+ dialect: opts.dialect,
29876
+ tables,
29877
+ execute: (sql) => queryService.executeRawSql(sql)
29878
+ });
29879
+ await store.saveProfile(connection, profile);
29880
+ send("status", { phase: "generating" });
29881
+ let provider;
29882
+ try {
29883
+ provider = createProviderForTask(opts.aiConfig, "hunch_generation", process.env);
29884
+ } catch (e) {
29885
+ send("error", { message: e.message });
29886
+ reply.raw.end();
29887
+ return;
29888
+ }
29889
+ const contextBlock = await loadContextBlock(opts, store, connection);
29890
+ const candidates = await generateHunches({
29891
+ provider,
29892
+ profile,
29893
+ metricNames: opts.metricNames,
29894
+ connection,
29895
+ contextBlock
29896
+ });
29897
+ const created = await store.createHunches(candidates);
29898
+ send("done", { hunches: created });
29899
+ } catch (e) {
29900
+ send("error", { message: e.message });
29901
+ }
29902
+ reply.raw.end();
29903
+ });
29904
+ fastify.get("/api/hunches", async (request, reply) => {
29905
+ if (!requireStore(reply))
29906
+ return;
29907
+ const q = request.query;
29908
+ return store.listHunches({ connection: q.connection, status: q.status });
29909
+ });
29910
+ fastify.post("/api/hunches/:id/confirm", async (request, reply) => {
29911
+ if (!requireStore(reply))
29912
+ return;
29913
+ if (!opts.canWrite(request)) {
29914
+ reply.code(403).send({ error: "Requires editor or admin role." });
29915
+ return;
29916
+ }
29917
+ try {
29918
+ return await confirmHunch({ store, queryService }, request.params.id, request.body ?? {});
29919
+ } catch (e) {
29920
+ if (e instanceof ReadOnlySqlError) {
29921
+ reply.code(400).send({ error: e.message });
29922
+ return;
29923
+ }
29924
+ throw e;
29925
+ }
29926
+ });
29927
+ fastify.post("/api/hunches/:id/reject", async (request, reply) => {
29928
+ if (!requireStore(reply))
29929
+ return;
29930
+ if (!opts.canWrite(request)) {
29931
+ reply.code(403).send({ error: "Requires editor or admin role." });
29932
+ return;
29933
+ }
29934
+ return rejectHunch({ store, queryService }, request.params.id);
29935
+ });
29936
+ fastify.post("/api/hunches/:id/run", async (request, reply) => {
29937
+ if (!requireStore(reply))
29938
+ return;
29939
+ if (!opts.canWrite(request)) {
29940
+ reply.code(403).send({ error: "Requires editor or admin role." });
29941
+ return;
29942
+ }
29943
+ return runHunch({ store, queryService }, request.params.id);
29944
+ });
29945
+ fastify.post("/api/blindspots/scan", async (request, reply) => {
29946
+ if (!requireStore(reply))
29947
+ return;
29948
+ if (!opts.canWrite(request)) {
29949
+ reply.code(403).send({ error: "Requires editor or admin role." });
29950
+ return;
29951
+ }
29952
+ const connection = request.body?.connection ?? opts.defaultConnectionName;
29953
+ reply.raw.writeHead(200, {
29954
+ "Content-Type": "text/event-stream",
29955
+ "Cache-Control": "no-cache",
29956
+ Connection: "keep-alive"
29957
+ });
29958
+ const send = (type, data) => reply.raw.write(formatSSEEvent({ type, data }));
29959
+ try {
29960
+ send("status", { phase: "scanning" });
29961
+ let profile = await store.getProfile(connection);
29962
+ if (!profile) {
29963
+ const tables = await opts.getConnectionTables(connection);
29964
+ profile = await buildSourceProfile({
29965
+ connection,
29966
+ dialect: opts.dialect,
29967
+ tables,
29968
+ execute: (sql) => queryService.executeRawSql(sql)
29969
+ });
29970
+ await store.saveProfile(connection, profile);
29971
+ }
29972
+ const contextBlock = await loadContextBlock(opts, store, connection);
29973
+ let provider;
29974
+ try {
29975
+ provider = createProviderForTask(opts.aiConfig, "blindspot_scan", process.env);
29976
+ } catch (e) {
29977
+ send("error", { message: e.message });
29978
+ reply.raw.end();
29979
+ return;
29980
+ }
29981
+ const items = await scanBlindspots({
29982
+ provider,
29983
+ profile,
29984
+ metricNames: opts.metricNames,
29985
+ contextBlock,
29986
+ connection
29987
+ });
29988
+ await store.deleteOpenBlindspots(connection);
29989
+ const created = await store.createBlindspots(items);
29990
+ send("done", { blindspots: created });
29991
+ } catch (e) {
29992
+ send("error", { message: e.message });
29993
+ }
29994
+ reply.raw.end();
29995
+ });
29996
+ fastify.get("/api/blindspots", async (request, reply) => {
29997
+ if (!requireStore(reply))
29998
+ return;
29999
+ const q = request.query;
30000
+ return store.listBlindspots({ connection: q.connection, status: q.status, kind: q.kind });
30001
+ });
30002
+ fastify.post("/api/blindspots/:id/answer", async (request, reply) => {
30003
+ if (!requireStore(reply))
30004
+ return;
30005
+ if (!opts.canWrite(request)) {
30006
+ reply.code(403).send({ error: "Requires editor or admin role." });
30007
+ return;
30008
+ }
30009
+ return answerBlindspot({ store }, request.params.id, request.body ?? { answer: "" });
30010
+ });
30011
+ fastify.post("/api/blindspots/:id/dismiss", async (request, reply) => {
30012
+ if (!requireStore(reply))
30013
+ return;
30014
+ if (!opts.canWrite(request)) {
30015
+ reply.code(403).send({ error: "Requires editor or admin role." });
30016
+ return;
30017
+ }
30018
+ return dismissBlindspot({ store }, request.params.id);
30019
+ });
30020
+ }
30021
+
30022
+ // ../server/dist/routes/data.js
30023
+ async function createKnowledgeDocHandler(deps, body) {
30024
+ const [doc] = await deps.store.createKnowledgeDoc({ ...body, source: "ui" });
30025
+ return doc;
30026
+ }
30027
+ async function dataRoutes(fastify, opts) {
30028
+ const { store } = opts;
30029
+ fastify.get("/api/sources", async () => {
30030
+ const conns = opts.getConnections();
30031
+ return Promise.all(conns.map(async (c) => {
30032
+ let tables = 0;
30033
+ let pendingHunches = 0;
30034
+ let openBlindspots = 0;
30035
+ try {
30036
+ tables = (await opts.getConnectionTables(c.name)).length;
30037
+ } catch {
30038
+ }
30039
+ if (store) {
30040
+ pendingHunches = (await store.listHunches({ connection: c.name, status: "pending" })).length;
30041
+ openBlindspots = (await store.listBlindspots({ connection: c.name, status: "open" })).length;
30042
+ }
30043
+ return { name: c.name, type: c.type, counts: { tables, pendingHunches, openBlindspots } };
30044
+ }));
30045
+ });
30046
+ fastify.get("/api/sources/:connection/tables", async (request) => {
30047
+ const connection = request.params.connection;
30048
+ const tables = await opts.getConnectionTables(connection);
30049
+ const profile = store ? await store.getProfile(connection) : void 0;
30050
+ return tables.map((t) => {
30051
+ const pt = profile?.tables.find((p) => p.table === t.table);
30052
+ const columns = t.columns.map((c) => {
30053
+ const pc = pt?.columns.find((x) => x.name === c.name);
30054
+ return {
30055
+ name: c.name,
30056
+ type: c.type,
30057
+ nullRate: pc?.nullRate,
30058
+ distinctCount: pc?.distinctCount,
30059
+ topValues: pc?.topValues
30060
+ };
30061
+ });
30062
+ return { table: t.table, columns };
30063
+ });
30064
+ });
30065
+ const requireStore = (reply) => {
30066
+ if (!store) {
30067
+ reply.code(503).send({ error: "Knowledge features require a configured database." });
30068
+ return false;
30069
+ }
30070
+ return true;
30071
+ };
30072
+ fastify.get("/api/knowledge", async (request, reply) => {
30073
+ if (!requireStore(reply))
30074
+ return;
30075
+ return store.listKnowledgeDocs({ connection: request.query.connection });
30076
+ });
30077
+ fastify.post("/api/knowledge", async (request, reply) => {
30078
+ if (!requireStore(reply))
30079
+ return;
30080
+ if (!opts.canWrite(request)) {
30081
+ reply.code(403).send({ error: "Requires editor or admin role." });
30082
+ return;
30083
+ }
30084
+ return createKnowledgeDocHandler({ store }, request.body);
30085
+ });
30086
+ fastify.put("/api/knowledge/:id", async (request, reply) => {
30087
+ if (!requireStore(reply))
30088
+ return;
30089
+ if (!opts.canWrite(request)) {
30090
+ reply.code(403).send({ error: "Requires editor or admin role." });
30091
+ return;
30092
+ }
30093
+ const { id } = request.params;
30094
+ return store.updateKnowledgeDoc(id, request.body);
30095
+ });
30096
+ fastify.delete("/api/knowledge/:id", async (request, reply) => {
30097
+ if (!requireStore(reply))
30098
+ return;
30099
+ if (!opts.canWrite(request)) {
30100
+ reply.code(403).send({ error: "Requires editor or admin role." });
30101
+ return;
30102
+ }
30103
+ await store.deleteKnowledgeDoc(request.params.id);
30104
+ return { ok: true };
30105
+ });
30106
+ }
30107
+
28966
30108
  // ../server/dist/oauth/service.js
28967
- import { randomUUID, timingSafeEqual as timingSafeEqual2 } from "crypto";
30109
+ import { randomUUID as randomUUID3, timingSafeEqual as timingSafeEqual2 } from "crypto";
28968
30110
 
28969
30111
  // ../server/dist/oauth/errors.js
28970
30112
  var OAuthError = class extends Error {
@@ -29032,7 +30174,7 @@ var OAuthService = class {
29032
30174
  }
29033
30175
  const authMethod = input.token_endpoint_auth_method === "client_secret_post" ? "client_secret_post" : "none";
29034
30176
  const grantTypes = input.grant_types?.length ? input.grant_types : ["authorization_code", "refresh_token"];
29035
- const clientId = randomUUID();
30177
+ const clientId = randomUUID3();
29036
30178
  let clientSecret;
29037
30179
  let clientSecretHash = null;
29038
30180
  if (authMethod === "client_secret_post") {
@@ -29489,17 +30631,17 @@ data: ${data}
29489
30631
  }
29490
30632
 
29491
30633
  // ../server/dist/server.js
29492
- import { join as join10, dirname as dirname2 } from "path";
30634
+ import { join as join11, dirname as dirname2 } from "path";
29493
30635
  import { fileURLToPath } from "url";
29494
30636
  import { access as access2, readFile as readFile6 } from "fs/promises";
29495
30637
  import { watch as fsWatch } from "fs";
29496
30638
  var __dirname = dirname2(fileURLToPath(import.meta.url));
29497
- var packageJsonPath = join10(__dirname, "..", "package.json");
30639
+ var packageJsonPath = join11(__dirname, "..", "package.json");
29498
30640
  var packageJson = JSON.parse(await readFile6(packageJsonPath, "utf-8"));
29499
30641
  var VERSION = packageJson.version;
29500
30642
  async function enrichRefsFromCatalog(projectDir, refs) {
29501
30643
  try {
29502
- const catalogPath = join10(projectDir, ".yamchart", "catalog.json");
30644
+ const catalogPath = join11(projectDir, ".yamchart", "catalog.json");
29503
30645
  const catalogData = JSON.parse(await readFile6(catalogPath, "utf-8"));
29504
30646
  for (const model of catalogData.models ?? []) {
29505
30647
  if (model.name && model.table) {
@@ -29510,7 +30652,7 @@ async function enrichRefsFromCatalog(projectDir, refs) {
29510
30652
  }
29511
30653
  }
29512
30654
  async function createServer(options) {
29513
- const { projectDir, port = 3001, host = "0.0.0.0", watch: watch2 = false, serveStatic = process.env.NODE_ENV === "production", staticDir = join10(__dirname, "public"), auth } = options;
30655
+ const { projectDir, port = 3001, host = "0.0.0.0", watch: watch2 = false, serveStatic = process.env.NODE_ENV === "production", staticDir = join11(__dirname, "public"), auth } = options;
29514
30656
  if (auth) {
29515
30657
  initAuthServer(auth.supabaseUrl, auth.supabaseServiceKey);
29516
30658
  }
@@ -29554,6 +30696,19 @@ async function createServer(options) {
29554
30696
  maxSize: 1e3,
29555
30697
  defaultTtlMs: cacheTtl
29556
30698
  });
30699
+ const knowledgeUrlVar = project.knowledge?.database_url_var ?? "KNOWLEDGE_DATABASE_URL";
30700
+ const knowledgeDbUrl = process.env[knowledgeUrlVar];
30701
+ let knowledgeStore;
30702
+ if (knowledgeDbUrl) {
30703
+ const store = new PostgresKnowledgeStore(knowledgeDbUrl);
30704
+ try {
30705
+ await store.init();
30706
+ knowledgeStore = store;
30707
+ } catch (err) {
30708
+ fastify.log.warn(`Knowledge store init failed: ${err.message}`);
30709
+ knowledgeStore = void 0;
30710
+ }
30711
+ }
29557
30712
  const models = {};
29558
30713
  const refs = {};
29559
30714
  for (const model of configLoader.getModels()) {
@@ -29700,16 +30855,103 @@ async function createServer(options) {
29700
30855
  await fastify.register(catalogRoutes, { configLoader, projectDir });
29701
30856
  }
29702
30857
  const mcpBaseUrl = process.env.BASE_URL || project.defaults?.base_url || `http://localhost:${port}`;
30858
+ let semanticLayer;
30859
+ try {
30860
+ const semanticDefs = loadSemanticDefinitions(projectDir);
30861
+ if (semanticDefs.length > 0) {
30862
+ semanticLayer = buildSemanticLayer(semanticDefs, (model) => model);
30863
+ }
30864
+ } catch (err) {
30865
+ fastify.log.warn({ err }, "Failed to load curated semantic definitions; semantic MCP tools disabled");
30866
+ }
30867
+ const connectionType = configLoader.getDefaultConnection()?.type;
30868
+ let embedder;
30869
+ let answerProvider;
30870
+ try {
30871
+ embedder = createEmbedder(project.ai?.embedding, process.env);
30872
+ } catch (err) {
30873
+ fastify.log.warn({ err }, "Failed to build embedder; answer_question tool disabled");
30874
+ }
30875
+ try {
30876
+ answerProvider = createProviderForTask(project.ai, "answer", process.env);
30877
+ } catch (err) {
30878
+ fastify.log.warn({ err }, "Failed to build answer provider; answer_question tool disabled");
30879
+ }
30880
+ const aiConnectionName = defaultConnection?.name ?? "default";
30881
+ const mcpOptions = {
30882
+ configLoader,
30883
+ queryService,
30884
+ projectDir,
30885
+ semanticLayer,
30886
+ connectionType,
30887
+ knowledgeStore,
30888
+ embedder,
30889
+ answerProvider,
30890
+ aiConnectionName
30891
+ };
29703
30892
  if (authDb && oauthService) {
29704
30893
  const mcpAuth = createMcpAuthPreHandler({ authDb, oauthService, baseUrl: mcpBaseUrl });
29705
30894
  await fastify.register(async (mcpScope) => {
29706
30895
  mcpScope.addHook("preHandler", mcpAuth);
29707
- await mcpScope.register(mcpRoutes, { configLoader, queryService, projectDir });
30896
+ await mcpScope.register(mcpRoutes, mcpOptions);
29708
30897
  });
29709
30898
  } else {
29710
- await fastify.register(mcpRoutes, { configLoader, queryService, projectDir });
30899
+ await fastify.register(mcpRoutes, mcpOptions);
29711
30900
  }
29712
- const assetsDir = join10(projectDir, "assets");
30901
+ const learningOptions = {
30902
+ store: knowledgeStore,
30903
+ queryService,
30904
+ projectDir,
30905
+ defaultConnectionName: defaultConnection?.name ?? "default",
30906
+ dialect: defaultConnection?.type ?? "duckdb",
30907
+ metricNames: semanticLayer?.listMetrics().map((m) => m.name) ?? [],
30908
+ getConnectionTables: async (connection) => {
30909
+ const catalogPath = join11(projectDir, ".yamchart", "catalog.json");
30910
+ try {
30911
+ const catalogData = JSON.parse(await readFile6(catalogPath, "utf-8"));
30912
+ return (catalogData.models ?? []).filter((m) => !m.connection || m.connection === connection).map((m) => ({
30913
+ table: m.table ?? m.name ?? "",
30914
+ columns: (m.columns ?? []).map((c) => ({ name: c.name, type: c.data_type ?? "string" }))
30915
+ })).filter((t) => t.table && t.columns.length > 0);
30916
+ } catch {
30917
+ return [];
30918
+ }
30919
+ },
30920
+ // Writes (learn/confirm/reject) require editor/admin. When auth is disabled
30921
+ // there is no request.user, so dev/local mode permits writes.
30922
+ canWrite: (req) => {
30923
+ const user = req.user;
30924
+ if (!user)
30925
+ return true;
30926
+ return user.role !== "viewer";
30927
+ },
30928
+ aiConfig: project.ai
30929
+ };
30930
+ const dataOptions = {
30931
+ store: knowledgeStore,
30932
+ getConnections: () => configLoader.getConnections().map((c) => ({ name: c.name, type: c.type })),
30933
+ getConnectionTables: learningOptions.getConnectionTables,
30934
+ canWrite: learningOptions.canWrite
30935
+ };
30936
+ if (auth) {
30937
+ await fastify.register(async (protectedRoutes) => {
30938
+ protectedRoutes.addHook("preHandler", authMiddleware);
30939
+ protectedRoutes.addHook("preHandler", orgMiddleware);
30940
+ await protectedRoutes.register(learningRoutes, learningOptions);
30941
+ await protectedRoutes.register(dataRoutes, dataOptions);
30942
+ });
30943
+ } else if (authDb) {
30944
+ const localMiddleware = createLocalAuthMiddleware(authDb);
30945
+ await fastify.register(async (protectedRoutes) => {
30946
+ protectedRoutes.addHook("preHandler", localMiddleware);
30947
+ await protectedRoutes.register(learningRoutes, learningOptions);
30948
+ await protectedRoutes.register(dataRoutes, dataOptions);
30949
+ });
30950
+ } else {
30951
+ await fastify.register(learningRoutes, learningOptions);
30952
+ await fastify.register(dataRoutes, dataOptions);
30953
+ }
30954
+ const assetsDir = join11(projectDir, "assets");
29713
30955
  try {
29714
30956
  await access2(assetsDir);
29715
30957
  await fastify.register(fastifyStatic, {
@@ -29720,7 +30962,7 @@ async function createServer(options) {
29720
30962
  } catch {
29721
30963
  }
29722
30964
  if (project.theme?.customCss) {
29723
- const cssPath = join10(projectDir, project.theme.customCss);
30965
+ const cssPath = join11(projectDir, project.theme.customCss);
29724
30966
  fastify.get("/api/theme/custom.css", async (_request2, reply) => {
29725
30967
  try {
29726
30968
  const content = await readFile6(cssPath, "utf-8");
@@ -29779,7 +31021,7 @@ async function createServer(options) {
29779
31021
  fastify.log.info(`Semantic model rebuilt: ${semanticService.getModel()?.entities.length ?? 0} entities`);
29780
31022
  broadcastConfigChange();
29781
31023
  });
29782
- const catalogPath = join10(projectDir, ".yamchart", "catalog.json");
31024
+ const catalogPath = join11(projectDir, ".yamchart", "catalog.json");
29783
31025
  try {
29784
31026
  catalogWatcher = fsWatch(catalogPath, () => {
29785
31027
  semanticService.buildFromDisk();
@@ -29843,15 +31085,15 @@ async function runDevServer(projectDir, options) {
29843
31085
  let usesBrowserAuth = false;
29844
31086
  let localAuth;
29845
31087
  try {
29846
- const { readFileSync: readFileSync2 } = await import("fs");
31088
+ const { readFileSync: readFileSync3 } = await import("fs");
29847
31089
  const { parse: parse2 } = await import("yaml");
29848
- const { resolveProjectConfig: resolveProjectConfig2, deepMerge: deepMerge3 } = await import("./dist-MX5K2ABB.js");
29849
- const raw = readFileSync2(resolve2(projectDir, "yamchart.yaml"), "utf-8");
31090
+ const { resolveProjectConfig: resolveProjectConfig2, deepMerge: deepMerge3 } = await import("./dist-PPAD6KOM.js");
31091
+ const raw = readFileSync3(resolve2(projectDir, "yamchart.yaml"), "utf-8");
29850
31092
  const rawConfig = parse2(raw);
29851
31093
  const resolvedEnv = options.env || process.env.YAMCHART_ENV || void 0;
29852
31094
  let projectConfig = resolveProjectConfig2(rawConfig, resolvedEnv);
29853
31095
  try {
29854
- const localRaw = readFileSync2(resolve2(projectDir, "yamchart.local.yaml"), "utf-8");
31096
+ const localRaw = readFileSync3(resolve2(projectDir, "yamchart.local.yaml"), "utf-8");
29855
31097
  const localOverrides = parse2(localRaw);
29856
31098
  if (localOverrides && typeof localOverrides === "object") {
29857
31099
  projectConfig = deepMerge3(projectConfig, localOverrides);
@@ -29869,7 +31111,7 @@ async function runDevServer(projectDir, options) {
29869
31111
  const targetConn = process.env.YAMCHART_CONNECTION || projectConfig?.defaults?.connection;
29870
31112
  if (targetConn) {
29871
31113
  try {
29872
- const connRaw = readFileSync2(resolve2(connectionDir, `${targetConn}.yaml`), "utf-8");
31114
+ const connRaw = readFileSync3(resolve2(connectionDir, `${targetConn}.yaml`), "utf-8");
29873
31115
  const connConfig = parse2(connRaw);
29874
31116
  if (connConfig?.auth?.type === "externalbrowser") {
29875
31117
  usesBrowserAuth = true;
@@ -29949,4 +31191,4 @@ async function runDevServer(projectDir, options) {
29949
31191
  export {
29950
31192
  runDevServer
29951
31193
  };
29952
- //# sourceMappingURL=dev-GWXHBBWB.js.map
31194
+ //# sourceMappingURL=dev-H6SJZ5UA.js.map