yamchart 0.9.4 → 0.10.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 (81) hide show
  1. package/dist/{advisor-SC64RTZO.js → advisor-O2BRAI4T.js} +24 -14
  2. package/dist/advisor-O2BRAI4T.js.map +1 -0
  3. package/dist/{chunk-NXQ6ZO3V.js → chunk-5FHV22X2.js} +6 -6
  4. package/dist/chunk-5FHV22X2.js.map +1 -0
  5. package/dist/{chunk-7CD6UQAV.js → chunk-DZVT6PHW.js} +9 -1
  6. package/dist/chunk-DZVT6PHW.js.map +1 -0
  7. package/dist/chunk-FN6R2LAC.js +15442 -0
  8. package/dist/chunk-FN6R2LAC.js.map +1 -0
  9. package/dist/chunk-QJ5CPQJK.js +2053 -0
  10. package/dist/chunk-QJ5CPQJK.js.map +1 -0
  11. package/dist/{chunk-S7YQXEKM.js → chunk-QUIDZO5G.js} +103 -275
  12. package/dist/chunk-QUIDZO5G.js.map +1 -0
  13. package/dist/{chunk-RMIDEBHD.js → chunk-S2CH4HUZ.js} +6 -6
  14. package/dist/{chunk-UND73EOB.js → chunk-UFDQ3C7Q.js} +473 -4
  15. package/dist/chunk-UFDQ3C7Q.js.map +1 -0
  16. package/dist/{chunk-H4L3FNLS.js → chunk-ZBCQNWVN.js} +2 -2
  17. package/dist/{chunk-RM6MNDVF.js → chunk-ZIY22VO7.js} +192 -11
  18. package/dist/chunk-ZIY22VO7.js.map +1 -0
  19. package/dist/{connection-utils-C4FQGBW6.js → connection-utils-FEUWER5I.js} +5 -5
  20. package/dist/{describe-X75C2VDU.js → describe-MEP72B56.js} +6 -6
  21. package/dist/{dev-7YLRQ6SA.js → dev-LZ4YHNDJ.js} +1334 -77
  22. package/dist/dev-LZ4YHNDJ.js.map +1 -0
  23. package/dist/dist-5IFWLWND.js +48 -0
  24. package/dist/{dist-NGQG7Z4G.js → dist-MIKFZKSD.js} +2 -2
  25. package/dist/{dist-MX5K2ABB.js → dist-PPAD6KOM.js} +44 -2
  26. package/dist/{dist-JMLAZUL7.js → dist-XNCED7JW.js} +29 -12
  27. package/dist/fileFromPath-7TNUU6RI.js +130 -0
  28. package/dist/fileFromPath-7TNUU6RI.js.map +1 -0
  29. package/dist/index.js +25 -25
  30. package/dist/public/assets/DataView-DUCz_96y.js +9 -0
  31. package/dist/public/assets/{EventManagement-B7leMxfo.js → EventManagement-BnmeJDQl.js} +1 -1
  32. package/dist/public/assets/{ExplorePage-zI1OiYlH.js → ExplorePage-kk4z9ldZ.js} +1 -1
  33. package/dist/public/assets/{LoginPage-Dyk2ILmo.js → LoginPage-CzaFkkjg.js} +1 -1
  34. package/dist/public/assets/{PublicViewer-C2tNYBR2.js → PublicViewer-irjxqH6a.js} +1 -1
  35. package/dist/public/assets/{SetupWizard-BzZSJlbt.js → SetupWizard-ConWIcmy.js} +1 -1
  36. package/dist/public/assets/{ShareManagement-C7RIZRWe.js → ShareManagement-CP4wdwLR.js} +1 -1
  37. package/dist/public/assets/SourceDetailView-DZS5518E.js +1 -0
  38. package/dist/public/assets/{UserManagement-BD-lLbVH.js → UserManagement-AubGd9hl.js} +1 -1
  39. package/dist/public/assets/data-3vtzSuAZ.js +1 -0
  40. package/dist/public/assets/{index-CfyF2Wf-.css → index-C1X8RW4Z.css} +1 -1
  41. package/dist/public/assets/{index-lklRbe2I.js → index-jlfTO7f5.js} +44 -44
  42. package/dist/public/assets/{index.es-CLyC5-GY.js → index.es-CgnvEWi5.js} +1 -1
  43. package/dist/public/assets/{jspdf.es.min-CTZVk96E.js → jspdf.es.min-Cw5WefMt.js} +3 -3
  44. package/dist/public/index.html +2 -2
  45. package/dist/{query-QNRDS74I.js → query-2H3YOPI2.js} +5 -5
  46. package/dist/{reset-password-HDCLH7PZ.js → reset-password-YVCZKZPC.js} +2 -2
  47. package/dist/{sample-SKLHBZBU.js → sample-ODUGGSFA.js} +5 -5
  48. package/dist/{search-4KMETZVX.js → search-IPE4ISFB.js} +6 -6
  49. package/dist/{semantic-6WKELH5V.js → semantic-K3MYXXJI.js} +3 -2
  50. package/dist/{semantic-6WKELH5V.js.map → semantic-K3MYXXJI.js.map} +1 -1
  51. package/dist/{source-resolver-R7WBIL7M.js → source-resolver-HZQLOODU.js} +6 -6
  52. package/dist/source-resolver-HZQLOODU.js.map +1 -0
  53. package/dist/{sync-warehouse-UWRNUXE7.js → sync-warehouse-26L6JDSV.js} +10 -10
  54. package/dist/{tables-V65QUGHK.js → tables-K5NAN2WK.js} +6 -6
  55. package/dist/templates/default/docs/yamchart-reference.md +46 -0
  56. package/dist/{test-UE5OWG3E.js → test-SRHVOXZB.js} +8 -7
  57. package/dist/{test-UE5OWG3E.js.map → test-SRHVOXZB.js.map} +1 -1
  58. package/package.json +2 -2
  59. package/dist/advisor-SC64RTZO.js.map +0 -1
  60. package/dist/chunk-7CD6UQAV.js.map +0 -1
  61. package/dist/chunk-NXQ6ZO3V.js.map +0 -1
  62. package/dist/chunk-RM6MNDVF.js.map +0 -1
  63. package/dist/chunk-S7YQXEKM.js.map +0 -1
  64. package/dist/chunk-UND73EOB.js.map +0 -1
  65. package/dist/dev-7YLRQ6SA.js.map +0 -1
  66. package/dist/dist-MNXSMGV6.js +0 -790
  67. package/dist/dist-MNXSMGV6.js.map +0 -1
  68. /package/dist/{chunk-RMIDEBHD.js.map → chunk-S2CH4HUZ.js.map} +0 -0
  69. /package/dist/{chunk-H4L3FNLS.js.map → chunk-ZBCQNWVN.js.map} +0 -0
  70. /package/dist/{connection-utils-C4FQGBW6.js.map → connection-utils-FEUWER5I.js.map} +0 -0
  71. /package/dist/{describe-X75C2VDU.js.map → describe-MEP72B56.js.map} +0 -0
  72. /package/dist/{dist-JMLAZUL7.js.map → dist-5IFWLWND.js.map} +0 -0
  73. /package/dist/{dist-MX5K2ABB.js.map → dist-MIKFZKSD.js.map} +0 -0
  74. /package/dist/{dist-NGQG7Z4G.js.map → dist-PPAD6KOM.js.map} +0 -0
  75. /package/dist/{source-resolver-R7WBIL7M.js.map → dist-XNCED7JW.js.map} +0 -0
  76. /package/dist/{query-QNRDS74I.js.map → query-2H3YOPI2.js.map} +0 -0
  77. /package/dist/{reset-password-HDCLH7PZ.js.map → reset-password-YVCZKZPC.js.map} +0 -0
  78. /package/dist/{sample-SKLHBZBU.js.map → sample-ODUGGSFA.js.map} +0 -0
  79. /package/dist/{search-4KMETZVX.js.map → search-IPE4ISFB.js.map} +0 -0
  80. /package/dist/{sync-warehouse-UWRNUXE7.js.map → sync-warehouse-26L6JDSV.js.map} +0 -0
  81. /package/dist/{tables-V65QUGHK.js.map → tables-K5NAN2WK.js.map} +0 -0
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  loadEnvFile,
3
3
  validateProject
