yamchart 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/CLAUDE.md +51 -0
  2. package/dist/{advisor-5BTRAICH.js → advisor-57BOMUUF.js} +11 -10
  3. package/dist/{advisor-5BTRAICH.js.map → advisor-57BOMUUF.js.map} +1 -1
  4. package/dist/{chunk-5VA43CTW.js → chunk-E2UZXDF6.js} +37 -42
  5. package/dist/chunk-E2UZXDF6.js.map +1 -0
  6. package/dist/{chunk-SSUVEADJ.js → chunk-JYQKDWLG.js} +76 -14
  7. package/dist/chunk-JYQKDWLG.js.map +1 -0
  8. package/dist/{chunk-565OEBW7.js → chunk-OTAUP5RC.js} +3 -3
  9. package/dist/chunk-UND73EOB.js +449 -0
  10. package/dist/chunk-UND73EOB.js.map +1 -0
  11. package/dist/{chunk-UDRJFEJU.js → chunk-X6LQGWUX.js} +349 -176
  12. package/dist/chunk-X6LQGWUX.js.map +1 -0
  13. package/dist/{connection-utils-AGSQ5HNN.js → connection-utils-FTSZU2VF.js} +5 -4
  14. package/dist/{describe-AA4UT4U6.js → describe-TGIOBNJB.js} +5 -4
  15. package/dist/{describe-AA4UT4U6.js.map → describe-TGIOBNJB.js.map} +1 -1
  16. package/dist/{dev-EUWIRL4N.js → dev-MRZ76V74.js} +485 -44
  17. package/dist/dev-MRZ76V74.js.map +1 -0
  18. package/dist/{dist-LZNDQDH3.js → dist-PVHFUYP2.js} +23 -3
  19. package/dist/{dist-YTGUIBKG.js → dist-WDTDQDTX.js} +72 -1
  20. package/dist/dist-WDTDQDTX.js.map +1 -0
  21. package/dist/index.js +135 -17
  22. package/dist/index.js.map +1 -1
  23. package/dist/{init-CI4VARQG.js → init-UYQE5DPU.js} +2 -2
  24. package/dist/{init-CI4VARQG.js.map → init-UYQE5DPU.js.map} +1 -1
  25. package/dist/public/assets/EventManagement-BlxJ2TFw.js +18 -0
  26. package/dist/public/assets/{LoginPage-BwMmpq8e.js → LoginPage-BT8ikmPn.js} +1 -1
  27. package/dist/public/assets/PublicViewer-B4uFxgbt.js +1 -0
  28. package/dist/public/assets/{SetupWizard-DU8R2dPp.js → SetupWizard-njrOhCzw.js} +1 -1
  29. package/dist/public/assets/ShareManagement-Bg16oJhW.js +1 -0
  30. package/dist/public/assets/UserManagement-tiCIT4UY.js +1 -0
  31. package/dist/public/assets/index-B41yj3io.js +187 -0
  32. package/dist/public/assets/{index-C0hWblBI.css → index-BZ25r23j.css} +1 -1
  33. package/dist/public/assets/{index.es-YeJujQ5o.js → index.es-BuktD_R2.js} +1 -1
  34. package/dist/public/assets/{jspdf.es.min-BzZgn3ka.js → jspdf.es.min-DFRl2hZQ.js} +3 -3
  35. package/dist/public/index.html +2 -2
  36. package/dist/{query-THDVBBVZ.js → query-JRMMNXX6.js} +5 -4
  37. package/dist/{query-THDVBBVZ.js.map → query-JRMMNXX6.js.map} +1 -1
  38. package/dist/{sample-6WPQS7PA.js → sample-VGIY4U4J.js} +5 -4
  39. package/dist/{sample-6WPQS7PA.js.map → sample-VGIY4U4J.js.map} +1 -1
  40. package/dist/{search-657DXBRS.js → search-QSYNG4SR.js} +5 -4
  41. package/dist/{search-657DXBRS.js.map → search-QSYNG4SR.js.map} +1 -1
  42. package/dist/semantic-RAP3S5PQ.js +39 -0
  43. package/dist/semantic-RAP3S5PQ.js.map +1 -0
  44. package/dist/{sync-warehouse-4KBV5S3L.js → sync-warehouse-XHTBZH25.js} +5 -4
  45. package/dist/{sync-warehouse-4KBV5S3L.js.map → sync-warehouse-XHTBZH25.js.map} +1 -1
  46. package/dist/{tables-KLDBUUSE.js → tables-UOO342TA.js} +5 -4
  47. package/dist/{tables-KLDBUUSE.js.map → tables-UOO342TA.js.map} +1 -1
  48. package/dist/templates/default/.claude/skills/databricks/SKILL.md +76 -0
  49. package/dist/templates/default/.claude/skills/dbt-advisor/SKILL.md +107 -0
  50. package/dist/templates/default/.claude/skills/duckdb/SKILL.md +67 -0
  51. package/dist/templates/default/.claude/skills/mysql/SKILL.md +69 -0
  52. package/dist/templates/default/.claude/skills/postgres/SKILL.md +69 -0
  53. package/dist/templates/default/.claude/skills/snowflake/SKILL.md +64 -0
  54. package/dist/templates/default/CLAUDE.md +7 -0
  55. package/dist/templates/default/docs/yamchart-reference.md +222 -1
  56. package/dist/{test-JSAWS5ZP.js → test-TXRZWNXK.js} +5 -4
  57. package/dist/{test-JSAWS5ZP.js.map → test-TXRZWNXK.js.map} +1 -1
  58. package/dist/update-UKMEWCSO.js +220 -0
  59. package/dist/update-UKMEWCSO.js.map +1 -0
  60. package/package.json +5 -3
  61. package/dist/chunk-5VA43CTW.js.map +0 -1
  62. package/dist/chunk-SSUVEADJ.js.map +0 -1
  63. package/dist/chunk-UDRJFEJU.js.map +0 -1
  64. package/dist/dev-EUWIRL4N.js.map +0 -1
  65. package/dist/dist-YTGUIBKG.js.map +0 -1
  66. package/dist/public/assets/PublicViewer-vIDojjIR.js +0 -1
  67. package/dist/public/assets/ShareManagement-7iS6lM2T.js +0 -1
  68. package/dist/public/assets/UserManagement-By7YRZrF.js +0 -1
  69. package/dist/public/assets/index-CXx1PiRF.js +0 -174
  70. package/dist/update-HCR6MYJX.js +0 -88
  71. package/dist/update-HCR6MYJX.js.map +0 -1
  72. /package/dist/{chunk-565OEBW7.js.map → chunk-OTAUP5RC.js.map} +0 -0
  73. /package/dist/{connection-utils-AGSQ5HNN.js.map → connection-utils-FTSZU2VF.js.map} +0 -0
  74. /package/dist/{dist-LZNDQDH3.js.map → dist-PVHFUYP2.js.map} +0 -0
package/CLAUDE.md ADDED
@@ -0,0 +1,51 @@
1
+ # yamchart — AI Assistant Instructions
2
+
3
+ You are working in a project that uses **yamchart**, a Git-native BI framework. Users define dashboards as YAML configs and SQL models; yamchart compiles, executes, and renders them.
4
+
5
+ ## Do Not Work Around Framework Bugs
6
+
7
+ If you identify a bug in yamchart's core behavior (rendering, cross-filtering, template processing, schema validation, etc.), do **NOT** modify the user's SQL models, chart configs, or project code to compensate. Instead:
8
+
9
+ 1. Diagnose and confirm the root cause is in the framework
10
+ 2. Write a bug report with: yamchart version, repro steps, root cause analysis, suggested fix
11
+ 3. Present the report to the user to file at https://github.com/simon-spenc/yamchart/issues
12
+
13
+ Examples of things you must NOT do:
14
+ - Add type-detection logic in Nunjucks templates to handle framework type mismatches
15
+ - Duplicate or restructure YAML configs to avoid rendering bugs
16
+ - Add pre/post-processing scripts to fix data before it reaches yamchart
17
+ - Wrap yamchart components or override styles to fix visual bugs
18
+ - Add SQL workarounds for query compilation issues
19
+
20
+ ## Config Reference
21
+
22
+ The authoritative config reference is `docs/yamchart-reference.md` in the user's project (generated by `yamchart init`). If documented behavior doesn't work, that's a bug — report it, don't work around it.
23
+
24
+ ## Template Engine
25
+
26
+ yamchart uses Nunjucks (Jinja-compatible). Key rules:
27
+ - Dashboard filters pass values with types matching their filter `type` config (e.g., `multi_select` passes arrays, `select` passes strings)
28
+ - All template parameters are provided by the framework — do not add type coercion in user templates
29
+ - Use `{{ ref('table') }}` for table references, not hardcoded table names
30
+ - Date range parameters automatically provide `{{ start_date }}` and `{{ end_date }}`
31
+
32
+ ## Project Structure
33
+
34
+ A yamchart project typically contains:
35
+ - `yamchart.yaml` — project config, default connection, theme
36
+ - `connections/*.yaml` — database connections
37
+ - `models/*.sql` — SQL with Jinja templating
38
+ - `charts/*.yaml` — chart definitions
39
+ - `dashboards/*.yaml` — grid layouts with widgets
40
+ - `schedules/*.yaml` — cron-based reports and alerts
41
+
42
+ ## Useful Commands
43
+
44
+ ```bash
45
+ yamchart dev # Start dev server
46
+ yamchart validate # Validate configs
47
+ yamchart test # Run model assertions
48
+ yamchart tables # List database tables
49
+ yamchart describe <table> # Show columns
50
+ yamchart search <keyword> # Find tables/columns
51
+ ```
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createConnector,
3
3
  resolveConnection