4
- } from "./chunk-RMIDEBHD.js";
4
+ } from "./chunk-S2CH4HUZ.js";
5
5
  import {
6
6
  box,
7
7
  detail,
@@ -12,31 +12,14 @@ import {
12
12
  spinner,
13
13
  success
14
14
  } 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
15
  import {
28
16
  StreamingChatAgent,
29
17
  filterCatalogEntries
30
18
  } from "./chunk-ZMJPRNOA.js";
31
19
  import {
32
- AuthDatabase,
33
- generateOpaqueToken,
34
- generateSessionToken,
35
- hashPassword,
36
- hashToken,
37
- parseTtl,
38
- verifyPassword
39
- } from "./chunk-7CD6UQAV.js";
20
+ createEmbedder,
21
+ createProviderForTask
22
+ } from "./chunk-FN6R2LAC.js";
40
23
  import {
41
24
  BigQueryConnector,
42
25
  DuckDBConnector,
@@ -46,28 +29,56 @@ import {
46
29
  ReconnectingConnector,
47
30
  SQLiteConnector,
48
31
  SnowflakeConnector,
49
- computePreviousPeriod,
32
+ buildSourceProfile,
50
33
  createTemplateContext,
51
- expandCustomDateRange,
52
- expandDatePreset,
53
- expandRelativeDateRange,
54
- formatPeriodLabel,
55
- isCustomDateRange,
56
- isDatePreset,
57
- isRelativeDateRange,
34
+ isReadOnlySql,
58
35
  parseModelMetadata,
59
36
  renderTemplate,
60
37
  resolveBigQueryAuth,
61
38
  resolveMySQLAuth,
62
39
  resolvePostgresAuth,
63
40
  resolveSnowflakeAuth
64
- } from "./chunk-S7YQXEKM.js";
41
+ } from "./chunk-QUIDZO5G.js";
65
42
  import {
66
43
  SemanticModelBuilder,
67
44
  SemanticQueryCompiler,
68
45
  SemanticValidationError,
46
+ buildSemanticLayer,
47
+ compileMetricQuery,
48
+ computePreviousPeriod,
49
+ expandCustomDateRange,
50
+ expandDatePreset,
51
+ expandRelativeDateRange,
52
+ formatPeriodLabel,
53
+ isCustomDateRange,
54
+ isDatePreset,
55
+ isRelativeDateRange,
56
+ loadSemanticDefinitions,
69
57
  validateSemanticQuery
70
- } from "./chunk-UND73EOB.js";
58
+ } from "./chunk-UFDQ3C7Q.js";
59
+ import {
60
+ ChartSchema,
61
+ ConnectionSchema,
62
+ DashboardSchema,
63
+ EventSchema,
64
+ EventsFileSchema,
65
+ NewBlindspotSchema,
66
+ ProjectSchema,
67
+ ScheduleSchema,
68
+ SemanticQuerySchema,
69
+ deepMerge,
70
+ resolveProjectConfig
71
+ } from "./chunk-ZIY22VO7.js";
72
+ import "./chunk-QJ5CPQJK.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
  }
@@ -20842,20 +21139,20 @@ async function authAdminRoutes(fastify, options) {
20842
21139
  });
20843
21140
  fastify.post("/api/auth/users", async (request, reply) => {
20844
21141
  const { email, name, password, role } = request.body;
20845
- if (!email || !name || !password || !role) {
20846
- return reply.status(400).send({ error: "Email, name, password, and role are required" });
21142
+ if (!email || !name || !role) {
21143
+ return reply.status(400).send({ error: "Email, name, and role are required" });
20847
21144
  }
20848
21145
  if (!["admin", "editor", "viewer"].includes(role)) {
20849
21146
  return reply.status(400).send({ error: "Role must be admin, editor, or viewer" });
20850
21147
  }
20851
- if (password.length < 8) {
21148
+ if (password && password.length < 8) {
20852
21149
  return reply.status(400).send({ error: "Password must be at least 8 characters" });
20853
21150
  }
20854
21151
  const existing = authDb.getUserByEmail(email);
20855
21152
  if (existing) {
20856
21153
  return reply.status(409).send({ error: "A user with this email already exists" });
20857
21154
  }
20858
- const passwordHash = await hashPassword(password);
21155
+ const passwordHash = password ? await hashPassword(password) : null;
20859
21156
  const user = authDb.createUser({ email, name, password_hash: passwordHash, role });
20860
21157
  return reply.status(201).send({ user });
20861
21158
  });
@@ -21016,6 +21313,21 @@ async function authSSORoutes(fastify, options) {
21016
21313
  }
21017
21314
  const existingByEmail = authDb.getUserByEmail(email);
21018
21315
  if (existingByEmail) {
21316
+ const isInvite = existingByEmail.password_hash === null && (existingByEmail.provider === "local" || existingByEmail.provider === provider) && !existingByEmail.external_id;
21317
+ if (isInvite) {
21318
+ authDb.updateUser(existingByEmail.id, {
21319
+ provider,
21320
+ external_id: externalId,
21321
+ name
21322
+ });
21323
+ const token2 = generateSessionToken();
21324
+ authDb.createSession(token2, existingByEmail.id, sessionTtlMs);
21325
+ reply.setCookie("yamchart_session", token2, cookieOptions);
21326
+ const ret2 = sanitizeReturnTo(request.cookies?.yamchart_sso_return);
21327
+ if (ret2)
21328
+ reply.clearCookie("yamchart_sso_return", { path: "/" });
21329
+ return reply.redirect(ret2 ?? "/#/");
21330
+ }
21019
21331
  return reply.redirect("/#/login?error=account_conflict");
21020
21332
  }
21021
21333
  const { allowed, role } = resolveSsoProvisioning(email, config);
@@ -21414,7 +21726,7 @@ async function chatRoutes(fastify, options) {
21414
21726
  systemParts.push("Output ONLY the new text to insert. Do NOT repeat the existing content.");
21415
21727
  }
21416
21728
  const systemPrompt = systemParts.join("\n");
21417
- const { AnthropicProvider } = await import("./dist-MNXSMGV6.js");
21729
+ const { AnthropicProvider } = await import("./dist-5IFWLWND.js");
21418
21730
  const provider = new AnthropicProvider(apiKey);
21419
21731
  const response = await provider.chat({
21420
21732
  system: systemPrompt,
@@ -28512,8 +28824,8 @@ async function runSql(deps, args) {
28512
28824
  if (DESTRUCTIVE.test(sql) || /^\s*WITH\b/i.test(sql) && WRITABLE_CTE.test(sql)) {
28513
28825
  return "Error: Destructive SQL statements are not allowed through the MCP.";
28514
28826
  }
28515
- const limit = args.limit ?? DEFAULT_LIMIT;
28516
- 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}`;
28517
28829
  const result = await deps.queryService.executeRawSql(limited);
28518
28830
  return JSON.stringify({ columns: result.columns, rows: result.rows });
28519
28831
  }
@@ -28569,8 +28881,9 @@ async function listTables(deps) {
28569
28881
  })));
28570
28882
  }
28571
28883
  async function describeTable(deps, args) {
28572
- const parts = args.table.split(".");
28573
- 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;
28574
28887
  const schema = parts.length > 1 ? parts.slice(0, -1).join(".") : void 0;
28575
28888
  const tables = await readCatalogTables(deps.projectDir, includeFilter(deps));
28576
28889
  const match = tables.find((t) => t.name === tableName && (schema === void 0 || t.schema === schema));
@@ -28588,14 +28901,14 @@ async function sampleTable(deps, args) {
28588
28901
  if (!TABLE_IDENT.test(args.table)) {
28589
28902
  return `Error: Invalid table identifier "${args.table}".`;
28590
28903
  }
28591
- const limit = Math.min(Math.max(args.limit ?? 5, 1), 100);
28904
+ const limit2 = Math.min(Math.max(args.limit ?? 5, 1), 100);
28592
28905
  const parts = args.table.split(".");
28593
28906
  const tableName = parts[parts.length - 1] ?? args.table;
28594
28907
  const providedSchema = parts.length > 1 ? parts.slice(0, -1).join(".") : void 0;
28595
28908
  const tables = await readCatalogTables(deps.projectDir, includeFilter(deps));
28596
28909
  const match = tables.find((t) => t.name === tableName && (providedSchema === void 0 || t.schema === providedSchema));
28597
28910
  const from = providedSchema === void 0 && match?.schema ? `${match.schema}.${args.table}` : args.table;
28598
- const result = await deps.queryService.executeRawSql(`SELECT * FROM ${from} LIMIT ${limit}`);
28911
+ const result = await deps.queryService.executeRawSql(`SELECT * FROM ${from} LIMIT ${limit2}`);
28599
28912
  return JSON.stringify({ columns: result.columns, rows: result.rows });
28600
28913
  }
28601
28914
 
@@ -28676,11 +28989,40 @@ function renderChartToPng(option, opts = {}) {
28676
28989
  return Buffer.from(resvg.render().asPng());
28677
28990
  }
28678
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
+
28679
29021
  // ../../packages/mcp-core/dist/tools/visualize.js
28680
29022
  async function visualize(_deps, args) {
28681
29023
  const parsed = ChartSchema.safeParse(args.spec);
28682
29024
  if (!parsed.success) {
28683
- return { error: `Invalid chart spec: ${parsed.error.issues.map((i) => i.message).join("; ")}` };
29025
+ return { error: formatChartErrors(parsed.error) };
28684
29026
  }
28685
29027
  try {
28686
29028
  const option = buildEChartsOption(parsed.data, args.data ?? []);
@@ -28707,7 +29049,7 @@ async function saveChart(deps, args) {
28707
29049
  }
28708
29050
  const parsed = ChartSchema.safeParse(args.spec);
28709
29051
  if (!parsed.success) {
28710
- return `Error: Invalid chart spec: ${parsed.error.issues.map((i) => i.message).join("; ")}`;
29052
+ return `Error: ${formatChartErrors(parsed.error)}`;
28711
29053
  }
28712
29054
  const name = parsed.data.name;
28713
29055
  if (!SAFE_NAME.test(name)) {
@@ -28723,6 +29065,450 @@ async function saveChart(deps, args) {
28723
29065
  return `Chart "${name}" saved to charts/${name}.yaml`;
28724
29066
  }
28725
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
+
28726
29512
  // ../../packages/mcp-core/dist/render/embedResource.js
28727
29513
  import { createHash } from "crypto";
28728
29514
  import { createUIResource } from "@mcp-ui/server";
@@ -28830,7 +29616,10 @@ function createMcpServer(deps) {
28830
29616
  inputSchema: { sql: z2.string(), limit: z2.number().optional() }
28831
29617
  }, async (args) => text(await runSql(deps, args)));
28832
29618
  server.registerTool("list_tables", { description: "List warehouse tables from the cached catalog (name, schema, connection, column count).", inputSchema: {} }, async () => text(await listTables(deps)));
28833
- 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)));
28834
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)));
28835
29624
  server.registerTool("visualize", {
28836
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.",
@@ -28858,6 +29647,66 @@ function createMcpServer(deps) {
28858
29647
  description: "Save a yamchart chart spec to charts/<name>.yaml (requires editor/admin).",
28859
29648
  inputSchema: { spec: z2.record(z2.unknown()) }
28860
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)));
28861
29710
  return server;
28862
29711
  }
28863
29712
 
@@ -28869,13 +29718,19 @@ function getUser(request) {
28869
29718
  return { id: user.id, email: user.email, role: user.role, attributes: user.attributes };
28870
29719
  }
28871
29720
  async function mcpRoutes(fastify, options) {
28872
- const { configLoader, queryService, projectDir } = options;
29721
+ const { configLoader, queryService, projectDir, semanticLayer, connectionType, knowledgeStore, embedder, answerProvider, aiConnectionName } = options;
28873
29722
  fastify.post("/mcp", async (request, reply) => {
28874
29723
  const server = createMcpServer({
28875
29724
  configLoader,
28876
29725
  queryService,
28877
29726
  projectDir,
28878
- user: getUser(request)
29727
+ user: getUser(request),
29728
+ semanticLayer,
29729
+ connectionType,
29730
+ knowledgeStore,
29731
+ embedder,
29732
+ answerProvider,
29733
+ aiConnectionName
28879
29734
  });
28880
29735
  const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
28881
29736
  reply.raw.on("close", () => {
@@ -28899,7 +29754,7 @@ async function mcpRoutes(fastify, options) {
28899
29754
  }
28900
29755
 
28901
29756
  // ../server/dist/routes/catalog.js
28902
- import { join as join9 } from "path";
29757
+ import { join as join10 } from "path";
28903
29758
  import { readFile as readFile5 } from "fs/promises";
28904
29759
  async function catalogRoutes(fastify, options) {
28905
29760
  const { configLoader, projectDir } = options;
@@ -28912,7 +29767,7 @@ async function catalogRoutes(fastify, options) {
28912
29767
  name: m.metadata.name,
28913
29768
  description: m.metadata.description
28914
29769
  }));
28915
- const catalogPath = join9(projectDir, ".yamchart", "catalog.json");
29770
+ const catalogPath = join10(projectDir, ".yamchart", "catalog.json");
28916
29771
  let tables = [];
28917
29772
  try {
28918
29773
  const catalogData = JSON.parse(await readFile5(catalogPath, "utf-8"));
@@ -28948,8 +29803,310 @@ async function catalogRoutes(fastify, options) {
28948
29803
  });
28949
29804
  }
28950
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
+
28951
30108
  // ../server/dist/oauth/service.js
28952
- import { randomUUID, timingSafeEqual as timingSafeEqual2 } from "crypto";
30109
+ import { randomUUID as randomUUID3, timingSafeEqual as timingSafeEqual2 } from "crypto";
28953
30110
 
28954
30111
  // ../server/dist/oauth/errors.js
28955
30112
  var OAuthError = class extends Error {
@@ -29017,7 +30174,7 @@ var OAuthService = class {
29017
30174
  }
29018
30175
  const authMethod = input.token_endpoint_auth_method === "client_secret_post" ? "client_secret_post" : "none";
29019
30176
  const grantTypes = input.grant_types?.length ? input.grant_types : ["authorization_code", "refresh_token"];
29020
- const clientId = randomUUID();
30177
+ const clientId = randomUUID3();
29021
30178
  let clientSecret;
29022
30179
  let clientSecretHash = null;
29023
30180
  if (authMethod === "client_secret_post") {
@@ -29474,17 +30631,17 @@ data: ${data}
29474
30631
  }
29475
30632
 
29476
30633
  // ../server/dist/server.js
29477
- import { join as join10, dirname as dirname2 } from "path";
30634
+ import { join as join11, dirname as dirname2 } from "path";
29478
30635
  import { fileURLToPath } from "url";
29479
30636
  import { access as access2, readFile as readFile6 } from "fs/promises";
29480
30637
  import { watch as fsWatch } from "fs";
29481
30638
  var __dirname = dirname2(fileURLToPath(import.meta.url));
29482
- var packageJsonPath = join10(__dirname, "..", "package.json");
30639
+ var packageJsonPath = join11(__dirname, "..", "package.json");
29483
30640
  var packageJson = JSON.parse(await readFile6(packageJsonPath, "utf-8"));
29484
30641
  var VERSION = packageJson.version;
29485
30642
  async function enrichRefsFromCatalog(projectDir, refs) {
29486
30643
  try {
29487
- const catalogPath = join10(projectDir, ".yamchart", "catalog.json");
30644
+ const catalogPath = join11(projectDir, ".yamchart", "catalog.json");
29488
30645
  const catalogData = JSON.parse(await readFile6(catalogPath, "utf-8"));
29489
30646
  for (const model of catalogData.models ?? []) {
29490
30647
  if (model.name && model.table) {
@@ -29495,7 +30652,7 @@ async function enrichRefsFromCatalog(projectDir, refs) {
29495
30652
  }
29496
30653
  }
29497
30654
  async function createServer(options) {
29498
- 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;
29499
30656
  if (auth) {
29500
30657
  initAuthServer(auth.supabaseUrl, auth.supabaseServiceKey);
29501
30658
  }
@@ -29539,6 +30696,19 @@ async function createServer(options) {
29539
30696
  maxSize: 1e3,
29540
30697
  defaultTtlMs: cacheTtl
29541
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
+ }
29542
30712
  const models = {};
29543
30713
  const refs = {};
29544
30714
  for (const model of configLoader.getModels()) {
@@ -29685,16 +30855,103 @@ async function createServer(options) {
29685
30855
  await fastify.register(catalogRoutes, { configLoader, projectDir });
29686
30856
  }
29687
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
+ };
29688
30892
  if (authDb && oauthService) {
29689
30893
  const mcpAuth = createMcpAuthPreHandler({ authDb, oauthService, baseUrl: mcpBaseUrl });
29690
30894
  await fastify.register(async (mcpScope) => {
29691
30895
  mcpScope.addHook("preHandler", mcpAuth);
29692
- await mcpScope.register(mcpRoutes, { configLoader, queryService, projectDir });
30896
+ await mcpScope.register(mcpRoutes, mcpOptions);
29693
30897
  });
29694
30898
  } else {
29695
- await fastify.register(mcpRoutes, { configLoader, queryService, projectDir });
30899
+ await fastify.register(mcpRoutes, mcpOptions);
29696
30900
  }
29697
- 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");
29698
30955
  try {
29699
30956
  await access2(assetsDir);
29700
30957
  await fastify.register(fastifyStatic, {
@@ -29705,7 +30962,7 @@ async function createServer(options) {
29705
30962
  } catch {
29706
30963
  }
29707
30964
  if (project.theme?.customCss) {
29708
- const cssPath = join10(projectDir, project.theme.customCss);
30965
+ const cssPath = join11(projectDir, project.theme.customCss);
29709
30966
  fastify.get("/api/theme/custom.css", async (_request2, reply) => {
29710
30967
  try {
29711
30968
  const content = await readFile6(cssPath, "utf-8");
@@ -29764,7 +31021,7 @@ async function createServer(options) {
29764
31021
  fastify.log.info(`Semantic model rebuilt: ${semanticService.getModel()?.entities.length ?? 0} entities`);
29765
31022
  broadcastConfigChange();
29766
31023
  });
29767
- const catalogPath = join10(projectDir, ".yamchart", "catalog.json");
31024
+ const catalogPath = join11(projectDir, ".yamchart", "catalog.json");
29768
31025
  try {
29769
31026
  catalogWatcher = fsWatch(catalogPath, () => {
29770
31027
  semanticService.buildFromDisk();
@@ -29828,15 +31085,15 @@ async function runDevServer(projectDir, options) {
29828
31085
  let usesBrowserAuth = false;
29829
31086
  let localAuth;
29830
31087
  try {
29831
- const { readFileSync: readFileSync2 } = await import("fs");
31088
+ const { readFileSync: readFileSync3 } = await import("fs");
29832
31089
  const { parse: parse2 } = await import("yaml");
29833
- const { resolveProjectConfig: resolveProjectConfig2, deepMerge: deepMerge3 } = await import("./dist-MX5K2ABB.js");
29834
- 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");
29835
31092
  const rawConfig = parse2(raw);
29836
31093
  const resolvedEnv = options.env || process.env.YAMCHART_ENV || void 0;
29837
31094
  let projectConfig = resolveProjectConfig2(rawConfig, resolvedEnv);
29838
31095
  try {
29839
- const localRaw = readFileSync2(resolve2(projectDir, "yamchart.local.yaml"), "utf-8");
31096
+ const localRaw = readFileSync3(resolve2(projectDir, "yamchart.local.yaml"), "utf-8");
29840
31097
  const localOverrides = parse2(localRaw);
29841
31098
  if (localOverrides && typeof localOverrides === "object") {
29842
31099
  projectConfig = deepMerge3(projectConfig, localOverrides);
@@ -29854,7 +31111,7 @@ async function runDevServer(projectDir, options) {
29854
31111
  const targetConn = process.env.YAMCHART_CONNECTION || projectConfig?.defaults?.connection;
29855
31112
  if (targetConn) {
29856
31113
  try {
29857
- const connRaw = readFileSync2(resolve2(connectionDir, `${targetConn}.yaml`), "utf-8");
31114
+ const connRaw = readFileSync3(resolve2(connectionDir, `${targetConn}.yaml`), "utf-8");
29858
31115
  const connConfig = parse2(connRaw);
29859
31116
  if (connConfig?.auth?.type === "externalbrowser") {
29860
31117
  usesBrowserAuth = true;
@@ -29934,4 +31191,4 @@ async function runDevServer(projectDir, options) {
29934
31191
  export {
29935
31192
  runDevServer
29936
31193
  };
29937
- //# sourceMappingURL=dev-7YLRQ6SA.js.map
31194
+ //# sourceMappingURL=dev-LZ4YHNDJ.js.map