4
- } from "./chunk-565OEBW7.js";
4
+ } from "./chunk-OTAUP5RC.js";
5
5
  import {
6
6
  detail,
7
7
  error,
@@ -10,8 +10,9 @@ import {
10
10
  success,
11
11
  warning
12
12
  } from "./chunk-HJVVHYVN.js";
13
- import "./chunk-UDRJFEJU.js";
14
- import "./chunk-SSUVEADJ.js";
13
+ import "./chunk-X6LQGWUX.js";
14
+ import "./chunk-JYQKDWLG.js";
15
+ import "./chunk-UND73EOB.js";
15
16
  import "./chunk-DGUM43GV.js";
16
17
 
17
18
  // src/commands/advisor.ts
@@ -43,7 +44,7 @@ async function getDbtProjectPath(projectDir, override) {
43
44
  return null;
44
45
  }
45
46
  async function buildContext(projectDir, options) {
46
- const { parseModelMetadata } = await import("./dist-LZNDQDH3.js");
47
+ const { parseModelMetadata } = await import("./dist-PVHFUYP2.js");
47
48
  const modelsDir = join(projectDir, "models");
48
49
  const yamchartModels = [];
49
50
  try {
@@ -139,7 +140,7 @@ async function buildContext(projectDir, options) {
139
140
  };
140
141
  if (dbtPath) {
141
142
  try {
142
- const { detectConventions, listDbtModels } = await import("./dist-YTGUIBKG.js");
143
+ const { detectConventions, listDbtModels } = await import("./dist-WDTDQDTX.js");
143
144
  const dbtProjectYml = await readFile(join(dbtPath, "dbt_project.yml"), "utf-8");
144
145
  const dbtConfig = parseYaml(dbtProjectYml);
145
146
  const conventions = await detectConventions(dbtPath);
@@ -235,7 +236,7 @@ async function runAdvisor(projectDir, questionOrMode, options) {
235
236
  `warehouse: ${context.warehouse ? `connected (${context.warehouse.connectionType})` : "not connected"}`
236
237
  );
237
238
  console.log("");
238
- const { AdvisorAgent, AnthropicProvider, buildModelPath } = await import("./dist-YTGUIBKG.js");
239
+ const { AdvisorAgent, AnthropicProvider, buildModelPath } = await import("./dist-WDTDQDTX.js");
239
240
  const provider = new AnthropicProvider(apiKey);
240
241
  const agent = new AdvisorAgent(provider);
241
242
  const isAudit = questionOrMode === "audit";
@@ -280,7 +281,7 @@ ${pc.bold(`[${i + 1}]`)} ${formatProposal(proposal, context.dbt.conventions, bui
280
281
  spin2.stop();
281
282
  console.log(result.response);
282
283
  if (result.proposals.length > 0) {
283
- const { buildModelPath: bmp } = await import("./dist-YTGUIBKG.js");
284
+ const { buildModelPath: bmp } = await import("./dist-WDTDQDTX.js");
284
285
  for (const proposal of result.proposals) {
285
286
  console.log(
286
287
  `
@@ -321,7 +322,7 @@ ${formatProposal(proposal, context.dbt.conventions, bmp)}`
321
322
  conversationHistory.length = 0;
322
323
  conversationHistory.push(...result.messages);
323
324
  if (result.proposals.length > 0) {
324
- const { buildModelPath: bmp } = await import("./dist-YTGUIBKG.js");
325
+ const { buildModelPath: bmp } = await import("./dist-WDTDQDTX.js");
325
326
  for (const proposal of result.proposals) {
326
327
  console.log(
327
328
  `
@@ -339,7 +340,7 @@ async function offerApply(context, proposals) {
339
340
  return;
340
341
  }
341
342
  const { confirm } = await import("@inquirer/prompts");
342
- const { writeDbtModel, buildModelPath, formatModelSql, updateSchemaYml } = await import("./dist-YTGUIBKG.js");
343
+ const { writeDbtModel, buildModelPath, formatModelSql, updateSchemaYml } = await import("./dist-WDTDQDTX.js");
343
344
  for (const proposal of proposals) {
344
345
  console.log("");
345
346
  const shouldApply = await confirm({
@@ -378,4 +379,4 @@ async function offerApply(context, proposals) {
378
379
  export {
379
380
  runAdvisor
380
381
  };
381
- //# sourceMappingURL=advisor-5BTRAICH.js.map
382
+ //# sourceMappingURL=advisor-57BOMUUF.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/commands/advisor.ts"],"sourcesContent":["import { readFile, readdir, access } from 'fs/promises';\nimport { join } from 'path';\nimport { parse as parseYaml } from 'yaml';\nimport { createInterface } from 'readline';\nimport pc from 'picocolors';\nimport * as output from '../utils/output.js';\nimport { resolveConnection, createConnector } from './connection-utils.js';\nimport type { Message, AdvisorContext, Proposal, CatalogJsonData } from '@yamchart/advisor';\n\nexport interface AdvisorOptions {\n top?: number;\n json?: boolean;\n dbtPath?: string;\n connection?: string;\n}\n\nasync function loadCatalogMarkdown(projectDir: string): Promise<string | null> {\n try {\n const catalogPath = join(projectDir, '.yamchart', 'catalog.md');\n await access(catalogPath);\n return readFile(catalogPath, 'utf-8');\n } catch {\n return null;\n }\n}\n\nasync function getDbtProjectPath(projectDir: string, override?: string): Promise<string | null> {\n if (override) return override;\n\n try {\n const dbtSourcePath = join(projectDir, '.yamchart', 'dbt-source.yaml');\n const content = await readFile(dbtSourcePath, 'utf-8');\n const parsed = parseYaml(content) as { path?: string };\n if (parsed.path) {\n return join(projectDir, parsed.path);\n }\n } catch {\n // No saved dbt source config\n }\n\n return null;\n}\n\nasync function buildContext(\n projectDir: string,\n options: AdvisorOptions,\n): Promise<AdvisorContext> {\n // 1. Load yamchart models\n const { parseModelMetadata } = await import('@yamchart/query');\n const modelsDir = join(projectDir, 'models');\n const yamchartModels: AdvisorContext['yamchart']['models'] = [];\n\n try {\n const files = await readdir(modelsDir, { recursive: true });\n for (const file of files) {\n if (!String(file).endsWith('.sql')) continue;\n const content = await readFile(join(modelsDir, String(file)), 'utf-8');\n try {\n const metadata = parseModelMetadata(content);\n yamchartModels.push({\n name: metadata.name,\n description: metadata.description,\n sql: metadata.sql,\n params: metadata.params?.map((p) => ({ name: p.name, type: p.type })),\n returns: metadata.returns?.map((r) => ({ name: r.name, type: r.type })),\n });\n } catch {\n // Skip unparseable models\n }\n }\n } catch {\n // No models dir\n }\n\n // 2. Load yamchart charts\n const chartsDir = join(projectDir, 'charts');\n const yamchartCharts: AdvisorContext['yamchart']['charts'] = [];\n\n try {\n const files = await readdir(chartsDir, { recursive: true });\n for (const file of files) {\n const fname = String(file);\n if (!fname.endsWith('.yaml') && !fname.endsWith('.yml')) continue;\n const content = await readFile(join(chartsDir, fname), 'utf-8');\n try {\n const parsed = parseYaml(content) as {\n name?: string;\n title?: string;\n source?: { model?: string };\n chart?: { type?: string };\n };\n if (parsed.name && parsed.source?.model) {\n yamchartCharts.push({\n name: parsed.name,\n title: parsed.title,\n model: parsed.source.model,\n type: parsed.chart?.type ?? 'unknown',\n });\n }\n } catch {\n // Skip unparseable charts\n }\n }\n } catch {\n // No charts dir\n }\n\n // 3. Load yamchart dashboards\n const dashboardsDir = join(projectDir, 'dashboards');\n const yamchartDashboards: AdvisorContext['yamchart']['dashboards'] = [];\n\n try {\n const files = await readdir(dashboardsDir, { recursive: true });\n for (const file of files) {\n const fname = String(file);\n if (!fname.endsWith('.yaml') && !fname.endsWith('.yml')) continue;\n const content = await readFile(join(dashboardsDir, fname), 'utf-8');\n try {\n const parsed = parseYaml(content) as {\n name?: string;\n title?: string;\n layout?: { rows?: Array<{ widgets?: Array<{ ref?: string }> }> };\n };\n if (parsed.name) {\n const charts: string[] = [];\n for (const row of parsed.layout?.rows ?? []) {\n for (const widget of row.widgets ?? []) {\n if (widget.ref) charts.push(widget.ref);\n }\n }\n yamchartDashboards.push({\n name: parsed.name,\n title: parsed.title,\n charts,\n });\n }\n } catch {\n // Skip\n }\n }\n } catch {\n // No dashboards dir\n }\n\n // 4. Load catalog\n const catalog = await loadCatalogMarkdown(projectDir);\n\n // 4b. Load catalog.json (warehouse table metadata)\n let catalogJson: CatalogJsonData | null = null;\n try {\n const raw = await readFile(join(projectDir, '.yamchart', 'catalog.json'), 'utf-8');\n const parsed = JSON.parse(raw);\n catalogJson = { models: parsed.models || [] };\n } catch {\n // No catalog.json available\n }\n\n // 5. Load dbt project\n const dbtPath = await getDbtProjectPath(projectDir, options.dbtPath);\n let dbt: AdvisorContext['dbt'] = {\n projectPath: dbtPath ?? '',\n projectName: 'unknown',\n conventions: {\n folderStructure: [],\n namingPrefixes: {},\n commonMaterializations: {},\n schemaYmlPattern: 'per-folder',\n testPatterns: [],\n },\n models: [],\n };\n\n if (dbtPath) {\n try {\n const { detectConventions, listDbtModels } = await import('@yamchart/advisor');\n const dbtProjectYml = await readFile(join(dbtPath, 'dbt_project.yml'), 'utf-8');\n const dbtConfig = parseYaml(dbtProjectYml) as { name?: string };\n\n const conventions = await detectConventions(dbtPath);\n const models = await listDbtModels(dbtPath);\n\n dbt = {\n projectPath: dbtPath,\n projectName: dbtConfig.name ?? 'unknown',\n conventions,\n models,\n };\n } catch (err) {\n output.warning(\n `Could not read dbt project: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n // 6. Set up warehouse connection (optional)\n let warehouse: AdvisorContext['warehouse'] = null;\n\n try {\n const connection = await resolveConnection(projectDir, options.connection);\n const connector = createConnector(connection, projectDir);\n await connector.connect();\n\n warehouse = {\n connectionType: connection.type,\n executeSql: async (sql: string) => {\n const result = await connector.execute(sql);\n return {\n columns: result.columns.map((c) => c.name),\n rows: result.rows,\n };\n },\n };\n } catch {\n // No connection available — advisor works without it\n }\n\n return {\n yamchart: {\n models: yamchartModels,\n charts: yamchartCharts,\n dashboards: yamchartDashboards,\n catalog,\n catalogJson,\n },\n dbt,\n warehouse,\n };\n}\n\nfunction formatProposal(\n proposal: Proposal,\n conventions: AdvisorContext['dbt']['conventions'],\n buildModelPathFn: (\n name: string,\n layer: string | undefined,\n conventions: AdvisorContext['dbt']['conventions'],\n subfolder?: string,\n ) => string,\n): string {\n const path = buildModelPathFn(\n proposal.name,\n proposal.layer,\n conventions,\n proposal.subfolder,\n );\n\n const lines = [\n pc.bold(` ${proposal.name}`),\n ` ${pc.dim(proposal.explanation)}`,\n ` Path: ${pc.cyan(path)} (${proposal.materialization})`,\n '',\n pc.dim(' ' + '\\u2500'.repeat(30)),\n proposal.sql\n .split('\\n')\n .map((l: string) => ` ${pc.dim(l)}`)\n .join('\\n'),\n pc.dim(' ' + '\\u2500'.repeat(30)),\n ];\n return lines.join('\\n');\n}\n\nexport async function runAdvisor(\n projectDir: string,\n questionOrMode: string | undefined,\n options: AdvisorOptions,\n): Promise<void> {\n const apiKey = process.env.ANTHROPIC_API_KEY;\n if (!apiKey) {\n output.error('ANTHROPIC_API_KEY environment variable is required');\n output.detail('Set it in your .env file or shell environment');\n process.exit(1);\n }\n\n const spin = output.spinner('Loading project context...');\n const context = await buildContext(projectDir, options);\n spin.stop();\n\n // Print startup banner\n console.log('');\n output.header('dbt advisor');\n if (context.dbt.projectPath) {\n output.detail(\n `dbt project: ${context.dbt.projectName} at ${context.dbt.projectPath}`,\n );\n output.detail(\n `dbt models: ${context.dbt.models.length} (layers: ${context.dbt.conventions.folderStructure.join(', ') || 'none'})`,\n );\n } else {\n output.warning(\n 'No dbt project found. Run `yamchart sync-dbt --path <dbt-project>` to connect one.',\n );\n }\n output.detail(\n `yamchart: ${context.yamchart.models.length} models, ${context.yamchart.charts.length} charts, ${context.yamchart.dashboards.length} dashboards`,\n );\n output.detail(`catalog: ${context.yamchart.catalog ? 'loaded' : 'not synced'}`);\n output.detail(\n `warehouse: ${context.warehouse ? `connected (${context.warehouse.connectionType})` : 'not connected'}`,\n );\n console.log('');\n\n // Create agent\n const { AdvisorAgent, AnthropicProvider, buildModelPath } = await import(\n '@yamchart/advisor'\n );\n const provider = new AnthropicProvider(apiKey);\n const agent = new AdvisorAgent(provider);\n\n const isAudit = questionOrMode === 'audit';\n\n if (isAudit) {\n // One-shot audit mode\n const top = options.top ?? 5;\n const auditPrompt = `Run a comprehensive audit of this project. Return your top ${top} suggestions ranked by impact. For each, call propose_model with the full SQL.`;\n\n const auditSpin = output.spinner('Running audit...');\n const result = await agent.run(context, [\n { role: 'user', content: auditPrompt },\n ]);\n auditSpin.stop();\n\n if (options.json) {\n console.log(\n JSON.stringify(\n { text: result.response, proposals: result.proposals },\n null,\n 2,\n ),\n );\n return;\n }\n\n console.log(result.response);\n\n if (result.proposals.length > 0) {\n console.log('');\n output.header(`${result.proposals.length} suggestion(s):`);\n\n for (let i = 0; i < result.proposals.length; i++) {\n const proposal = result.proposals[i]!;\n console.log(\n `\\n${pc.bold(`[${i + 1}]`)} ${formatProposal(proposal, context.dbt.conventions, buildModelPath)}`,\n );\n }\n\n // Offer to apply\n await offerApply(context, result.proposals);\n }\n return;\n }\n\n // Interactive mode\n if (questionOrMode) {\n // Single question mode\n const spin2 = output.spinner('Thinking...');\n const result = await agent.run(context, [\n { role: 'user', content: questionOrMode },\n ]);\n spin2.stop();\n\n console.log(result.response);\n\n if (result.proposals.length > 0) {\n const { buildModelPath: bmp } = await import('@yamchart/advisor');\n for (const proposal of result.proposals) {\n console.log(\n `\\n${formatProposal(proposal, context.dbt.conventions, bmp)}`,\n );\n }\n await offerApply(context, result.proposals);\n }\n return;\n }\n\n // Multi-turn interactive mode\n console.log(\n pc.dim(\n ' Ask me anything, or type \"audit\" for a full analysis. Type \"exit\" to quit.\\n',\n ),\n );\n\n const conversationHistory: Message[] = [];\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n\n const prompt = (): Promise<string> =>\n new Promise((resolve) => rl.question(pc.green('> '), resolve));\n\n while (true) {\n const userInput = await prompt();\n if (!userInput.trim()) continue;\n if (userInput.trim().toLowerCase() === 'exit') {\n rl.close();\n break;\n }\n\n if (userInput.trim().toLowerCase() === 'audit') {\n conversationHistory.push({\n role: 'user',\n content: `Run a comprehensive audit. Return your top ${options.top ?? 5} suggestions ranked by impact.`,\n });\n } else {\n conversationHistory.push({ role: 'user', content: userInput });\n }\n\n const spin3 = output.spinner('Thinking...');\n const result = await agent.run(context, conversationHistory);\n spin3.stop();\n\n console.log('\\n' + result.response);\n\n // Update conversation history with the full exchange\n conversationHistory.length = 0;\n conversationHistory.push(...result.messages);\n\n if (result.proposals.length > 0) {\n const { buildModelPath: bmp } = await import('@yamchart/advisor');\n for (const proposal of result.proposals) {\n console.log(\n `\\n${formatProposal(proposal, context.dbt.conventions, bmp)}`,\n );\n }\n await offerApply(context, result.proposals);\n }\n\n console.log('');\n }\n}\n\nasync function offerApply(\n context: AdvisorContext,\n proposals: Proposal[],\n): Promise<void> {\n if (!context.dbt.projectPath) {\n output.warning('No dbt project path configured \\u2014 cannot write files');\n return;\n }\n\n const { confirm } = await import('@inquirer/prompts');\n const { writeDbtModel, buildModelPath, formatModelSql, updateSchemaYml } =\n await import('@yamchart/advisor');\n\n for (const proposal of proposals) {\n console.log('');\n const shouldApply = await confirm({\n message: `Apply ${proposal.name} to dbt project?`,\n default: false,\n });\n\n if (!shouldApply) continue;\n\n const modelPath = buildModelPath(\n proposal.name,\n proposal.layer,\n context.dbt.conventions,\n proposal.subfolder,\n );\n\n const sql = formatModelSql({\n materialization: proposal.materialization,\n sql: proposal.sql,\n });\n\n await writeDbtModel(context.dbt.projectPath, modelPath, sql);\n output.success(`Created ${modelPath}`);\n\n // Update schema.yml\n const schemaDir = modelPath.split('/').slice(0, -1).join('/');\n const schemaPath = `${schemaDir}/schema.yml`;\n\n try {\n await updateSchemaYml(context.dbt.projectPath, schemaPath, {\n name: proposal.name,\n description: proposal.description,\n columns: proposal.columns,\n });\n output.success(`Updated ${schemaPath}`);\n } catch (err) {\n output.warning(\n `Could not update schema.yml: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAAA,SAAS,UAAU,SAAS,cAAc;AAC1C,SAAS,YAAY;AACrB,SAAS,SAAS,iBAAiB;AACnC,SAAS,uBAAuB;AAChC,OAAO,QAAQ;AAYf,eAAe,oBAAoB,YAA4C;AAC7E,MAAI;AACF,UAAM,cAAc,KAAK,YAAY,aAAa,YAAY;AAC9D,UAAM,OAAO,WAAW;AACxB,WAAO,SAAS,aAAa,OAAO;AAAA,EACtC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,kBAAkB,YAAoB,UAA2C;AAC9F,MAAI,SAAU,QAAO;AAErB,MAAI;AACF,UAAM,gBAAgB,KAAK,YAAY,aAAa,iBAAiB;AACrE,UAAM,UAAU,MAAM,SAAS,eAAe,OAAO;AACrD,UAAM,SAAS,UAAU,OAAO;AAChC,QAAI,OAAO,MAAM;AACf,aAAO,KAAK,YAAY,OAAO,IAAI;AAAA,IACrC;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO;AACT;AAEA,eAAe,aACb,YACA,SACyB;AAEzB,QAAM,EAAE,mBAAmB,IAAI,MAAM,OAAO,oBAAiB;AAC7D,QAAM,YAAY,KAAK,YAAY,QAAQ;AAC3C,QAAM,iBAAuD,CAAC;AAE9D,MAAI;AACF,UAAM,QAAQ,MAAM,QAAQ,WAAW,EAAE,WAAW,KAAK,CAAC;AAC1D,eAAW,QAAQ,OAAO;AACxB,UAAI,CAAC,OAAO,IAAI,EAAE,SAAS,MAAM,EAAG;AACpC,YAAM,UAAU,MAAM,SAAS,KAAK,WAAW,OAAO,IAAI,CAAC,GAAG,OAAO;AACrE,UAAI;AACF,cAAM,WAAW,mBAAmB,OAAO;AAC3C,uBAAe,KAAK;AAAA,UAClB,MAAM,SAAS;AAAA,UACf,aAAa,SAAS;AAAA,UACtB,KAAK,SAAS;AAAA,UACd,QAAQ,SAAS,QAAQ,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,EAAE;AAAA,UACpE,SAAS,SAAS,SAAS,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,EAAE;AAAA,QACxE,CAAC;AAAA,MACH,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAGA,QAAM,YAAY,KAAK,YAAY,QAAQ;AAC3C,QAAM,iBAAuD,CAAC;AAE9D,MAAI;AACF,UAAM,QAAQ,MAAM,QAAQ,WAAW,EAAE,WAAW,KAAK,CAAC;AAC1D,eAAW,QAAQ,OAAO;AACxB,YAAM,QAAQ,OAAO,IAAI;AACzB,UAAI,CAAC,MAAM,SAAS,OAAO,KAAK,CAAC,MAAM,SAAS,MAAM,EAAG;AACzD,YAAM,UAAU,MAAM,SAAS,KAAK,WAAW,KAAK,GAAG,OAAO;AAC9D,UAAI;AACF,cAAM,SAAS,UAAU,OAAO;AAMhC,YAAI,OAAO,QAAQ,OAAO,QAAQ,OAAO;AACvC,yBAAe,KAAK;AAAA,YAClB,MAAM,OAAO;AAAA,YACb,OAAO,OAAO;AAAA,YACd,OAAO,OAAO,OAAO;AAAA,YACrB,MAAM,OAAO,OAAO,QAAQ;AAAA,UAC9B,CAAC;AAAA,QACH;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAGA,QAAM,gBAAgB,KAAK,YAAY,YAAY;AACnD,QAAM,qBAA+D,CAAC;AAEtE,MAAI;AACF,UAAM,QAAQ,MAAM,QAAQ,eAAe,EAAE,WAAW,KAAK,CAAC;AAC9D,eAAW,QAAQ,OAAO;AACxB,YAAM,QAAQ,OAAO,IAAI;AACzB,UAAI,CAAC,MAAM,SAAS,OAAO,KAAK,CAAC,MAAM,SAAS,MAAM,EAAG;AACzD,YAAM,UAAU,MAAM,SAAS,KAAK,eAAe,KAAK,GAAG,OAAO;AAClE,UAAI;AACF,cAAM,SAAS,UAAU,OAAO;AAKhC,YAAI,OAAO,MAAM;AACf,gBAAM,SAAmB,CAAC;AAC1B,qBAAW,OAAO,OAAO,QAAQ,QAAQ,CAAC,GAAG;AAC3C,uBAAW,UAAU,IAAI,WAAW,CAAC,GAAG;AACtC,kBAAI,OAAO,IAAK,QAAO,KAAK,OAAO,GAAG;AAAA,YACxC;AAAA,UACF;AACA,6BAAmB,KAAK;AAAA,YACtB,MAAM,OAAO;AAAA,YACb,OAAO,OAAO;AAAA,YACd;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAGA,QAAM,UAAU,MAAM,oBAAoB,UAAU;AAGpD,MAAI,cAAsC;AAC1C,MAAI;AACF,UAAM,MAAM,MAAM,SAAS,KAAK,YAAY,aAAa,cAAc,GAAG,OAAO;AACjF,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,kBAAc,EAAE,QAAQ,OAAO,UAAU,CAAC,EAAE;AAAA,EAC9C,QAAQ;AAAA,EAER;AAGA,QAAM,UAAU,MAAM,kBAAkB,YAAY,QAAQ,OAAO;AACnE,MAAI,MAA6B;AAAA,IAC/B,aAAa,WAAW;AAAA,IACxB,aAAa;AAAA,IACb,aAAa;AAAA,MACX,iBAAiB,CAAC;AAAA,MAClB,gBAAgB,CAAC;AAAA,MACjB,wBAAwB,CAAC;AAAA,MACzB,kBAAkB;AAAA,MAClB,cAAc,CAAC;AAAA,IACjB;AAAA,IACA,QAAQ,CAAC;AAAA,EACX;AAEA,MAAI,SAAS;AACX,QAAI;AACF,YAAM,EAAE,mBAAmB,cAAc,IAAI,MAAM,OAAO,oBAAmB;AAC7E,YAAM,gBAAgB,MAAM,SAAS,KAAK,SAAS,iBAAiB,GAAG,OAAO;AAC9E,YAAM,YAAY,UAAU,aAAa;AAEzC,YAAM,cAAc,MAAM,kBAAkB,OAAO;AACnD,YAAM,SAAS,MAAM,cAAc,OAAO;AAE1C,YAAM;AAAA,QACJ,aAAa;AAAA,QACb,aAAa,UAAU,QAAQ;AAAA,QAC/B;AAAA,QACA;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,MAAO;AAAA,QACL,+BAA+B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MACjF;AAAA,IACF;AAAA,EACF;AAGA,MAAI,YAAyC;AAE7C,MAAI;AACF,UAAM,aAAa,MAAM,kBAAkB,YAAY,QAAQ,UAAU;AACzE,UAAM,YAAY,gBAAgB,YAAY,UAAU;AACxD,UAAM,UAAU,QAAQ;AAExB,gBAAY;AAAA,MACV,gBAAgB,WAAW;AAAA,MAC3B,YAAY,OAAO,QAAgB;AACjC,cAAM,SAAS,MAAM,UAAU,QAAQ,GAAG;AAC1C,eAAO;AAAA,UACL,SAAS,OAAO,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,UACzC,MAAM,OAAO;AAAA,QACf;AAAA,MACF;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO;AAAA,IACL,UAAU;AAAA,MACR,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,eACP,UACA,aACA,kBAMQ;AACR,QAAM,OAAO;AAAA,IACX,SAAS;AAAA,IACT,SAAS;AAAA,IACT;AAAA,IACA,SAAS;AAAA,EACX;AAEA,QAAM,QAAQ;AAAA,IACZ,GAAG,KAAK,KAAK,SAAS,IAAI,EAAE;AAAA,IAC5B,KAAK,GAAG,IAAI,SAAS,WAAW,CAAC;AAAA,IACjC,WAAW,GAAG,KAAK,IAAI,CAAC,KAAK,SAAS,eAAe;AAAA,IACrD;AAAA,IACA,GAAG,IAAI,OAAO,SAAS,OAAO,EAAE,CAAC;AAAA,IACjC,SAAS,IACN,MAAM,IAAI,EACV,IAAI,CAAC,MAAc,KAAK,GAAG,IAAI,CAAC,CAAC,EAAE,EACnC,KAAK,IAAI;AAAA,IACZ,GAAG,IAAI,OAAO,SAAS,OAAO,EAAE,CAAC;AAAA,EACnC;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,eAAsB,WACpB,YACA,gBACA,SACe;AACf,QAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,CAAC,QAAQ;AACX,IAAO,MAAM,oDAAoD;AACjE,IAAO,OAAO,+CAA+C;AAC7D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,OAAc,QAAQ,4BAA4B;AACxD,QAAM,UAAU,MAAM,aAAa,YAAY,OAAO;AACtD,OAAK,KAAK;AAGV,UAAQ,IAAI,EAAE;AACd,EAAO,OAAO,aAAa;AAC3B,MAAI,QAAQ,IAAI,aAAa;AAC3B,IAAO;AAAA,MACL,gBAAgB,QAAQ,IAAI,WAAW,OAAO,QAAQ,IAAI,WAAW;AAAA,IACvE;AACA,IAAO;AAAA,MACL,eAAe,QAAQ,IAAI,OAAO,MAAM,aAAa,QAAQ,IAAI,YAAY,gBAAgB,KAAK,IAAI,KAAK,MAAM;AAAA,IACnH;AAAA,EACF,OAAO;AACL,IAAO;AAAA,MACL;AAAA,IACF;AAAA,EACF;AACA,EAAO;AAAA,IACL,aAAa,QAAQ,SAAS,OAAO,MAAM,YAAY,QAAQ,SAAS,OAAO,MAAM,YAAY,QAAQ,SAAS,WAAW,MAAM;AAAA,EACrI;AACA,EAAO,OAAO,YAAY,QAAQ,SAAS,UAAU,WAAW,YAAY,EAAE;AAC9E,EAAO;AAAA,IACL,cAAc,QAAQ,YAAY,cAAc,QAAQ,UAAU,cAAc,MAAM,eAAe;AAAA,EACvG;AACA,UAAQ,IAAI,EAAE;AAGd,QAAM,EAAE,cAAc,mBAAmB,eAAe,IAAI,MAAM,OAChE,oBACF;AACA,QAAM,WAAW,IAAI,kBAAkB,MAAM;AAC7C,QAAM,QAAQ,IAAI,aAAa,QAAQ;AAEvC,QAAM,UAAU,mBAAmB;AAEnC,MAAI,SAAS;AAEX,UAAM,MAAM,QAAQ,OAAO;AAC3B,UAAM,cAAc,8DAA8D,GAAG;AAErF,UAAM,YAAmB,QAAQ,kBAAkB;AACnD,UAAM,SAAS,MAAM,MAAM,IAAI,SAAS;AAAA,MACtC,EAAE,MAAM,QAAQ,SAAS,YAAY;AAAA,IACvC,CAAC;AACD,cAAU,KAAK;AAEf,QAAI,QAAQ,MAAM;AAChB,cAAQ;AAAA,QACN,KAAK;AAAA,UACH,EAAE,MAAM,OAAO,UAAU,WAAW,OAAO,UAAU;AAAA,UACrD;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA;AAAA,IACF;AAEA,YAAQ,IAAI,OAAO,QAAQ;AAE3B,QAAI,OAAO,UAAU,SAAS,GAAG;AAC/B,cAAQ,IAAI,EAAE;AACd,MAAO,OAAO,GAAG,OAAO,UAAU,MAAM,iBAAiB;AAEzD,eAAS,IAAI,GAAG,IAAI,OAAO,UAAU,QAAQ,KAAK;AAChD,cAAM,WAAW,OAAO,UAAU,CAAC;AACnC,gBAAQ;AAAA,UACN;AAAA,EAAK,GAAG,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,eAAe,UAAU,QAAQ,IAAI,aAAa,cAAc,CAAC;AAAA,QACjG;AAAA,MACF;AAGA,YAAM,WAAW,SAAS,OAAO,SAAS;AAAA,IAC5C;AACA;AAAA,EACF;AAGA,MAAI,gBAAgB;AAElB,UAAM,QAAe,QAAQ,aAAa;AAC1C,UAAM,SAAS,MAAM,MAAM,IAAI,SAAS;AAAA,MACtC,EAAE,MAAM,QAAQ,SAAS,eAAe;AAAA,IAC1C,CAAC;AACD,UAAM,KAAK;AAEX,YAAQ,IAAI,OAAO,QAAQ;AAE3B,QAAI,OAAO,UAAU,SAAS,GAAG;AAC/B,YAAM,EAAE,gBAAgB,IAAI,IAAI,MAAM,OAAO,oBAAmB;AAChE,iBAAW,YAAY,OAAO,WAAW;AACvC,gBAAQ;AAAA,UACN;AAAA,EAAK,eAAe,UAAU,QAAQ,IAAI,aAAa,GAAG,CAAC;AAAA,QAC7D;AAAA,MACF;AACA,YAAM,WAAW,SAAS,OAAO,SAAS;AAAA,IAC5C;AACA;AAAA,EACF;AAGA,UAAQ;AAAA,IACN,GAAG;AAAA,MACD;AAAA,IACF;AAAA,EACF;AAEA,QAAM,sBAAiC,CAAC;AACxC,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAE3E,QAAM,SAAS,MACb,IAAI,QAAQ,CAAC,YAAY,GAAG,SAAS,GAAG,MAAM,IAAI,GAAG,OAAO,CAAC;AAE/D,SAAO,MAAM;AACX,UAAM,YAAY,MAAM,OAAO;AAC/B,QAAI,CAAC,UAAU,KAAK,EAAG;AACvB,QAAI,UAAU,KAAK,EAAE,YAAY,MAAM,QAAQ;AAC7C,SAAG,MAAM;AACT;AAAA,IACF;AAEA,QAAI,UAAU,KAAK,EAAE,YAAY,MAAM,SAAS;AAC9C,0BAAoB,KAAK;AAAA,QACvB,MAAM;AAAA,QACN,SAAS,8CAA8C,QAAQ,OAAO,CAAC;AAAA,MACzE,CAAC;AAAA,IACH,OAAO;AACL,0BAAoB,KAAK,EAAE,MAAM,QAAQ,SAAS,UAAU,CAAC;AAAA,IAC/D;AAEA,UAAM,QAAe,QAAQ,aAAa;AAC1C,UAAM,SAAS,MAAM,MAAM,IAAI,SAAS,mBAAmB;AAC3D,UAAM,KAAK;AAEX,YAAQ,IAAI,OAAO,OAAO,QAAQ;AAGlC,wBAAoB,SAAS;AAC7B,wBAAoB,KAAK,GAAG,OAAO,QAAQ;AAE3C,QAAI,OAAO,UAAU,SAAS,GAAG;AAC/B,YAAM,EAAE,gBAAgB,IAAI,IAAI,MAAM,OAAO,oBAAmB;AAChE,iBAAW,YAAY,OAAO,WAAW;AACvC,gBAAQ;AAAA,UACN;AAAA,EAAK,eAAe,UAAU,QAAQ,IAAI,aAAa,GAAG,CAAC;AAAA,QAC7D;AAAA,MACF;AACA,YAAM,WAAW,SAAS,OAAO,SAAS;AAAA,IAC5C;AAEA,YAAQ,IAAI,EAAE;AAAA,EAChB;AACF;AAEA,eAAe,WACb,SACA,WACe;AACf,MAAI,CAAC,QAAQ,IAAI,aAAa;AAC5B,IAAO,QAAQ,0DAA0D;AACzE;AAAA,EACF;AAEA,QAAM,EAAE,QAAQ,IAAI,MAAM,OAAO,mBAAmB;AACpD,QAAM,EAAE,eAAe,gBAAgB,gBAAgB,gBAAgB,IACrE,MAAM,OAAO,oBAAmB;AAElC,aAAW,YAAY,WAAW;AAChC,YAAQ,IAAI,EAAE;AACd,UAAM,cAAc,MAAM,QAAQ;AAAA,MAChC,SAAS,SAAS,SAAS,IAAI;AAAA,MAC/B,SAAS;AAAA,IACX,CAAC;AAED,QAAI,CAAC,YAAa;AAElB,UAAM,YAAY;AAAA,MAChB,SAAS;AAAA,MACT,SAAS;AAAA,MACT,QAAQ,IAAI;AAAA,MACZ,SAAS;AAAA,IACX;AAEA,UAAM,MAAM,eAAe;AAAA,MACzB,iBAAiB,SAAS;AAAA,MAC1B,KAAK,SAAS;AAAA,IAChB,CAAC;AAED,UAAM,cAAc,QAAQ,IAAI,aAAa,WAAW,GAAG;AAC3D,IAAO,QAAQ,WAAW,SAAS,EAAE;AAGrC,UAAM,YAAY,UAAU,MAAM,GAAG,EAAE,MAAM,GAAG,EAAE,EAAE,KAAK,GAAG;AAC5D,UAAM,aAAa,GAAG,SAAS;AAE/B,QAAI;AACF,YAAM,gBAAgB,QAAQ,IAAI,aAAa,YAAY;AAAA,QACzD,MAAM,SAAS;AAAA,QACf,aAAa,SAAS;AAAA,QACtB,SAAS,SAAS;AAAA,MACpB,CAAC;AACD,MAAO,QAAQ,WAAW,UAAU,EAAE;AAAA,IACxC,SAAS,KAAK;AACZ,MAAO;AAAA,QACL,gCAAgC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MAClF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/commands/advisor.ts"],"sourcesContent":["import { readFile, readdir, access } from 'fs/promises';\nimport { join } from 'path';\nimport { parse as parseYaml } from 'yaml';\nimport { createInterface } from 'readline';\nimport pc from 'picocolors';\nimport * as output from '../utils/output.js';\nimport { resolveConnection, createConnector } from './connection-utils.js';\nimport type { Message, AdvisorContext, Proposal, CatalogJsonData } from '@yamchart/advisor';\n\nexport interface AdvisorOptions {\n top?: number;\n json?: boolean;\n dbtPath?: string;\n connection?: string;\n}\n\nasync function loadCatalogMarkdown(projectDir: string): Promise<string | null> {\n try {\n const catalogPath = join(projectDir, '.yamchart', 'catalog.md');\n await access(catalogPath);\n return readFile(catalogPath, 'utf-8');\n } catch {\n return null;\n }\n}\n\nasync function getDbtProjectPath(projectDir: string, override?: string): Promise<string | null> {\n if (override) return override;\n\n try {\n const dbtSourcePath = join(projectDir, '.yamchart', 'dbt-source.yaml');\n const content = await readFile(dbtSourcePath, 'utf-8');\n const parsed = parseYaml(content) as { path?: string };\n if (parsed.path) {\n return join(projectDir, parsed.path);\n }\n } catch {\n // No saved dbt source config\n }\n\n return null;\n}\n\nasync function buildContext(\n projectDir: string,\n options: AdvisorOptions,\n): Promise<AdvisorContext> {\n // 1. Load yamchart models\n const { parseModelMetadata } = await import('@yamchart/query');\n const modelsDir = join(projectDir, 'models');\n const yamchartModels: AdvisorContext['yamchart']['models'] = [];\n\n try {\n const files = await readdir(modelsDir, { recursive: true });\n for (const file of files) {\n if (!String(file).endsWith('.sql')) continue;\n const content = await readFile(join(modelsDir, String(file)), 'utf-8');\n try {\n const metadata = parseModelMetadata(content);\n yamchartModels.push({\n name: metadata.name,\n description: metadata.description,\n sql: metadata.sql,\n params: metadata.params?.map((p) => ({ name: p.name, type: p.type })),\n returns: metadata.returns?.map((r) => ({ name: r.name, type: r.type })),\n });\n } catch {\n // Skip unparseable models\n }\n }\n } catch {\n // No models dir\n }\n\n // 2. Load yamchart charts\n const chartsDir = join(projectDir, 'charts');\n const yamchartCharts: AdvisorContext['yamchart']['charts'] = [];\n\n try {\n const files = await readdir(chartsDir, { recursive: true });\n for (const file of files) {\n const fname = String(file);\n if (!fname.endsWith('.yaml') && !fname.endsWith('.yml')) continue;\n const content = await readFile(join(chartsDir, fname), 'utf-8');\n try {\n const parsed = parseYaml(content) as {\n name?: string;\n title?: string;\n source?: { model?: string };\n chart?: { type?: string };\n };\n if (parsed.name && parsed.source?.model) {\n yamchartCharts.push({\n name: parsed.name,\n title: parsed.title,\n model: parsed.source.model,\n type: parsed.chart?.type ?? 'unknown',\n });\n }\n } catch {\n // Skip unparseable charts\n }\n }\n } catch {\n // No charts dir\n }\n\n // 3. Load yamchart dashboards\n const dashboardsDir = join(projectDir, 'dashboards');\n const yamchartDashboards: AdvisorContext['yamchart']['dashboards'] = [];\n\n try {\n const files = await readdir(dashboardsDir, { recursive: true });\n for (const file of files) {\n const fname = String(file);\n if (!fname.endsWith('.yaml') && !fname.endsWith('.yml')) continue;\n const content = await readFile(join(dashboardsDir, fname), 'utf-8');\n try {\n const parsed = parseYaml(content) as {\n name?: string;\n title?: string;\n layout?: { rows?: Array<{ widgets?: Array<{ ref?: string }> }> };\n };\n if (parsed.name) {\n const charts: string[] = [];\n for (const row of parsed.layout?.rows ?? []) {\n for (const widget of row.widgets ?? []) {\n if (widget.ref) charts.push(widget.ref);\n }\n }\n yamchartDashboards.push({\n name: parsed.name,\n title: parsed.title,\n charts,\n });\n }\n } catch {\n // Skip\n }\n }\n } catch {\n // No dashboards dir\n }\n\n // 4. Load catalog\n const catalog = await loadCatalogMarkdown(projectDir);\n\n // 4b. Load catalog.json (warehouse table metadata)\n let catalogJson: CatalogJsonData | null = null;\n try {\n const raw = await readFile(join(projectDir, '.yamchart', 'catalog.json'), 'utf-8');\n const parsed = JSON.parse(raw);\n catalogJson = { models: parsed.models || [] };\n } catch {\n // No catalog.json available\n }\n\n // 5. Load dbt project\n const dbtPath = await getDbtProjectPath(projectDir, options.dbtPath);\n let dbt: AdvisorContext['dbt'] = {\n projectPath: dbtPath ?? '',\n projectName: 'unknown',\n conventions: {\n folderStructure: [],\n namingPrefixes: {},\n commonMaterializations: {},\n schemaYmlPattern: 'per-folder',\n testPatterns: [],\n },\n models: [],\n };\n\n if (dbtPath) {\n try {\n const { detectConventions, listDbtModels } = await import('@yamchart/advisor');\n const dbtProjectYml = await readFile(join(dbtPath, 'dbt_project.yml'), 'utf-8');\n const dbtConfig = parseYaml(dbtProjectYml) as { name?: string };\n\n const conventions = await detectConventions(dbtPath);\n const models = await listDbtModels(dbtPath);\n\n dbt = {\n projectPath: dbtPath,\n projectName: dbtConfig.name ?? 'unknown',\n conventions,\n models,\n };\n } catch (err) {\n output.warning(\n `Could not read dbt project: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n // 6. Set up warehouse connection (optional)\n let warehouse: AdvisorContext['warehouse'] = null;\n\n try {\n const connection = await resolveConnection(projectDir, options.connection);\n const connector = createConnector(connection, projectDir);\n await connector.connect();\n\n warehouse = {\n connectionType: connection.type,\n executeSql: async (sql: string) => {\n const result = await connector.execute(sql);\n return {\n columns: result.columns.map((c) => c.name),\n rows: result.rows,\n };\n },\n };\n } catch {\n // No connection available — advisor works without it\n }\n\n return {\n yamchart: {\n models: yamchartModels,\n charts: yamchartCharts,\n dashboards: yamchartDashboards,\n catalog,\n catalogJson,\n },\n dbt,\n warehouse,\n };\n}\n\nfunction formatProposal(\n proposal: Proposal,\n conventions: AdvisorContext['dbt']['conventions'],\n buildModelPathFn: (\n name: string,\n layer: string | undefined,\n conventions: AdvisorContext['dbt']['conventions'],\n subfolder?: string,\n ) => string,\n): string {\n const path = buildModelPathFn(\n proposal.name,\n proposal.layer,\n conventions,\n proposal.subfolder,\n );\n\n const lines = [\n pc.bold(` ${proposal.name}`),\n ` ${pc.dim(proposal.explanation)}`,\n ` Path: ${pc.cyan(path)} (${proposal.materialization})`,\n '',\n pc.dim(' ' + '\\u2500'.repeat(30)),\n proposal.sql\n .split('\\n')\n .map((l: string) => ` ${pc.dim(l)}`)\n .join('\\n'),\n pc.dim(' ' + '\\u2500'.repeat(30)),\n ];\n return lines.join('\\n');\n}\n\nexport async function runAdvisor(\n projectDir: string,\n questionOrMode: string | undefined,\n options: AdvisorOptions,\n): Promise<void> {\n const apiKey = process.env.ANTHROPIC_API_KEY;\n if (!apiKey) {\n output.error('ANTHROPIC_API_KEY environment variable is required');\n output.detail('Set it in your .env file or shell environment');\n process.exit(1);\n }\n\n const spin = output.spinner('Loading project context...');\n const context = await buildContext(projectDir, options);\n spin.stop();\n\n // Print startup banner\n console.log('');\n output.header('dbt advisor');\n if (context.dbt.projectPath) {\n output.detail(\n `dbt project: ${context.dbt.projectName} at ${context.dbt.projectPath}`,\n );\n output.detail(\n `dbt models: ${context.dbt.models.length} (layers: ${context.dbt.conventions.folderStructure.join(', ') || 'none'})`,\n );\n } else {\n output.warning(\n 'No dbt project found. Run `yamchart sync-dbt --path <dbt-project>` to connect one.',\n );\n }\n output.detail(\n `yamchart: ${context.yamchart.models.length} models, ${context.yamchart.charts.length} charts, ${context.yamchart.dashboards.length} dashboards`,\n );\n output.detail(`catalog: ${context.yamchart.catalog ? 'loaded' : 'not synced'}`);\n output.detail(\n `warehouse: ${context.warehouse ? `connected (${context.warehouse.connectionType})` : 'not connected'}`,\n );\n console.log('');\n\n // Create agent\n const { AdvisorAgent, AnthropicProvider, buildModelPath } = await import(\n '@yamchart/advisor'\n );\n const provider = new AnthropicProvider(apiKey);\n const agent = new AdvisorAgent(provider);\n\n const isAudit = questionOrMode === 'audit';\n\n if (isAudit) {\n // One-shot audit mode\n const top = options.top ?? 5;\n const auditPrompt = `Run a comprehensive audit of this project. Return your top ${top} suggestions ranked by impact. For each, call propose_model with the full SQL.`;\n\n const auditSpin = output.spinner('Running audit...');\n const result = await agent.run(context, [\n { role: 'user', content: auditPrompt },\n ]);\n auditSpin.stop();\n\n if (options.json) {\n console.log(\n JSON.stringify(\n { text: result.response, proposals: result.proposals },\n null,\n 2,\n ),\n );\n return;\n }\n\n console.log(result.response);\n\n if (result.proposals.length > 0) {\n console.log('');\n output.header(`${result.proposals.length} suggestion(s):`);\n\n for (let i = 0; i < result.proposals.length; i++) {\n const proposal = result.proposals[i]!;\n console.log(\n `\\n${pc.bold(`[${i + 1}]`)} ${formatProposal(proposal, context.dbt.conventions, buildModelPath)}`,\n );\n }\n\n // Offer to apply\n await offerApply(context, result.proposals);\n }\n return;\n }\n\n // Interactive mode\n if (questionOrMode) {\n // Single question mode\n const spin2 = output.spinner('Thinking...');\n const result = await agent.run(context, [\n { role: 'user', content: questionOrMode },\n ]);\n spin2.stop();\n\n console.log(result.response);\n\n if (result.proposals.length > 0) {\n const { buildModelPath: bmp } = await import('@yamchart/advisor');\n for (const proposal of result.proposals) {\n console.log(\n `\\n${formatProposal(proposal, context.dbt.conventions, bmp)}`,\n );\n }\n await offerApply(context, result.proposals);\n }\n return;\n }\n\n // Multi-turn interactive mode\n console.log(\n pc.dim(\n ' Ask me anything, or type \"audit\" for a full analysis. Type \"exit\" to quit.\\n',\n ),\n );\n\n const conversationHistory: Message[] = [];\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n\n const prompt = (): Promise<string> =>\n new Promise((resolve) => rl.question(pc.green('> '), resolve));\n\n while (true) {\n const userInput = await prompt();\n if (!userInput.trim()) continue;\n if (userInput.trim().toLowerCase() === 'exit') {\n rl.close();\n break;\n }\n\n if (userInput.trim().toLowerCase() === 'audit') {\n conversationHistory.push({\n role: 'user',\n content: `Run a comprehensive audit. Return your top ${options.top ?? 5} suggestions ranked by impact.`,\n });\n } else {\n conversationHistory.push({ role: 'user', content: userInput });\n }\n\n const spin3 = output.spinner('Thinking...');\n const result = await agent.run(context, conversationHistory);\n spin3.stop();\n\n console.log('\\n' + result.response);\n\n // Update conversation history with the full exchange\n conversationHistory.length = 0;\n conversationHistory.push(...result.messages);\n\n if (result.proposals.length > 0) {\n const { buildModelPath: bmp } = await import('@yamchart/advisor');\n for (const proposal of result.proposals) {\n console.log(\n `\\n${formatProposal(proposal, context.dbt.conventions, bmp)}`,\n );\n }\n await offerApply(context, result.proposals);\n }\n\n console.log('');\n }\n}\n\nasync function offerApply(\n context: AdvisorContext,\n proposals: Proposal[],\n): Promise<void> {\n if (!context.dbt.projectPath) {\n output.warning('No dbt project path configured \\u2014 cannot write files');\n return;\n }\n\n const { confirm } = await import('@inquirer/prompts');\n const { writeDbtModel, buildModelPath, formatModelSql, updateSchemaYml } =\n await import('@yamchart/advisor');\n\n for (const proposal of proposals) {\n console.log('');\n const shouldApply = await confirm({\n message: `Apply ${proposal.name} to dbt project?`,\n default: false,\n });\n\n if (!shouldApply) continue;\n\n const modelPath = buildModelPath(\n proposal.name,\n proposal.layer,\n context.dbt.conventions,\n proposal.subfolder,\n );\n\n const sql = formatModelSql({\n materialization: proposal.materialization,\n sql: proposal.sql,\n });\n\n await writeDbtModel(context.dbt.projectPath, modelPath, sql);\n output.success(`Created ${modelPath}`);\n\n // Update schema.yml\n const schemaDir = modelPath.split('/').slice(0, -1).join('/');\n const schemaPath = `${schemaDir}/schema.yml`;\n\n try {\n await updateSchemaYml(context.dbt.projectPath, schemaPath, {\n name: proposal.name,\n description: proposal.description,\n columns: proposal.columns,\n });\n output.success(`Updated ${schemaPath}`);\n } catch (err) {\n output.warning(\n `Could not update schema.yml: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -4,10 +4,10 @@ import {
4
4
  DashboardSchema,
5
5
  ProjectSchema,
6
6
  ScheduleSchema
7
- } from "./chunk-UDRJFEJU.js";
7
+ } from "./chunk-X6LQGWUX.js";
8
8
  import {
9
9
  parseModelMetadata
10
- } from "./chunk-SSUVEADJ.js";
10
+ } from "./chunk-JYQKDWLG.js";
11
11
 
12
12
  // src/commands/validate.ts
13
13
  import { readFile, readdir, access } from "fs/promises";
@@ -95,51 +95,21 @@ ${formatZodErrors(result.error).map((e) => ` - ${e}`).join("\n")}`
95
95
  } catch {
96
96
  }
97
97
  const chartsDir = join(projectDir, "charts");
98
+ const chartStats = { filesChecked: 0, filesPassed: 0 };
98
99
  try {
99
100
  await access(chartsDir);
100
- const files = await readdir(chartsDir);
101
- for (const file of files) {
102
- if (extname(file) !== ".yaml" && extname(file) !== ".yml") continue;
103
- filesChecked++;
104
- const filePath = join(chartsDir, file);
105
- const content = await readFile(filePath, "utf-8");
106
- const parsed = parseYaml(content);
107
- const result = ChartSchema.safeParse(parsed);
108
- if (result.success) {
109
- config.charts.set(result.data.name, result.data);
110
- filesPassed++;
111
- } else {
112
- errors.push({
113
- file: `charts/${file}`,
114
- message: `Invalid schema:
115
- ${formatZodErrors(result.error).map((e) => ` - ${e}`).join("\n")}`
116
- });
117
- }
118
- }
101
+ await validateYamlDir(chartsDir, chartsDir, "charts", ChartSchema, config.charts, errors, chartStats);
102
+ filesChecked += chartStats.filesChecked;
103
+ filesPassed += chartStats.filesPassed;
119
104
  } catch {
120
105
  }
121
106
  const dashboardsDir = join(projectDir, "dashboards");
107
+ const dashboardStats = { filesChecked: 0, filesPassed: 0 };
122
108
  try {
123
109
  await access(dashboardsDir);
124
- const files = await readdir(dashboardsDir);
125
- for (const file of files) {
126
- if (extname(file) !== ".yaml" && extname(file) !== ".yml") continue;
127
- filesChecked++;
128
- const filePath = join(dashboardsDir, file);
129
- const content = await readFile(filePath, "utf-8");
130
- const parsed = parseYaml(content);
131
- const result = DashboardSchema.safeParse(parsed);
132
- if (result.success) {
133
- config.dashboards.set(result.data.name, result.data);
134
- filesPassed++;
135
- } else {
136
- errors.push({
137
- file: `dashboards/${file}`,
138
- message: `Invalid schema:
139
- ${formatZodErrors(result.error).map((e) => ` - ${e}`).join("\n")}`
140
- });
141
- }
142
- }
110
+ await validateYamlDir(dashboardsDir, dashboardsDir, "dashboards", DashboardSchema, config.dashboards, errors, dashboardStats);
111
+ filesChecked += dashboardStats.filesChecked;
112
+ filesPassed += dashboardStats.filesPassed;
143
113
  } catch {
144
114
  }
145
115
  const schedulesDir = join(projectDir, "schedules");
@@ -206,6 +176,31 @@ async function validateModelsDir(dir, projectDir, config, errors, stats) {
206
176
  }
207
177
  }
208
178
  }
179
+ async function validateYamlDir(dir, baseDir, dirLabel, schema, targetMap, errors, stats) {
180
+ const entries = await readdir(dir, { withFileTypes: true });
181
+ for (const entry of entries) {
182
+ const fullPath = join(dir, entry.name);
183
+ if (entry.isDirectory() && dir === baseDir) {
184
+ await validateYamlDir(fullPath, baseDir, dirLabel, schema, targetMap, errors, stats);
185
+ } else if (entry.isFile() && (extname(entry.name) === ".yaml" || extname(entry.name) === ".yml")) {
186
+ stats.filesChecked++;
187
+ const relPath = relative(baseDir, fullPath);
188
+ const content = await readFile(fullPath, "utf-8");
189
+ const parsed = parseYaml(content);
190
+ const result = schema.safeParse(parsed);
191
+ if (result.success) {
192
+ targetMap.set(result.data.name, result.data);
193
+ stats.filesPassed++;
194
+ } else {
195
+ errors.push({
196
+ file: `${dirLabel}/${relPath}`,
197
+ message: `Invalid schema:
198
+ ${formatZodErrors(result.error).map((e) => ` - ${e}`).join("\n")}`
199
+ });
200
+ }
201
+ }
202
+ }
203
+ }
209
204
  function crossReferenceValidation(config, errors, warnings) {
210
205
  for (const [chartName, chart] of config.charts) {
211
206
  if (chart.source.model && !config.models.has(chart.source.model)) {
@@ -282,7 +277,7 @@ function levenshtein(a, b) {
282
277
  return matrix[b.length][a.length];
283
278
  }
284
279
  async function dryRunValidation(projectDir, config, connectionName, errors) {
285
- const { DuckDBConnector } = await import("./dist-LZNDQDH3.js");
280
+ const { DuckDBConnector } = await import("./dist-PVHFUYP2.js");
286
281
  let passed = 0;
287
282
  let failed = 0;
288
283
  const connName = connectionName || config.project?.defaults?.connection;
@@ -365,4 +360,4 @@ export {
365
360
  findProjectRoot,
366
361
  loadEnvFile
367
362
  };
368
- //# sourceMappingURL=chunk-5VA43CTW.js.map
363
+ //# sourceMappingURL=chunk-E2UZXDF6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/validate.ts","../src/utils/config.ts"],"sourcesContent":["import { readFile, readdir, access } from 'fs/promises';\nimport { join, extname, relative } from 'path';\nimport { parse as parseYaml } from 'yaml';\nimport {\n ProjectSchema,\n ConnectionSchema,\n ChartSchema,\n DashboardSchema,\n ScheduleSchema,\n type Schedule,\n} from '@yamchart/schema';\nimport type { ZodError, ZodType } from 'zod';\nimport { parseModelMetadata } from '@yamchart/query';\n\nfunction formatZodErrors(error: ZodError): string[] {\n return error.errors.map((e) => {\n const path = e.path.length > 0 ? e.path.join('.') : '(root)';\n return `${path}: ${e.message}`;\n });\n}\n\nexport interface ValidationError {\n file: string;\n line?: number;\n message: string;\n suggestion?: string;\n}\n\nexport interface ValidationResult {\n success: boolean;\n errors: ValidationError[];\n warnings: ValidationError[];\n stats: {\n files: number;\n passed: number;\n failed: number;\n };\n dryRunStats?: {\n passed: number;\n failed: number;\n };\n}\n\nexport interface ValidateOptions {\n dryRun: boolean;\n connection?: string;\n}\n\ninterface LoadedConfig {\n project: { name: string; version: string; defaults?: { connection?: string } } | null;\n connections: Map<string, { name: string; type: string }>;\n models: Map<string, { name: string; sql: string }>;\n charts: Map<string, { name: string; source: { model?: string; sql?: string } }>;\n dashboards: Map<string, { name: string; layout: unknown }>;\n schedules: Map<string, Schedule>;\n}\n\nexport async function validateProject(\n projectDir: string,\n options: ValidateOptions\n): Promise<ValidationResult> {\n const errors: ValidationError[] = [];\n const warnings: ValidationError[] = [];\n let filesChecked = 0;\n let filesPassed = 0;\n\n const config: LoadedConfig = {\n project: null,\n connections: new Map(),\n models: new Map(),\n charts: new Map(),\n dashboards: new Map(),\n schedules: new Map(),\n };\n\n // Phase 1: Schema validation\n\n // Validate yamchart.yaml\n const projectPath = join(projectDir, 'yamchart.yaml');\n try {\n await access(projectPath);\n filesChecked++;\n const content = await readFile(projectPath, 'utf-8');\n const parsed = parseYaml(content);\n const result = ProjectSchema.safeParse(parsed);\n\n if (result.success) {\n config.project = result.data;\n filesPassed++;\n } else {\n errors.push({\n file: 'yamchart.yaml',\n message: `Invalid schema:\\n${formatZodErrors(result.error).map(e => ` - ${e}`).join('\\n')}`,\n });\n }\n } catch {\n errors.push({\n file: 'yamchart.yaml',\n message: 'yamchart.yaml not found',\n });\n return {\n success: false,\n errors,\n warnings,\n stats: { files: filesChecked, passed: filesPassed, failed: filesChecked - filesPassed },\n };\n }\n\n // Validate connections\n const connectionsDir = join(projectDir, 'connections');\n try {\n await access(connectionsDir);\n const files = await readdir(connectionsDir);\n\n for (const file of files) {\n if (extname(file) !== '.yaml' && extname(file) !== '.yml') continue;\n filesChecked++;\n\n const filePath = join(connectionsDir, file);\n const content = await readFile(filePath, 'utf-8');\n const parsed = parseYaml(content);\n const result = ConnectionSchema.safeParse(parsed);\n\n if (result.success) {\n config.connections.set(result.data.name, result.data);\n filesPassed++;\n } else {\n errors.push({\n file: `connections/${file}`,\n message: `Invalid schema:\\n${formatZodErrors(result.error).map(e => ` - ${e}`).join('\\n')}`,\n });\n }\n }\n } catch {\n // No connections directory is ok\n }\n\n // Validate models\n const modelsDir = join(projectDir, 'models');\n const modelStats = { filesChecked: 0, filesPassed: 0 };\n try {\n await access(modelsDir);\n await validateModelsDir(modelsDir, projectDir, config, errors, modelStats);\n filesChecked += modelStats.filesChecked;\n filesPassed += modelStats.filesPassed;\n } catch {\n // No models directory is ok\n }\n\n // Validate charts (recurse one level into subdirectories, matching config-loader)\n const chartsDir = join(projectDir, 'charts');\n const chartStats = { filesChecked: 0, filesPassed: 0 };\n try {\n await access(chartsDir);\n await validateYamlDir(chartsDir, chartsDir, 'charts', ChartSchema, config.charts, errors, chartStats);\n filesChecked += chartStats.filesChecked;\n filesPassed += chartStats.filesPassed;\n } catch {\n // No charts directory is ok\n }\n\n // Validate dashboards (recurse one level into subdirectories, matching config-loader)\n const dashboardsDir = join(projectDir, 'dashboards');\n const dashboardStats = { filesChecked: 0, filesPassed: 0 };\n try {\n await access(dashboardsDir);\n await validateYamlDir(dashboardsDir, dashboardsDir, 'dashboards', DashboardSchema, config.dashboards, errors, dashboardStats);\n filesChecked += dashboardStats.filesChecked;\n filesPassed += dashboardStats.filesPassed;\n } catch {\n // No dashboards directory is ok\n }\n\n // Validate schedules\n const schedulesDir = join(projectDir, 'schedules');\n try {\n await access(schedulesDir);\n const files = await readdir(schedulesDir);\n\n for (const file of files) {\n if (extname(file) !== '.yaml' && extname(file) !== '.yml') continue;\n filesChecked++;\n\n const filePath = join(schedulesDir, file);\n const content = await readFile(filePath, 'utf-8');\n const parsed = parseYaml(content);\n const result = ScheduleSchema.safeParse(parsed);\n\n if (result.success) {\n config.schedules.set(result.data.name, result.data);\n filesPassed++;\n } else {\n errors.push({\n file: `schedules/${file}`,\n message: `Invalid schema:\\n${formatZodErrors(result.error).map(e => ` - ${e}`).join('\\n')}`,\n });\n }\n }\n } catch {\n // No schedules directory is ok\n }\n\n // Phase 2: Cross-reference validation\n crossReferenceValidation(config, errors, warnings);\n\n // Phase 3: Dry-run query validation (if enabled)\n let dryRunStats: { passed: number; failed: number } | undefined;\n if (options.dryRun) {\n dryRunStats = await dryRunValidation(projectDir, config, options.connection, errors);\n }\n\n return {\n success: errors.length === 0,\n errors,\n warnings,\n stats: {\n files: filesChecked,\n passed: filesPassed,\n failed: filesChecked - filesPassed,\n },\n dryRunStats,\n };\n}\n\nasync function validateModelsDir(\n dir: string,\n projectDir: string,\n config: LoadedConfig,\n errors: ValidationError[],\n stats: { filesChecked: number; filesPassed: number }\n): Promise<void> {\n const entries = await readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n\n if (entry.isDirectory()) {\n await validateModelsDir(fullPath, projectDir, config, errors, stats);\n } else if (extname(entry.name) === '.sql') {\n stats.filesChecked++;\n const relPath = relative(projectDir, fullPath);\n const content = await readFile(fullPath, 'utf-8');\n\n try {\n const parsed = parseModelMetadata(content);\n config.models.set(parsed.name, { name: parsed.name, sql: parsed.sql });\n stats.filesPassed++;\n } catch (err) {\n errors.push({\n file: relPath,\n message: err instanceof Error ? err.message : 'Failed to parse model',\n });\n }\n }\n }\n}\n\nasync function validateYamlDir(\n dir: string,\n baseDir: string,\n dirLabel: string,\n schema: ZodType,\n targetMap: Map<string, any>,\n errors: ValidationError[],\n stats: { filesChecked: number; filesPassed: number }\n): Promise<void> {\n const entries = await readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n\n if (entry.isDirectory() && dir === baseDir) {\n // Recurse one level deep (matching config-loader behavior)\n await validateYamlDir(fullPath, baseDir, dirLabel, schema, targetMap, errors, stats);\n } else if (entry.isFile() && (extname(entry.name) === '.yaml' || extname(entry.name) === '.yml')) {\n stats.filesChecked++;\n const relPath = relative(baseDir, fullPath);\n\n const content = await readFile(fullPath, 'utf-8');\n const parsed = parseYaml(content);\n const result = schema.safeParse(parsed);\n\n if (result.success) {\n targetMap.set((result.data as any).name, result.data);\n stats.filesPassed++;\n } else {\n errors.push({\n file: `${dirLabel}/${relPath}`,\n message: `Invalid schema:\\n${formatZodErrors(result.error as ZodError).map(e => ` - ${e}`).join('\\n')}`,\n });\n }\n }\n }\n}\n\nfunction crossReferenceValidation(\n config: LoadedConfig,\n errors: ValidationError[],\n warnings: ValidationError[]\n): void {\n // Check that charts reference existing models\n for (const [chartName, chart] of config.charts) {\n if (chart.source.model && !config.models.has(chart.source.model)) {\n const suggestion = findSimilar(chart.source.model, Array.from(config.models.keys()));\n errors.push({\n file: `charts/${chartName}.yaml`,\n message: `Unknown model reference \"${chart.source.model}\"`,\n suggestion: suggestion ? `Did you mean \"${suggestion}\"?` : undefined,\n });\n }\n }\n\n // Check that schedules reference existing charts\n for (const [scheduleName, schedule] of config.schedules) {\n const chartNames = schedule.type === 'report' ? schedule.charts : [schedule.chart];\n for (const chartName of chartNames) {\n if (!config.charts.has(chartName)) {\n const suggestion = findSimilar(chartName, Array.from(config.charts.keys()));\n warnings.push({\n file: `schedules/${scheduleName}.yaml`,\n message: `Unknown chart reference \"${chartName}\"`,\n suggestion: suggestion ? `Did you mean \"${suggestion}\"?` : undefined,\n });\n }\n }\n\n // Validate cron expression (basic: 5 or 6 space-separated fields)\n const cronFields = schedule.schedule.trim().split(/\\s+/);\n if (cronFields.length < 5 || cronFields.length > 6) {\n errors.push({\n file: `schedules/${scheduleName}.yaml`,\n message: `Invalid cron expression \"${schedule.schedule}\" (expected 5-6 fields)`,\n });\n }\n }\n\n // Check that default connection exists\n if (config.project?.defaults?.connection) {\n const connName = config.project.defaults.connection;\n if (!config.connections.has(connName)) {\n const suggestion = findSimilar(connName, Array.from(config.connections.keys()));\n errors.push({\n file: 'yamchart.yaml',\n message: `Default connection \"${connName}\" not found`,\n suggestion: suggestion ? `Did you mean \"${suggestion}\"?` : undefined,\n });\n }\n }\n}\n\nfunction findSimilar(target: string, candidates: string[]): string | null {\n const threshold = 3; // Levenshtein distance threshold\n\n for (const candidate of candidates) {\n if (levenshtein(target.toLowerCase(), candidate.toLowerCase()) <= threshold) {\n return candidate;\n }\n }\n return null;\n}\n\nfunction levenshtein(a: string, b: string): number {\n const matrix: number[][] = [];\n\n for (let i = 0; i <= b.length; i++) {\n matrix[i] = [i];\n }\n for (let j = 0; j <= a.length; j++) {\n matrix[0]![j] = j;\n }\n\n for (let i = 1; i <= b.length; i++) {\n for (let j = 1; j <= a.length; j++) {\n if (b.charAt(i - 1) === a.charAt(j - 1)) {\n matrix[i]![j] = matrix[i - 1]![j - 1]!;\n } else {\n matrix[i]![j] = Math.min(\n matrix[i - 1]![j - 1]! + 1,\n matrix[i]![j - 1]! + 1,\n matrix[i - 1]![j]! + 1\n );\n }\n }\n }\n\n return matrix[b.length]![a.length]!;\n}\n\nasync function dryRunValidation(\n projectDir: string,\n config: LoadedConfig,\n connectionName: string | undefined,\n errors: ValidationError[]\n): Promise<{ passed: number; failed: number }> {\n const { DuckDBConnector } = await import('@yamchart/query');\n\n let passed = 0;\n let failed = 0;\n\n // Determine which connection to use\n const connName = connectionName || config.project?.defaults?.connection;\n if (!connName) {\n errors.push({\n file: 'yamchart.yaml',\n message: 'No connection specified for dry-run (use --connection or set defaults.connection)',\n });\n return { passed, failed: 1 };\n }\n\n const connection = config.connections.get(connName);\n if (!connection) {\n errors.push({\n file: 'yamchart.yaml',\n message: `Connection \"${connName}\" not found`,\n });\n return { passed, failed: 1 };\n }\n\n // Only DuckDB supported for now\n if (connection.type !== 'duckdb') {\n errors.push({\n file: `connections/${connName}.yaml`,\n message: `Dry-run not yet supported for connection type \"${connection.type}\"`,\n });\n return { passed, failed: 1 };\n }\n\n // Load full connection config to get path\n const connPath = join(projectDir, 'connections', `${connName}.yaml`);\n const connContent = await readFile(connPath, 'utf-8');\n const connConfig = parseYaml(connContent) as { config: { path: string } };\n\n // Resolve path relative to project\n const dbPath = connConfig.config.path.startsWith('/')\n ? connConfig.config.path\n : join(projectDir, connConfig.config.path);\n\n const connector = new DuckDBConnector({ path: dbPath });\n\n try {\n await connector.connect();\n\n for (const [modelName, model] of config.models) {\n const result = await connector.explain(model.sql);\n\n if (result.valid) {\n passed++;\n } else {\n failed++;\n errors.push({\n file: `models/${modelName}.sql`,\n message: result.error || 'Query validation failed',\n });\n }\n }\n } finally {\n await connector.disconnect();\n }\n\n return { passed, failed };\n}\n","import { access } from 'fs/promises';\nimport { join, dirname, resolve } from 'path';\nimport { config as loadDotenv } from 'dotenv';\n\n/**\n * Find the project root by looking for yamchart.yaml.\n * Searches current directory and parent directories.\n */\nexport async function findProjectRoot(startDir: string): Promise<string | null> {\n let currentDir = resolve(startDir);\n const root = dirname(currentDir);\n\n while (currentDir !== root) {\n const configPath = join(currentDir, 'yamchart.yaml');\n try {\n await access(configPath);\n return currentDir;\n } catch {\n currentDir = dirname(currentDir);\n }\n }\n\n // Check root directory too\n try {\n await access(join(root, 'yamchart.yaml'));\n return root;\n } catch {\n return null;\n }\n}\n\n/**\n * Load .env file from project directory.\n */\nexport function loadEnvFile(projectDir: string): void {\n loadDotenv({ path: join(projectDir, '.env') });\n}\n\n/**\n * Resolve ${VAR} syntax in a string from environment variables.\n */\nexport function resolveEnvVars(value: string): string {\n return value.replace(/\\$\\{([^}]+)\\}/g, (match, varName) => {\n const envValue = process.env[varName];\n if (envValue === undefined) {\n throw new Error(`Environment variable not found: ${varName}`);\n }\n return envValue;\n });\n}\n\n/**\n * Recursively resolve env vars in an object.\n */\nexport function resolveEnvVarsInObject<T>(obj: T): T {\n if (typeof obj === 'string') {\n return resolveEnvVars(obj) as T;\n }\n if (Array.isArray(obj)) {\n return obj.map(resolveEnvVarsInObject) as T;\n }\n if (obj !== null && typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[key] = resolveEnvVarsInObject(value);\n }\n return result as T;\n }\n return obj;\n}\n"],"mappings":"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","names":["access","join"]}
@@ -135,6 +135,8 @@ function expandCustomDateRange(range) {
135
135
  };
136
136
  }
137
137
  var DATE_PRESETS = [
138
+ "today",
139
+ "yesterday",
138
140
  "last_7_days",
139
141
  "last_30_days",
140
142
  "last_90_days",
@@ -154,26 +156,46 @@ function expandDatePreset(preset) {
154
156
  const now = /* @__PURE__ */ new Date();
155
157
  const today = formatDate(now);
156
158
  switch (preset) {
157
- case "last_7_days":
159
+ case "today":
158
160
  return {
159
- start_date: formatDate(subDays(now, 7)),
161
+ start_date: today,
160
162
  end_date: today
161
163
  };
162
- case "last_30_days":
164
+ case "yesterday": {
165
+ const yesterday = formatDate(subDays(now, 1));
166
+ return {
167
+ start_date: yesterday,
168
+ end_date: yesterday
169
+ };
170
+ }
171
+ case "last_7_days": {
172
+ const yesterday = formatDate(subDays(now, 1));
173
+ return {
174
+ start_date: formatDate(subDays(now, 7)),
175
+ end_date: yesterday
176
+ };
177
+ }
178
+ case "last_30_days": {
179
+ const yesterday = formatDate(subDays(now, 1));
163
180
  return {
164
181
  start_date: formatDate(subDays(now, 30)),
165
- end_date: today
182
+ end_date: yesterday
166
183
  };
167
- case "last_90_days":
184
+ }
185
+ case "last_90_days": {
186
+ const yesterday = formatDate(subDays(now, 1));
168
187
  return {
169
188
  start_date: formatDate(subDays(now, 90)),
170
- end_date: today
189
+ end_date: yesterday
171
190
  };
172
- case "last_12_months":
191
+ }
192
+ case "last_12_months": {
193
+ const yesterday = formatDate(subDays(now, 1));
173
194
  return {
174
195
  start_date: formatDate(subMonths(now, 12)),
175
- end_date: today
196
+ end_date: yesterday
176
197
  };
198
+ }
177
199
  case "year_to_date":
178
200
  return {
179
201
  start_date: formatDate(startOfYear(now)),
@@ -218,6 +240,13 @@ function computePreviousPeriod(startDate, endDate, presetName) {
218
240
  const start = parseISO(startDate);
219
241
  const end = parseISO(endDate);
220
242
  switch (presetName) {
243
+ case "today":
244
+ case "yesterday": {
245
+ return {
246
+ start_date: formatDate(subDays(start, 1)),
247
+ end_date: formatDate(subDays(end, 1))
248
+ };
249
+ }
221
250
  case "last_7_days": {
222
251
  return {
223
252
  start_date: formatDate(subDays(start, 7)),
@@ -357,9 +386,23 @@ var env = new nunjucks.Environment(null, {
357
386
  throwOnUndefined: false
358
387
  // user.x may be undefined for RLS
359
388
  });
389
+ var SqlList = class extends Array {
390
+ toString() {
391
+ return this.map((v) => `'${String(v).replace(/'/g, "''")}'`).join(", ");
392
+ }
393
+ };
360
394
  function createTemplateContext(params, refs = {}, userContext) {
395
+ const processed = {};
396
+ for (const [key, value] of Object.entries(params)) {
397
+ if (Array.isArray(value)) {
398
+ const list = new SqlList(...value);
399
+ processed[key] = list;
400
+ } else {
401
+ processed[key] = value;
402
+ }
403
+ }
361
404
  return {
362
- ...params,
405
+ ...processed,
363
406
  user: userContext ?? {},
364
407
  ref: (name) => {
365
408
  const resolved = refs[name];
@@ -471,7 +514,11 @@ var QueryCompiler = class {
471
514
  if (inputParams.start_date && inputParams.end_date) {
472
515
  return expanded;
473
516
  }
474
- if (isCustomDateRange(params.date_range)) {
517
+ if (isRelativeDateRange(params.date_range)) {
518
+ const dateRange = expandRelativeDateRange(params.date_range);
519
+ expanded.start_date = dateRange.start_date;
520
+ expanded.end_date = dateRange.end_date;
521
+ } else if (isCustomDateRange(params.date_range)) {
475
522
  const dateRange = expandCustomDateRange(params.date_range);
476
523
  expanded.start_date = dateRange.start_date;
477
524
  expanded.end_date = dateRange.end_date;
@@ -604,6 +651,8 @@ var DuckDBConnector = class {
604
651
  for (const [key, value] of Object.entries(row)) {
605
652
  if (typeof value === "bigint") {
606
653
  result[key] = Number(value);
654
+ } else if (value instanceof Date) {
655
+ result[key] = serializeDateValue(value);
607
656
  } else {
608
657
  result[key] = value;
609
658
  }
@@ -772,7 +821,7 @@ var PostgresConnector = class {
772
821
  return Number(value);
773
822
  }
774
823
  if (value instanceof Date) {
775
- return value.toISOString();
824
+ return serializeDateValue(value);
776
825
  }
777
826
  return value;
778
827
  }
@@ -921,7 +970,7 @@ var MySQLConnector = class {
921
970
  return Number(value);
922
971
  }
923
972
  if (value instanceof Date) {
924
- return value.toISOString();
973
+ return serializeDateValue(value);
925
974
  }
926
975
  if (Buffer.isBuffer(value)) {
927
976
  return value.toString("utf-8");
@@ -1204,7 +1253,7 @@ var SnowflakeConnector = class {
1204
1253
  return Number(value);
1205
1254
  }
1206
1255
  if (value instanceof Date) {
1207
- return value.toISOString();
1256
+ return serializeDateValue(value);
1208
1257
  }
1209
1258
  return value;
1210
1259
  }
@@ -1280,6 +1329,17 @@ function resolveSnowflakeAuth(connection) {
1280
1329
  throw new Error(`Auth type "${auth.type}" not yet implemented for Snowflake`);
1281
1330
  }
1282
1331
 
1332
+ // ../../packages/query/dist/connectors/index.js
1333
+ function serializeDateValue(value) {
1334
+ if (value.getUTCHours() === 0 && value.getUTCMinutes() === 0 && value.getUTCSeconds() === 0 && value.getUTCMilliseconds() === 0) {
1335
+ const y = value.getUTCFullYear();
1336
+ const m = String(value.getUTCMonth() + 1).padStart(2, "0");
1337
+ const d = String(value.getUTCDate()).padStart(2, "0");
1338
+ return `${y}-${m}-${d}`;
1339
+ }
1340
+ return value.toISOString();
1341
+ }
1342
+
1283
1343
  // ../../packages/query/dist/test-runner.js
1284
1344
  var TYPE_MAP = {
1285
1345
  date: ["date", "timestamp", "datetime", "timestamptz", "timestamp with time zone"],
@@ -1442,6 +1502,7 @@ export {
1442
1502
  isDatePreset,
1443
1503
  isRelativeDateRange,
1444
1504
  expandRelativeDateRange,
1505
+ SqlList,
1445
1506
  createTemplateContext,
1446
1507
  renderTemplate,
1447
1508
  templateHasVariable,
@@ -1455,6 +1516,7 @@ export {
1455
1516
  resolvePostgresAuth,
1456
1517
  resolveMySQLAuth,
1457
1518
  resolveSnowflakeAuth,
1519
+ serializeDateValue,
1458
1520
  expandThis,
1459
1521
  runAssertion,
1460
1522
  runModel,
@@ -1462,4 +1524,4 @@ export {
1462
1524
  checkSchema,
1463
1525
  VERSION
1464
1526
  };
1465
- //# sourceMappingURL=chunk-SSUVEADJ.js.map
1527
+ //# sourceMappingURL=chunk-JYQKDWLG.js.map