yamchart 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,184 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
3
+ // src/commands/lineage.ts
4
+ import { readFile, readdir, access } from "fs/promises";
5
+ import { join, extname } from "path";
6
+ function resolveNodeKey(key) {
7
+ const parts = key.split(".");
8
+ if (parts[0] === "source" && parts.length >= 4) {
9
+ return parts.slice(2).join(".");
10
+ }
11
+ return parts[parts.length - 1];
12
+ }
13
+ function extractSqlDependencies(sql) {
14
+ const refs = [];
15
+ const sources = [];
16
+ const tables = [];
17
+ const refRegex = /\{\{\s*ref\(['"]([^'"]+)['"]\)\s*\}\}/g;
18
+ let match;
19
+ while ((match = refRegex.exec(sql)) !== null) {
20
+ if (match[1]) refs.push(match[1]);
21
+ }
22
+ const sourceRegex = /--\s*@source:\s*(.+)/g;
23
+ while ((match = sourceRegex.exec(sql)) !== null) {
24
+ if (match[1]) sources.push(match[1].trim());
25
+ }
26
+ const cleaned = sql.replace(/--.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\{\{[\s\S]*?\}\}/g, "").replace(/\{%[\s\S]*?%\}/g, "");
27
+ const tableRegex = /\b(?:FROM|JOIN)\s+([\w]+(?:\.[\w]+){1,2})/gi;
28
+ while ((match = tableRegex.exec(cleaned)) !== null) {
29
+ if (match[1]) tables.push(match[1]);
30
+ }
31
+ return { refs, sources, tables };
32
+ }
33
+ function resolveDependencies(deps, catalogModels) {
34
+ const resolved = /* @__PURE__ */ new Set();
35
+ const byName = /* @__PURE__ */ new Map();
36
+ const byTable = /* @__PURE__ */ new Map();
37
+ for (const model of catalogModels) {
38
+ byName.set(model.name.toLowerCase(), model.name);
39
+ if (model.table) {
40
+ byTable.set(model.table.toUpperCase(), model.name);
41
+ }
42
+ }
43
+ for (const ref of deps.refs) {
44
+ const nameMatch = byName.get(ref.toLowerCase());
45
+ if (nameMatch) resolved.add(nameMatch);
46
+ }
47
+ for (const source of deps.sources) {
48
+ const nameMatch = byName.get(source.toLowerCase());
49
+ if (nameMatch) resolved.add(nameMatch);
50
+ }
51
+ for (const table of deps.tables) {
52
+ const upper = table.toUpperCase();
53
+ const exactMatch = byTable.get(upper);
54
+ if (exactMatch) {
55
+ resolved.add(exactMatch);
56
+ continue;
57
+ }
58
+ const parts = table.split(".");
59
+ const lastPart = parts[parts.length - 1];
60
+ const nameMatch = byName.get(lastPart.toLowerCase());
61
+ if (nameMatch) {
62
+ resolved.add(nameMatch);
63
+ continue;
64
+ }
65
+ for (const [tablePath, modelName] of byTable) {
66
+ if (tablePath.endsWith("." + upper)) {
67
+ resolved.add(modelName);
68
+ break;
69
+ }
70
+ }
71
+ }
72
+ return Array.from(resolved);
73
+ }
74
+ function buildLineageTree(modelName, models, options) {
75
+ const modelMap = /* @__PURE__ */ new Map();
76
+ for (const model of models) {
77
+ modelMap.set(model.name, model);
78
+ }
79
+ function walk(name, visited, depth) {
80
+ if (visited.has(name)) {
81
+ return null;
82
+ }
83
+ const entry = modelMap.get(name);
84
+ const node = {
85
+ name,
86
+ children: []
87
+ };
88
+ if (entry?.source === "dbt-source") {
89
+ node.isSource = true;
90
+ }
91
+ if (!entry) {
92
+ return node;
93
+ }
94
+ if (options?.maxDepth !== void 0 && depth >= options.maxDepth) {
95
+ return node;
96
+ }
97
+ visited.add(name);
98
+ if (entry.dependsOn) {
99
+ for (const dep of entry.dependsOn) {
100
+ const resolvedName = resolveNodeKey(dep);
101
+ const child = walk(resolvedName, visited, depth + 1);
102
+ if (child) {
103
+ node.children.push(child);
104
+ }
105
+ }
106
+ }
107
+ visited.delete(name);
108
+ return node;
109
+ }
110
+ return walk(modelName, /* @__PURE__ */ new Set(), 0);
111
+ }
112
+ function renderLineageTree(node, indent = 0) {
113
+ const lines = [];
114
+ if (indent === 0) {
115
+ lines.push(node.name);
116
+ } else {
117
+ const padding = " ".repeat(indent);
118
+ const label = node.isSource ? `source: ${node.name}` : node.name;
119
+ lines.push(`${padding}\u2190 ${label}`);
120
+ }
121
+ for (const child of node.children) {
122
+ lines.push(renderLineageTree(child, indent + 1));
123
+ }
124
+ return lines.join("\n");
125
+ }
126
+ async function findSqlFiles(dir) {
127
+ const files = [];
128
+ const entries = await readdir(dir, { withFileTypes: true });
129
+ for (const entry of entries) {
130
+ const fullPath = join(dir, entry.name);
131
+ if (entry.isDirectory()) {
132
+ files.push(...await findSqlFiles(fullPath));
133
+ } else if (extname(entry.name) === ".sql") {
134
+ files.push(fullPath);
135
+ }
136
+ }
137
+ return files;
138
+ }
139
+ async function scanYamchartModelDeps(projectDir, catalogModels) {
140
+ const modelsDir = join(projectDir, "models");
141
+ const entries = [];
142
+ const existingNames = new Set(catalogModels.map((m) => m.name));
143
+ try {
144
+ await access(modelsDir);
145
+ } catch {
146
+ return [];
147
+ }
148
+ const sqlFiles = await findSqlFiles(modelsDir);
149
+ for (const filePath of sqlFiles) {
150
+ const sql = await readFile(filePath, "utf-8");
151
+ const nameMatch = sql.match(/--\s*@name:\s*(.+)/);
152
+ if (!nameMatch?.[1]) continue;
153
+ const name = nameMatch[1].trim();
154
+ if (existingNames.has(name)) continue;
155
+ const deps = extractSqlDependencies(sql);
156
+ const resolved = resolveDependencies(deps, catalogModels);
157
+ entries.push({
158
+ name,
159
+ ...resolved.length > 0 ? { dependsOn: resolved } : {}
160
+ });
161
+ }
162
+ return entries;
163
+ }
164
+ async function getLineage(projectDir, modelName, options) {
165
+ const catalogPath = join(projectDir, ".yamchart", "catalog.json");
166
+ const content = await readFile(catalogPath, "utf-8");
167
+ const catalog = JSON.parse(content);
168
+ const models = catalog.models || [];
169
+ const yamchartEntries = await scanYamchartModelDeps(projectDir, models);
170
+ const allModels = [...models, ...yamchartEntries];
171
+ const tree = buildLineageTree(modelName, allModels, {
172
+ maxDepth: options?.depth
173
+ });
174
+ const rendered = renderLineageTree(tree);
175
+ return { tree, rendered };
176
+ }
177
+ export {
178
+ buildLineageTree,
179
+ extractSqlDependencies,
180
+ getLineage,
181
+ renderLineageTree,
182
+ resolveDependencies
183
+ };
184
+ //# sourceMappingURL=lineage-XSITWW2O.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/lineage.ts"],"sourcesContent":["import { readFile, readdir, access } from 'fs/promises';\nimport { join, extname } from 'path';\n\nexport interface LineageNode {\n name: string;\n isSource?: boolean;\n children: LineageNode[];\n}\n\nexport interface CatalogEntry {\n name: string;\n table?: string;\n source?: string;\n dependsOn?: string[];\n}\n\n/**\n * Convert a dbt node key to a model name.\n *\n * Examples:\n * model.project.stg_orders → stg_orders\n * source.project.raw.orders → raw.orders\n */\nfunction resolveNodeKey(key: string): string {\n const parts = key.split('.');\n if (parts[0] === 'source' && parts.length >= 4) {\n // source.project.schema.table → schema.table\n return parts.slice(2).join('.');\n }\n // model.project.name → name\n return parts[parts.length - 1];\n}\n\n/**\n * Extract all dependencies from yamchart model SQL.\n * Finds ref() calls, @source annotations, and FROM/JOIN table references.\n */\nexport function extractSqlDependencies(sql: string): {\n refs: string[];\n sources: string[];\n tables: string[];\n} {\n const refs: string[] = [];\n const sources: string[] = [];\n const tables: string[] = [];\n\n // Extract {{ ref('name') }} calls\n const refRegex = /\\{\\{\\s*ref\\(['\"]([^'\"]+)['\"]\\)\\s*\\}\\}/g;\n let match;\n while ((match = refRegex.exec(sql)) !== null) {\n if (match[1]) refs.push(match[1]);\n }\n\n // Extract all @source: annotations\n const sourceRegex = /--\\s*@source:\\s*(.+)/g;\n while ((match = sourceRegex.exec(sql)) !== null) {\n if (match[1]) sources.push(match[1].trim());\n }\n\n // Remove comments and Jinja for table extraction\n const cleaned = sql\n .replace(/--.*$/gm, '')\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n .replace(/\\{\\{[\\s\\S]*?\\}\\}/g, '')\n .replace(/\\{%[\\s\\S]*?%\\}/g, '');\n\n // Extract FROM and JOIN table references\n const tableRegex = /\\b(?:FROM|JOIN)\\s+([\\w]+(?:\\.[\\w]+){1,2})/gi;\n while ((match = tableRegex.exec(cleaned)) !== null) {\n if (match[1]) tables.push(match[1]);\n }\n\n return { refs, sources, tables };\n}\n\n/**\n * Resolve extracted SQL dependencies against catalog entries.\n * Returns an array of catalog model names that were matched.\n */\nexport function resolveDependencies(\n deps: { refs: string[]; sources: string[]; tables: string[] },\n catalogModels: CatalogEntry[],\n): string[] {\n const resolved = new Set<string>();\n\n // Build lookup maps\n const byName = new Map<string, string>();\n const byTable = new Map<string, string>();\n\n for (const model of catalogModels) {\n byName.set(model.name.toLowerCase(), model.name);\n if (model.table) {\n byTable.set(model.table.toUpperCase(), model.name);\n }\n }\n\n // Resolve ref() calls by name\n for (const ref of deps.refs) {\n const nameMatch = byName.get(ref.toLowerCase());\n if (nameMatch) resolved.add(nameMatch);\n }\n\n // Resolve @source by name\n for (const source of deps.sources) {\n const nameMatch = byName.get(source.toLowerCase());\n if (nameMatch) resolved.add(nameMatch);\n }\n\n // Resolve FROM/JOIN tables\n for (const table of deps.tables) {\n // Try exact match on table path (case-insensitive)\n const upper = table.toUpperCase();\n const exactMatch = byTable.get(upper);\n if (exactMatch) {\n resolved.add(exactMatch);\n continue;\n }\n\n // Try matching by name (last segment)\n const parts = table.split('.');\n const lastPart = parts[parts.length - 1]!;\n const nameMatch = byName.get(lastPart.toLowerCase());\n if (nameMatch) {\n resolved.add(nameMatch);\n continue;\n }\n\n // Try suffix match on table paths (for partial qualification like SCHEMA.TABLE)\n for (const [tablePath, modelName] of byTable) {\n if (tablePath.endsWith('.' + upper)) {\n resolved.add(modelName);\n break;\n }\n }\n }\n\n return Array.from(resolved);\n}\n\n/**\n * Build a tree of upstream dependencies from catalog data.\n * Walks `dependsOn` recursively with cycle detection.\n */\nexport function buildLineageTree(\n modelName: string,\n models: CatalogEntry[],\n options?: { maxDepth?: number },\n): LineageNode {\n const modelMap = new Map<string, CatalogEntry>();\n for (const model of models) {\n modelMap.set(model.name, model);\n }\n\n function walk(name: string, visited: Set<string>, depth: number): LineageNode | null {\n // Cycle detection: if already on the current path, skip entirely\n if (visited.has(name)) {\n return null;\n }\n\n const entry = modelMap.get(name);\n const node: LineageNode = {\n name,\n children: [],\n };\n\n if (entry?.source === 'dbt-source') {\n node.isSource = true;\n }\n\n // Unknown model or depth limit reached: return node with no children\n if (!entry) {\n return node;\n }\n\n if (options?.maxDepth !== undefined && depth >= options.maxDepth) {\n return node;\n }\n\n visited.add(name);\n\n if (entry.dependsOn) {\n for (const dep of entry.dependsOn) {\n const resolvedName = resolveNodeKey(dep);\n const child = walk(resolvedName, visited, depth + 1);\n if (child) {\n node.children.push(child);\n }\n }\n }\n\n // Remove from visited so the same node can appear in other branches\n // (cycle detection is path-based, not global)\n visited.delete(name);\n\n return node;\n }\n\n // Root call never returns null (visited set is empty)\n return walk(modelName, new Set<string>(), 0)!;\n}\n\n/**\n * Render a lineage tree as indented text for terminal output.\n *\n * Format:\n * fct_revenue\n * ← stg_orders\n * ← source: raw.orders\n * ← stg_payments\n * ← source: raw.payments\n */\nexport function renderLineageTree(node: LineageNode, indent: number = 0): string {\n const lines: string[] = [];\n\n if (indent === 0) {\n // Root node: just the name\n lines.push(node.name);\n } else {\n const padding = ' '.repeat(indent);\n const label = node.isSource ? `source: ${node.name}` : node.name;\n lines.push(`${padding}\\u2190 ${label}`);\n }\n\n for (const child of node.children) {\n lines.push(renderLineageTree(child, indent + 1));\n }\n\n return lines.join('\\n');\n}\n\ninterface CatalogFile {\n models?: CatalogEntry[];\n}\n\n/**\n * Recursively find all .sql files in a directory.\n */\nasync function findSqlFiles(dir: string): Promise<string[]> {\n const files: string[] = [];\n const entries = await readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n files.push(...await findSqlFiles(fullPath));\n } else if (extname(entry.name) === '.sql') {\n files.push(fullPath);\n }\n }\n\n return files;\n}\n\n/**\n * Scan yamchart model .sql files and build catalog entries with resolved dependencies.\n * Only creates entries for models not already in the catalog.\n */\nasync function scanYamchartModelDeps(\n projectDir: string,\n catalogModels: CatalogEntry[],\n): Promise<CatalogEntry[]> {\n const modelsDir = join(projectDir, 'models');\n const entries: CatalogEntry[] = [];\n const existingNames = new Set(catalogModels.map(m => m.name));\n\n try {\n await access(modelsDir);\n } catch {\n return [];\n }\n\n const sqlFiles = await findSqlFiles(modelsDir);\n\n for (const filePath of sqlFiles) {\n const sql = await readFile(filePath, 'utf-8');\n\n // Extract model name from @name annotation\n const nameMatch = sql.match(/--\\s*@name:\\s*(.+)/);\n if (!nameMatch?.[1]) continue;\n\n const name = nameMatch[1].trim();\n\n // Skip if already in catalog (dbt/warehouse entry takes precedence)\n if (existingNames.has(name)) continue;\n\n // Extract and resolve dependencies\n const deps = extractSqlDependencies(sql);\n const resolved = resolveDependencies(deps, catalogModels);\n\n entries.push({\n name,\n ...(resolved.length > 0 ? { dependsOn: resolved } : {}),\n });\n }\n\n return entries;\n}\n\n/**\n * Load catalog.json and run lineage for a model.\n * Scans yamchart model SQL files to resolve dependencies not in the catalog.\n */\nexport async function getLineage(\n projectDir: string,\n modelName: string,\n options?: { depth?: number; json?: boolean },\n): Promise<{ tree: LineageNode; rendered: string }> {\n const catalogPath = join(projectDir, '.yamchart', 'catalog.json');\n const content = await readFile(catalogPath, 'utf-8');\n const catalog: CatalogFile = JSON.parse(content);\n const models = catalog.models || [];\n\n // Scan yamchart model SQL files for dependencies not in the catalog\n const yamchartEntries = await scanYamchartModelDeps(projectDir, models);\n const allModels = [...models, ...yamchartEntries];\n\n const tree = buildLineageTree(modelName, allModels, {\n maxDepth: options?.depth,\n });\n\n const rendered = renderLineageTree(tree);\n\n return { tree, rendered };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,12 @@
1
+ import {
2
+ detectDatabaseMismatch,
3
+ extractDatabase,
4
+ rewriteTableDatabase
5
+ } from "./chunk-IVD4OP3K.js";
6
+ import "./chunk-DGUM43GV.js";
7
+ export {
8
+ detectDatabaseMismatch,
9
+ extractDatabase,
10
+ rewriteTableDatabase
11
+ };
12
+ //# sourceMappingURL=rewrite-database-FDJIXKZ2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -2,6 +2,9 @@ import {
2
2
  generateCatalogJson,
3
3
  generateCatalogMd
4
4
  } from "./chunk-WYS4ULBE.js";
5
+ import {
6
+ rewriteTableDatabase
7
+ } from "./chunk-IVD4OP3K.js";
5
8
  import "./chunk-DGUM43GV.js";
6
9
 
7
10
  // src/commands/sync-dbt.ts
@@ -442,7 +445,8 @@ async function syncDbt(projectDir, options) {
442
445
  branch: savedConfig.branch,
443
446
  include: savedConfig.filters.include,
444
447
  exclude: savedConfig.filters.exclude,
445
- tags: savedConfig.filters.tags
448
+ tags: savedConfig.filters.tags,
449
+ targetDatabase: options.targetDatabase || savedConfig.targetDatabase
446
450
  };
447
451
  }
448
452
  if (effectiveOptions.source !== "local") {
@@ -491,6 +495,12 @@ async function syncDbt(projectDir, options) {
491
495
  yamchartModels: referencingModels
492
496
  };
493
497
  });
498
+ const targetDatabase = effectiveOptions.targetDatabase;
499
+ if (targetDatabase) {
500
+ for (const model of catalogModels) {
501
+ model.table = rewriteTableDatabase(model.table, targetDatabase);
502
+ }
503
+ }
494
504
  const catalogData = {
495
505
  syncedAt: (/* @__PURE__ */ new Date()).toISOString(),
496
506
  source: {
@@ -513,6 +523,7 @@ async function syncDbt(projectDir, options) {
513
523
  path: effectiveOptions.path,
514
524
  repo: effectiveOptions.repo,
515
525
  branch: effectiveOptions.branch,
526
+ targetDatabase: effectiveOptions.targetDatabase,
516
527
  lastSync: catalogData.syncedAt,
517
528
  filters: {
518
529
  include: effectiveOptions.include,
@@ -526,11 +537,23 @@ async function syncDbt(projectDir, options) {
526
537
  };
527
538
  const configYaml = stringifyYaml(syncConfig);
528
539
  await writeFile(join4(yamchartDir, "dbt-source.yaml"), configYaml, "utf-8");
540
+ const warnings = [];
541
+ const hasLineage = fullModels.some((m) => m.dependsOn && m.dependsOn.length > 0);
542
+ if (!hasLineage && fullModels.length > 0) {
543
+ try {
544
+ await access3(join4(effectiveOptions.path, "target", "manifest.json"));
545
+ } catch {
546
+ warnings.push(
547
+ "No lineage data: target/manifest.json not found in dbt project. Run `dbt compile` to generate it, then re-sync."
548
+ );
549
+ }
550
+ }
529
551
  return {
530
552
  success: true,
531
553
  modelsIncluded: filteredModels.length,
532
554
  modelsExcluded,
533
- catalogPath
555
+ catalogPath,
556
+ ...warnings.length > 0 ? { warnings } : {}
534
557
  };
535
558
  } catch (err) {
536
559
  return {
@@ -546,4 +569,4 @@ export {
546
569
  loadSyncConfig,
547
570
  syncDbt
548
571
  };
549
- //# sourceMappingURL=sync-dbt-RKEHTN4P.js.map
572
+ //# sourceMappingURL=sync-dbt-22QQKT3A.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/sync-dbt.ts","../src/dbt/local-source.ts","../src/dbt/parser.ts","../src/dbt/scanner.ts"],"sourcesContent":["import { mkdir, writeFile, readFile, access } from 'fs/promises';\nimport { join } from 'path';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport { LocalDbtSource } from '../dbt/local-source.js';\nimport { scanYamchartModels } from '../dbt/scanner.js';\nimport { generateCatalogMd, generateCatalogJson, type CatalogData, type CatalogModel } from '../dbt/catalog.js';\nimport { rewriteTableDatabase } from '../dbt/rewrite-database.js';\nimport type { DbtSourceConfig } from '../dbt/types.js';\n\nexport interface SyncDbtOptions {\n source: 'local' | 'github' | 'dbt-cloud';\n path?: string;\n repo?: string;\n branch?: string;\n include: string[];\n exclude: string[];\n tags: string[];\n refresh?: boolean;\n targetDatabase?: string;\n}\n\nexport interface SyncDbtResult {\n success: boolean;\n modelsIncluded: number;\n modelsExcluded: number;\n catalogPath: string;\n error?: string;\n warnings?: string[];\n}\n\n/**\n * Load saved sync config from .yamchart/dbt-source.yaml\n */\nexport async function loadSyncConfig(projectDir: string): Promise<DbtSourceConfig | null> {\n const configPath = join(projectDir, '.yamchart', 'dbt-source.yaml');\n\n try {\n await access(configPath);\n const content = await readFile(configPath, 'utf-8');\n return parseYaml(content) as DbtSourceConfig;\n } catch {\n return null;\n }\n}\n\n/**\n * Sync dbt project metadata to yamchart catalog.\n *\n * This function:\n * 1. Ensures .yamchart directory exists\n * 2. Handles refresh mode (load saved config)\n * 3. Validates source type (only 'local' supported for v1)\n * 4. Creates LocalDbtSource, lists models, applies filters\n * 5. Uses smart defaults if no filters specified\n * 6. Scans yamchart models for cross-references\n * 7. Generates catalog.md and catalog.json\n * 8. Saves sync config to dbt-source.yaml\n */\nexport async function syncDbt(\n projectDir: string,\n options: SyncDbtOptions\n): Promise<SyncDbtResult> {\n const yamchartDir = join(projectDir, '.yamchart');\n const catalogPath = join(yamchartDir, 'catalog.md');\n\n try {\n // Step 1: Ensure .yamchart directory exists\n await mkdir(yamchartDir, { recursive: true });\n\n // Step 2: Handle refresh mode - load saved config\n let effectiveOptions = { ...options };\n if (options.refresh) {\n const savedConfig = await loadSyncConfig(projectDir);\n if (!savedConfig) {\n return {\n success: false,\n modelsIncluded: 0,\n modelsExcluded: 0,\n catalogPath,\n error: 'No saved sync config found. Run sync-dbt with --path first.',\n };\n }\n\n effectiveOptions = {\n source: savedConfig.source,\n path: savedConfig.path,\n repo: savedConfig.repo,\n branch: savedConfig.branch,\n include: savedConfig.filters.include,\n exclude: savedConfig.filters.exclude,\n tags: savedConfig.filters.tags,\n targetDatabase: options.targetDatabase || savedConfig.targetDatabase,\n };\n }\n\n // Step 3: Validate source type (only 'local' supported for v1)\n if (effectiveOptions.source !== 'local') {\n return {\n success: false,\n modelsIncluded: 0,\n modelsExcluded: 0,\n catalogPath,\n error: `Source type \"${effectiveOptions.source}\" not yet supported. Only \"local\" is available.`,\n };\n }\n\n if (!effectiveOptions.path) {\n return {\n success: false,\n modelsIncluded: 0,\n modelsExcluded: 0,\n catalogPath,\n error: 'Path to dbt project is required for local source.',\n };\n }\n\n // Step 4: Create LocalDbtSource, list models\n const dbtSource = new LocalDbtSource(effectiveOptions.path);\n const allModels = await dbtSource.listModels();\n\n // Step 5: Apply filters or use smart defaults\n let hasFilters =\n effectiveOptions.include.length > 0 ||\n effectiveOptions.exclude.length > 0 ||\n effectiveOptions.tags.length > 0;\n\n let filteredModels;\n if (hasFilters) {\n filteredModels = LocalDbtSource.filterModels(allModels, {\n include: effectiveOptions.include,\n exclude: effectiveOptions.exclude,\n tags: effectiveOptions.tags,\n });\n } else {\n // Use smart defaults - prefer marts/reporting, exclude staging/intermediate\n const defaults = LocalDbtSource.getDefaultFilters();\n const withDefaults = LocalDbtSource.filterModels(allModels, defaults);\n\n // If defaults filter out everything, include all models\n filteredModels = withDefaults.length > 0 ? withDefaults : allModels;\n }\n\n const modelsExcluded = allModels.length - filteredModels.length;\n\n // Get full model details for included models\n const modelNames = filteredModels.map(m => m.name);\n const fullModels = await dbtSource.getModels(modelNames);\n\n // Step 6: Scan yamchart models for cross-references\n const yamchartModels = await scanYamchartModels(projectDir);\n\n // Build catalog models with cross-references\n const catalogModels: CatalogModel[] = fullModels.map(model => {\n // Find yamchart models that reference this dbt model\n const referencingModels = yamchartModels.filter(ym =>\n ym.source === model.name || ym.source === model.table\n );\n\n return {\n ...model,\n yamchartModels: referencingModels,\n };\n });\n\n // Rewrite database in table paths if targetDatabase is set\n const targetDatabase = effectiveOptions.targetDatabase;\n if (targetDatabase) {\n for (const model of catalogModels) {\n model.table = rewriteTableDatabase(model.table, targetDatabase);\n }\n }\n\n // Step 7: Generate catalog files\n const catalogData: CatalogData = {\n syncedAt: new Date().toISOString(),\n source: {\n type: effectiveOptions.source,\n path: effectiveOptions.path,\n repo: effectiveOptions.repo,\n },\n stats: {\n modelsIncluded: filteredModels.length,\n modelsExcluded,\n },\n models: catalogModels,\n };\n\n const catalogMd = generateCatalogMd(catalogData);\n const catalogJson = generateCatalogJson(catalogData);\n\n await writeFile(catalogPath, catalogMd, 'utf-8');\n await writeFile(join(yamchartDir, 'catalog.json'), catalogJson, 'utf-8');\n\n // Step 8: Save sync config for re-sync\n const syncConfig: DbtSourceConfig = {\n source: effectiveOptions.source,\n path: effectiveOptions.path,\n repo: effectiveOptions.repo,\n branch: effectiveOptions.branch,\n targetDatabase: effectiveOptions.targetDatabase,\n lastSync: catalogData.syncedAt,\n filters: {\n include: effectiveOptions.include,\n exclude: effectiveOptions.exclude,\n tags: effectiveOptions.tags,\n },\n stats: {\n modelsIncluded: filteredModels.length,\n modelsExcluded,\n },\n };\n\n const configYaml = stringifyYaml(syncConfig);\n await writeFile(join(yamchartDir, 'dbt-source.yaml'), configYaml, 'utf-8');\n\n // Check if lineage data was populated\n const warnings: string[] = [];\n const hasLineage = fullModels.some(m => m.dependsOn && m.dependsOn.length > 0);\n if (!hasLineage && fullModels.length > 0) {\n try {\n await access(join(effectiveOptions.path!, 'target', 'manifest.json'));\n // Manifest exists but no deps — models may have no inter-model dependencies\n } catch {\n warnings.push(\n 'No lineage data: target/manifest.json not found in dbt project. ' +\n 'Run `dbt compile` to generate it, then re-sync.'\n );\n }\n }\n\n return {\n success: true,\n modelsIncluded: filteredModels.length,\n modelsExcluded,\n catalogPath,\n ...(warnings.length > 0 ? { warnings } : {}),\n };\n } catch (err) {\n return {\n success: false,\n modelsIncluded: 0,\n modelsExcluded: 0,\n catalogPath,\n error: err instanceof Error ? err.message : 'Unknown error during sync',\n };\n }\n}\n","import { readFile, access } from 'fs/promises';\nimport { join } from 'path';\nimport fg from 'fast-glob';\nimport { minimatch } from 'minimatch';\nimport { parseSchemaYml, parseProjectYml } from './parser.js';\nimport type {\n DbtSource,\n DbtProjectConfig,\n DbtModel,\n DbtModelSummary,\n} from './types.js';\n\nexport interface ModelFilters {\n include?: string[];\n exclude?: string[];\n tags?: string[];\n}\n\n/**\n * LocalDbtSource reads dbt project metadata from a local filesystem.\n * It parses schema.yml files to extract model definitions, columns, and hints.\n */\nexport class LocalDbtSource implements DbtSource {\n readonly type = 'local' as const;\n private projectPath: string;\n private modelsCache: Map<string, DbtModel> | null = null;\n private configCache: DbtProjectConfig | null = null;\n\n constructor(projectPath: string) {\n this.projectPath = projectPath;\n }\n\n /**\n * Read and parse dbt_project.yml\n */\n async getProjectConfig(): Promise<DbtProjectConfig> {\n if (this.configCache) {\n return this.configCache;\n }\n\n const configPath = join(this.projectPath, 'dbt_project.yml');\n const content = await readFile(configPath, 'utf-8');\n const parsed = parseProjectYml(content);\n\n this.configCache = {\n name: parsed.name,\n version: parsed.version,\n profile: parsed.profile,\n model_paths: parsed.modelPaths,\n target_path: 'target',\n vars: parsed.vars,\n };\n\n return this.configCache;\n }\n\n /**\n * Find all schema.yml files and parse models from them.\n * Returns summaries for model selection UI.\n */\n async listModels(): Promise<DbtModelSummary[]> {\n await this.loadModels();\n\n const summaries: DbtModelSummary[] = [];\n for (const model of this.modelsCache!.values()) {\n summaries.push({\n name: model.name,\n path: model.path,\n description: model.description || 'No description',\n tags: model.tags || [],\n });\n }\n\n return summaries;\n }\n\n /**\n * Get full model details by name.\n * @throws Error if model not found\n */\n async getModel(name: string): Promise<DbtModel> {\n await this.loadModels();\n\n const model = this.modelsCache!.get(name);\n if (!model) {\n throw new Error(`Model not found: ${name}`);\n }\n\n return model;\n }\n\n /**\n * Get multiple models by name.\n * @throws Error if any model not found\n */\n async getModels(names: string[]): Promise<DbtModel[]> {\n if (names.length === 0) {\n return [];\n }\n\n const models: DbtModel[] = [];\n for (const name of names) {\n models.push(await this.getModel(name));\n }\n\n return models;\n }\n\n /**\n * Load all models from schema.yml files into cache.\n */\n private async loadModels(): Promise<void> {\n if (this.modelsCache) {\n return;\n }\n\n const config = await this.getProjectConfig();\n this.modelsCache = new Map();\n\n // Find all schema.yml files in model paths\n for (const modelPath of config.model_paths) {\n const pattern = join(this.projectPath, modelPath, '**/*.yml');\n const files = await fg(pattern, {\n ignore: ['**/node_modules/**'],\n });\n\n for (const file of files) {\n const content = await readFile(file, 'utf-8');\n // Get relative path from project root\n const relativePath = file.slice(this.projectPath.length + 1);\n const models = parseSchemaYml(content, relativePath);\n\n for (const model of models) {\n this.modelsCache.set(model.name, model);\n }\n }\n }\n\n // Merge table paths from target/manifest.json (always present after dbt run)\n await this.mergeManifestPaths();\n // Merge column types from target/catalog.json if available (requires dbt docs generate)\n await this.mergeCatalogTypes();\n }\n\n /**\n * Read dbt's target/manifest.json and extract fully-qualified table paths.\n * manifest.json is always present after dbt run/build (unlike catalog.json\n * which requires dbt docs generate).\n */\n private async mergeManifestPaths(): Promise<void> {\n const manifestPath = join(this.projectPath, 'target', 'manifest.json');\n\n try {\n await access(manifestPath);\n } catch {\n return;\n }\n\n try {\n const content = await readFile(manifestPath, 'utf-8');\n const manifest = JSON.parse(content) as {\n nodes?: Record<string, {\n relation_name?: string;\n database?: string;\n schema?: string;\n alias?: string;\n name?: string;\n depends_on?: { nodes?: string[] };\n }>;\n sources?: Record<string, {\n database?: string;\n schema?: string;\n name?: string;\n identifier?: string;\n description?: string;\n columns?: Record<string, { name?: string; type?: string; description?: string }>;\n source_name?: string;\n }>;\n };\n\n if (!manifest.nodes) return;\n\n for (const [nodeKey, node] of Object.entries(manifest.nodes)) {\n const parts = nodeKey.split('.');\n const modelName = parts[parts.length - 1];\n\n const model = this.modelsCache!.get(modelName);\n if (!model || model.table) continue;\n\n // Prefer relation_name (e.g. '\"PROD\".\"ANALYTICS\".\"DIM_CUSTOMERS\"')\n if (node.relation_name) {\n // Strip quotes: \"PROD\".\"ANALYTICS\".\"DIM_CUSTOMERS\" → PROD.ANALYTICS.DIM_CUSTOMERS\n model.table = node.relation_name.replace(/\"/g, '');\n continue;\n }\n\n // Fall back to database/schema/alias fields\n const tableName = node.alias || node.name;\n if (node.schema && tableName) {\n model.table = node.database\n ? `${node.database}.${node.schema}.${tableName}`\n : `${node.schema}.${tableName}`;\n }\n }\n\n // Extract depends_on for each model\n for (const [nodeKey, node] of Object.entries(manifest.nodes)) {\n const parts = nodeKey.split('.');\n const modelName = parts[parts.length - 1];\n const model = this.modelsCache!.get(modelName);\n if (!model || !node.depends_on?.nodes?.length) continue;\n model.dependsOn = node.depends_on.nodes;\n }\n\n // Extract dbt sources from manifest.json sources section\n if (manifest.sources) {\n for (const [sourceKey, source] of Object.entries(manifest.sources)) {\n const sourceName = source.source_name\n ? `${source.source_name}.${source.name}`\n : source.name || sourceKey.split('.').slice(-2).join('.');\n\n // Skip if already in cache (model with same name takes precedence)\n if (this.modelsCache!.has(sourceName)) continue;\n\n const tablePath = [source.database, source.schema, source.identifier || source.name]\n .filter(Boolean)\n .join('.');\n\n const columns = source.columns\n ? Object.values(source.columns).map((col) => ({\n name: col.name || '',\n description: col.description || '',\n data_type: col.type || '',\n hints: [] as string[],\n }))\n : [];\n\n this.modelsCache!.set(sourceName, {\n name: sourceName,\n path: '',\n description: source.description || '',\n table: tablePath,\n tags: [],\n meta: {},\n columns,\n source: 'dbt-source',\n });\n }\n }\n } catch {\n // manifest.json unreadable or malformed — silently skip\n }\n }\n\n /**\n * Read dbt's target/catalog.json and merge column data_type, missing columns,\n * and table paths into loaded models.\n */\n private async mergeCatalogTypes(): Promise<void> {\n const catalogPath = join(this.projectPath, 'target', 'catalog.json');\n\n try {\n await access(catalogPath);\n } catch {\n return; // No catalog.json, nothing to merge\n }\n\n try {\n const content = await readFile(catalogPath, 'utf-8');\n const catalog = JSON.parse(content) as {\n nodes?: Record<string, {\n metadata?: { schema?: string; name?: string; database?: string };\n columns?: Record<string, { type?: string; name?: string }>;\n }>;\n };\n\n if (!catalog.nodes) return;\n\n // Build lookups: model name → column types, model name → table path\n const typesByModel = new Map<string, Map<string, string>>();\n const tableByModel = new Map<string, string>();\n\n for (const [nodeKey, node] of Object.entries(catalog.nodes)) {\n // Node keys are like \"model.project_name.model_name\"\n const parts = nodeKey.split('.');\n const modelName = parts[parts.length - 1];\n\n // Build fully-qualified table path from metadata\n if (node.metadata?.schema && node.metadata?.name) {\n const tablePath = node.metadata.database\n ? `${node.metadata.database}.${node.metadata.schema}.${node.metadata.name}`\n : `${node.metadata.schema}.${node.metadata.name}`;\n tableByModel.set(modelName, tablePath);\n }\n\n if (!node.columns) continue;\n\n const colTypes = new Map<string, string>();\n for (const [colName, colInfo] of Object.entries(node.columns)) {\n if (colInfo.type) {\n colTypes.set(colName.toLowerCase(), colInfo.type);\n }\n }\n\n if (colTypes.size > 0) {\n typesByModel.set(modelName, colTypes);\n }\n }\n\n // Merge into cached models\n for (const [modelName, model] of this.modelsCache!) {\n // Set table path from catalog if not already set in schema.yml\n const tablePath = tableByModel.get(modelName);\n if (tablePath && !model.table) {\n model.table = tablePath;\n }\n\n const colTypes = typesByModel.get(modelName);\n if (!colTypes) continue;\n\n // Merge types into existing columns\n const existingCols = new Set(model.columns.map(c => c.name.toLowerCase()));\n for (const column of model.columns) {\n if (!column.data_type) {\n const catalogType = colTypes.get(column.name.toLowerCase());\n if (catalogType) {\n column.data_type = catalogType;\n }\n }\n }\n\n // Add columns from catalog that don't exist in schema.yml\n for (const [colNameLower, colType] of colTypes) {\n if (!existingCols.has(colNameLower)) {\n model.columns.push({\n name: colNameLower,\n description: '',\n data_type: colType,\n hints: [],\n });\n }\n }\n }\n } catch {\n // catalog.json unreadable or malformed — silently skip\n }\n }\n\n /**\n * Filter models by include/exclude glob patterns and tags.\n * Patterns match against model paths.\n */\n static filterModels(\n models: DbtModelSummary[],\n filters: ModelFilters\n ): DbtModelSummary[] {\n let filtered = [...models];\n\n // Apply include patterns (match any)\n if (filters.include && filters.include.length > 0) {\n filtered = filtered.filter((model) =>\n filters.include!.some((pattern) => minimatch(model.path, pattern))\n );\n }\n\n // Apply exclude patterns (exclude any matches)\n if (filters.exclude && filters.exclude.length > 0) {\n filtered = filtered.filter(\n (model) =>\n !filters.exclude!.some((pattern) => minimatch(model.path, pattern))\n );\n }\n\n // Apply tag filters (match any)\n if (filters.tags && filters.tags.length > 0) {\n filtered = filtered.filter((model) =>\n filters.tags!.some((tag) => model.tags.includes(tag))\n );\n }\n\n return filtered;\n }\n\n /**\n * Get default filters that focus on marts/reporting models.\n * These are typically the models most useful for BI dashboards.\n */\n static getDefaultFilters(): Required<ModelFilters> {\n return {\n include: ['**/marts/**', '**/reporting/**'],\n exclude: ['**/staging/**', '**/intermediate/**'],\n tags: [],\n };\n }\n}\n","import { parse as parseYaml } from 'yaml';\nimport { dirname, join } from 'path';\nimport type { DbtModel, DbtColumn } from './types.js';\n\ninterface RawSchemaYml {\n version: number;\n models?: RawModel[];\n}\n\ninterface RawModel {\n name: string;\n description?: string;\n meta?: Record<string, unknown>;\n tags?: string[];\n columns?: RawColumn[];\n}\n\ninterface RawColumn {\n name: string;\n description?: string;\n data_type?: string;\n tests?: (string | Record<string, unknown>)[];\n}\n\n/**\n * Extract hints from dbt column tests.\n * - unique → \"unique\"\n * - not_null → \"required\"\n * - relationships: { to: ref('X') } → \"fk:X\"\n */\nexport function extractHintsFromTests(tests: (string | Record<string, unknown>)[]): string[] {\n const hints: string[] = [];\n\n for (const test of tests) {\n if (typeof test === 'string') {\n if (test === 'unique') {\n hints.push('unique');\n } else if (test === 'not_null') {\n hints.push('required');\n } else if (test === 'primary_key') {\n hints.push('primary_key');\n }\n } else if (typeof test === 'object' && test !== null) {\n // Handle relationships test\n if ('relationships' in test) {\n const rel = test.relationships as { to?: string; field?: string };\n if (rel.to) {\n // Extract table name from ref('table_name')\n const match = rel.to.match(/ref\\(['\"]([^'\"]+)['\"]\\)/);\n if (match) {\n hints.push(`fk:${match[1]}`);\n }\n }\n }\n // Handle dbt_constraints for primary key\n if ('dbt_constraints' in test) {\n const constraint = test.dbt_constraints as { type?: string };\n if (constraint.type === 'primary_key') {\n hints.push('primary_key');\n }\n }\n }\n }\n\n return hints;\n}\n\n/**\n * Parse a dbt schema.yml file and extract model definitions.\n * @param content - Raw YAML content\n * @param schemaPath - Path to the schema file (e.g., \"models/marts/_schema.yml\")\n * @returns Array of parsed models\n */\nexport function parseSchemaYml(content: string, schemaPath: string): DbtModel[] {\n const parsed = parseYaml(content) as RawSchemaYml;\n\n if (!parsed?.models || !Array.isArray(parsed.models)) {\n return [];\n }\n\n const schemaDir = dirname(schemaPath);\n\n return parsed.models.map((model): DbtModel => {\n const columns: DbtColumn[] = (model.columns || []).map((col) => ({\n name: col.name,\n description: col.description || '',\n data_type: col.data_type,\n hints: col.tests ? extractHintsFromTests(col.tests) : [],\n }));\n\n return {\n name: model.name,\n path: join(schemaDir, `${model.name}.sql`),\n description: model.description || 'No description',\n tags: model.tags || [],\n meta: model.meta || {},\n columns,\n };\n });\n}\n\n/**\n * Parse dbt_project.yml to get project-level config.\n */\nexport function parseProjectYml(content: string): {\n name: string;\n version?: string;\n profile?: string;\n modelPaths: string[];\n vars: Record<string, unknown>;\n} {\n const parsed = parseYaml(content) as Record<string, unknown>;\n\n return {\n name: (parsed.name as string) || 'unknown',\n version: parsed.version as string | undefined,\n profile: parsed.profile as string | undefined,\n modelPaths: (parsed['model-paths'] as string[]) || (parsed.model_paths as string[]) || ['models'],\n vars: (parsed.vars as Record<string, unknown>) || {},\n };\n}\n","import { readFile, access, readdir } from 'fs/promises';\nimport { join, extname, relative } from 'path';\n\nexport interface YamchartModel {\n name: string;\n description: string;\n path: string; // relative to project\n source?: string; // dbt table this queries\n}\n\n/**\n * Scan yamchart models directory and extract metadata.\n * Used to cross-reference which yamchart models use which dbt tables.\n */\nexport async function scanYamchartModels(projectDir: string): Promise<YamchartModel[]> {\n const modelsDir = join(projectDir, 'models');\n const models: YamchartModel[] = [];\n\n try {\n await access(modelsDir);\n } catch {\n return [];\n }\n\n await scanModelsRecursive(modelsDir, projectDir, models);\n return models;\n}\n\nasync function scanModelsRecursive(\n dir: string,\n projectDir: string,\n models: YamchartModel[]\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 scanModelsRecursive(fullPath, projectDir, models);\n } else if (extname(entry.name) === '.sql') {\n const content = await readFile(fullPath, 'utf-8');\n const metadata = parseModelMetadata(content);\n\n if (metadata.name) {\n models.push({\n name: metadata.name,\n description: metadata.description || '',\n path: relative(projectDir, fullPath),\n source: metadata.source || extractSourceFromSql(content),\n });\n }\n }\n }\n}\n\ninterface ModelMetadata {\n name?: string;\n description?: string;\n source?: string;\n}\n\n/**\n * Parse yamchart model metadata from SQL comments.\n */\nfunction parseModelMetadata(content: string): ModelMetadata {\n const metadata: ModelMetadata = {};\n\n // Match @name: value\n const nameMatch = content.match(/--\\s*@name:\\s*(.+)/);\n if (nameMatch) {\n metadata.name = nameMatch[1].trim();\n }\n\n // Match @description: value\n const descMatch = content.match(/--\\s*@description:\\s*(.+)/);\n if (descMatch) {\n metadata.description = descMatch[1].trim();\n }\n\n // Match @source: value (explicit dbt table reference)\n const sourceMatch = content.match(/--\\s*@source:\\s*(.+)/);\n if (sourceMatch) {\n metadata.source = sourceMatch[1].trim();\n }\n\n return metadata;\n}\n\n/**\n * Extract the primary table name from SQL FROM clause.\n * This is a best-effort extraction for cross-referencing.\n */\nfunction extractSourceFromSql(sql: string): string | undefined {\n // Remove comments\n const noComments = sql.replace(/--.*$/gm, '').replace(/\\/\\*[\\s\\S]*?\\*\\//g, '');\n\n // Match FROM table_name (handles schema.table and database.schema.table)\n const fromMatch = noComments.match(/\\bFROM\\s+(\\{\\{\\s*ref\\(['\"]([^'\"]+)['\"]\\)\\s*\\}\\}|[\\w.]+)/i);\n\n if (fromMatch) {\n // If it's a Jinja ref(), extract the table name\n if (fromMatch[2]) {\n return fromMatch[2];\n }\n // Otherwise return the raw table name\n return fromMatch[1];\n }\n\n return undefined;\n}\n"],"mappings":"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","names":["readFile","access","join","parseYaml","join","join","readFile","access","join","join","access","readFile","parseYaml"]}
@@ -52,6 +52,10 @@ yamchart search <keyword> # Find tables and columns matching keyword
52
52
  yamchart query "SELECT ..." # Execute ad-hoc SQL (default limit: 100)
53
53
  yamchart query "SELECT ..." --limit 500 # Custom row limit
54
54
  yamchart reset-password --email <e> # Reset a user's password (requires auth enabled)
55
+ yamchart sync-dbt --path ../dbt # Sync dbt metadata to .yamchart/catalog
56
+ yamchart sync-dbt --refresh # Re-sync with saved config
57
+ yamchart sync-dbt --target-database PROD_DB # Rewrite catalog refs to target production database
58
+ yamchart sync-dbt --refresh --target-database PROD_DB # Override saved target database
55
59
  yamchart advisor # Interactive dbt model advisor (requires ANTHROPIC_API_KEY)
56
60
  yamchart advisor "question" # Single question mode
57
61
  yamchart advisor audit # Comprehensive dbt project audit
@@ -697,6 +701,29 @@ Dashboard text widgets support AI-powered text generation via slash commands in
697
701
  - `/ai! create a quarterly business review with all KPI highlights`
698
702
  - `/ai add a bullet list of key trends`
699
703
 
704
+ ## dbt Sync
705
+
706
+ Syncs dbt project metadata (models, sources, lineage) into `.yamchart/catalog.json` and `.yamchart/catalog.md`. This powers `ref()` resolution, `describe`/`sample` with dbt model names, lineage visualization, and advisor context.
707
+
708
+ ```bash
709
+ yamchart sync-dbt --path ../dbt-project # Sync from a local dbt project
710
+ yamchart sync-dbt --refresh # Re-sync with saved path and options
711
+ yamchart sync-dbt --target-database PROD_DB # Rewrite catalog refs to target production database
712
+ yamchart sync-dbt --refresh --target-database PROD_DB # Override saved target database
713
+ ```
714
+
715
+ | Flag | Description |
716
+ |------|-------------|
717
+ | `-p, --path <dir>` | Path to dbt project directory |
718
+ | `--refresh` | Re-sync using saved configuration |
719
+ | `--target-database <database>` | Override database in `ref()` paths (e.g. switch from dev to prod) |
720
+
721
+ > **Tip:** If you sync from a dbt dev target, use `--target-database` to rewrite refs to your production database. The setting is saved and reused on `--refresh`.
722
+
723
+ **Database mismatch warning:** After sync, yamchart compares the catalog database against your default connection. If they differ (e.g. catalog has `DEV_DB` but connection uses `PROD_DB`), a warning suggests running `sync-dbt --refresh --target-database <connection_database>` to align them.
724
+
725
+ **Requirements:** Your dbt project must have run `dbt compile` or `dbt run` to generate `target/manifest.json`.
726
+
700
727
  ## Warehouse Metadata Sync
701
728
 
702
729
  Pulls table metadata and sample rows directly from your warehouse into the catalog. Tables landed by ELT tools (Fivetran, Hevo, Rudderstack, Airbyte, etc.) become visible to the advisor, `ref()` resolution, and CLI commands like `describe` and `sample`.
@@ -720,10 +747,11 @@ yamchart sync-warehouse --json # JSON output
720
747
 
721
748
  ## Lineage
722
749
 
723
- Traces upstream dependencies for any model in the catalog. Reads from `.yamchart/catalog.json` (populated by `sync-dbt`). Shows dbt models, dbt sources, and the full ancestry tree.
750
+ Traces upstream dependencies for any model both dbt catalog models and local yamchart SQL models. For dbt models, lineage follows `dependsOn` from `target/manifest.json`. For yamchart models (local `.sql` files), lineage parses SQL to extract `{{ ref() }}` calls, `@source` annotations, and `FROM`/`JOIN` table references, then resolves them against the catalog.
724
751
 
725
752
  ```bash
726
753
  yamchart lineage fct_revenue # Show upstream dependencies
754
+ yamchart lineage marketing__daily_traffic # Works for yamchart models too
727
755
  yamchart lineage fct_revenue --depth 2 # Limit recursion depth
728
756
  yamchart lineage fct_revenue --json # JSON output
729
757
  ```
@@ -737,7 +765,12 @@ fct_revenue
737
765
  ← source: raw.payments
738
766
  ```
739
767
 
740
- **Note:** Lineage data requires `sync-dbt` with a dbt project that has run `dbt compile` or `dbt run` (which generates `target/manifest.json`). Source nodes are automatically extracted and included in the catalog.
768
+ **How yamchart model dependencies are resolved:**
769
+ - `{{ ref('model_name') }}` → matched by catalog model name
770
+ - `-- @source: model_name` → matched by catalog model name
771
+ - `FROM database.schema.table` → matched by catalog table path (case-insensitive)
772
+
773
+ **Note:** dbt lineage data requires `sync-dbt` with a dbt project that has run `dbt compile` or `dbt run` (which generates `target/manifest.json`). If the manifest is missing, `sync-dbt` will warn you. Yamchart model lineage works independently by parsing SQL files directly.
741
774
 
742
775
  ## Axis Types
743
776
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yamchart",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Git-native business intelligence dashboards",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -65,11 +65,11 @@
65
65
  "typescript": "^5.7.0",
66
66
  "vitest": "^2.1.0",
67
67
  "@yamchart/auth-local": "0.1.0",
68
+ "@yamchart/advisor": "0.1.0",
68
69
  "@yamchart/config": "0.1.2",
69
- "@yamchart/query": "0.1.2",
70
+ "@yamchart/schema": "0.1.2",
70
71
  "@yamchart/server": "0.1.2",
71
- "@yamchart/advisor": "0.1.0",
72
- "@yamchart/schema": "0.1.2"
72
+ "@yamchart/query": "0.1.2"
73
73
  },
74
74
  "files": [
75
75
  "dist",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../packages/advisor/src/dbt/knowledge.ts","../../../packages/advisor/src/dbt/project.ts","../../../packages/advisor/src/tools.ts","../../../packages/advisor/src/agent.ts","../../../packages/advisor/src/providers/anthropic.ts","../../../packages/advisor/src/dbt/writer.ts"],"sourcesContent":["import { readFile } from 'fs/promises';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst KNOWLEDGE_DIR = join(__dirname, '..', 'knowledge');\n\nexport const KNOWLEDGE_TOPICS = [\n 'materializations',\n 'incremental',\n 'macros',\n 'testing',\n 'naming',\n 'performance',\n] as const;\n\nexport type KnowledgeTopic = (typeof KNOWLEDGE_TOPICS)[number];\n\nexport async function loadKnowledge(topic: string): Promise<string> {\n if (!KNOWLEDGE_TOPICS.includes(topic as KnowledgeTopic)) {\n return `Unknown topic: \"${topic}\". Available topics: ${KNOWLEDGE_TOPICS.join(', ')}`;\n }\n\n const filePath = join(KNOWLEDGE_DIR, `${topic}.md`);\n return readFile(filePath, 'utf-8');\n}\n\nexport function getKnowledgeOverview(): string {\n return `You have access to a dbt knowledge base via the get_knowledge tool. Available topics:\n${KNOWLEDGE_TOPICS.map((t) => `- ${t}`).join('\\n')}\n\nUse get_knowledge when you need specifics on materializations, incremental strategies, Jinja macros, testing, naming conventions, or performance optimization.`;\n}\n","import { readFile } from 'fs/promises';\nimport { join, basename, relative, dirname } from 'path';\nimport fg from 'fast-glob';\nimport { parse as parseYaml } from 'yaml';\n\nexport interface DbtConventions {\n folderStructure: string[];\n namingPrefixes: Record<string, string>;\n commonMaterializations: Record<string, string>;\n schemaYmlPattern: 'per-folder' | 'per-model' | 'single-file';\n testPatterns: string[];\n}\n\nexport interface DbtModelInfo {\n name: string;\n path: string;\n layer: string;\n materialization: string;\n folder: string;\n}\n\ninterface DbtProjectYml {\n name: string;\n 'model-paths'?: string[];\n model_paths?: string[];\n}\n\nasync function getModelPaths(projectPath: string): Promise<string[]> {\n const content = await readFile(join(projectPath, 'dbt_project.yml'), 'utf-8');\n const parsed = parseYaml(content) as DbtProjectYml;\n return parsed['model-paths'] ?? parsed.model_paths ?? ['models'];\n}\n\nfunction extractMaterialization(sql: string): string {\n const match = sql.match(/materialized\\s*=\\s*['\"](\\w+)['\"]/);\n return match?.[1] ?? 'view';\n}\n\nfunction detectLayer(relativePath: string): string {\n const parts = relativePath.split('/');\n return parts[0] ?? 'unknown';\n}\n\nfunction detectPrefix(\n models: Array<{ name: string; layer: string }>\n): Record<string, string> {\n const prefixesByLayer = new Map<string, Map<string, number>>();\n\n for (const model of models) {\n const underscoreIdx = model.name.indexOf('_');\n if (underscoreIdx === -1) continue;\n const prefix = model.name.slice(0, underscoreIdx + 1);\n\n if (!prefixesByLayer.has(model.layer)) {\n prefixesByLayer.set(model.layer, new Map());\n }\n const counts = prefixesByLayer.get(model.layer)!;\n counts.set(prefix, (counts.get(prefix) ?? 0) + 1);\n }\n\n const result: Record<string, string> = {};\n for (const [layer, counts] of prefixesByLayer) {\n let maxCount = 0;\n let maxPrefix = '';\n for (const [prefix, count] of counts) {\n if (count > maxCount) {\n maxCount = count;\n maxPrefix = prefix;\n }\n }\n if (maxPrefix && maxCount >= 2) {\n result[layer] = maxPrefix;\n }\n }\n\n return result;\n}\n\nfunction detectMaterializations(\n models: Array<{ layer: string; materialization: string }>\n): Record<string, string> {\n const matByLayer = new Map<string, Map<string, number>>();\n\n for (const model of models) {\n if (!matByLayer.has(model.layer)) {\n matByLayer.set(model.layer, new Map());\n }\n const counts = matByLayer.get(model.layer)!;\n counts.set(model.materialization, (counts.get(model.materialization) ?? 0) + 1);\n }\n\n const result: Record<string, string> = {};\n for (const [layer, counts] of matByLayer) {\n let maxCount = 0;\n let maxMat = '';\n for (const [mat, count] of counts) {\n if (count > maxCount) {\n maxCount = count;\n maxMat = mat;\n }\n }\n if (maxMat) result[layer] = maxMat;\n }\n\n return result;\n}\n\nasync function detectSchemaYmlPattern(\n projectPath: string,\n modelPaths: string[]\n): Promise<'per-folder' | 'per-model' | 'single-file'> {\n const ymlFiles: string[] = [];\n for (const mp of modelPaths) {\n const pattern = join(projectPath, mp, '**/*.yml');\n const files = await fg(pattern, { ignore: ['**/node_modules/**'] });\n ymlFiles.push(...files);\n }\n\n if (ymlFiles.length === 0) return 'per-folder';\n if (ymlFiles.length === 1) return 'single-file';\n\n const dirs = new Set(ymlFiles.map((f) => dirname(f)));\n return dirs.size > 1 ? 'per-folder' : 'single-file';\n}\n\nexport async function detectConventions(projectPath: string): Promise<DbtConventions> {\n const modelPaths = await getModelPaths(projectPath);\n const models = await listDbtModels(projectPath);\n\n const folders = [...new Set(models.map((m) => m.layer))].filter((l) => l !== 'unknown');\n\n return {\n folderStructure: folders,\n namingPrefixes: detectPrefix(models),\n commonMaterializations: detectMaterializations(models),\n schemaYmlPattern: await detectSchemaYmlPattern(projectPath, modelPaths),\n testPatterns: [],\n };\n}\n\nexport async function listDbtModels(projectPath: string): Promise<DbtModelInfo[]> {\n const modelPaths = await getModelPaths(projectPath);\n const models: DbtModelInfo[] = [];\n\n for (const mp of modelPaths) {\n const pattern = join(projectPath, mp, '**/*.sql');\n const files = await fg(pattern, { ignore: ['**/node_modules/**'] });\n\n for (const file of files) {\n const relPath = relative(join(projectPath, mp), file);\n const name = basename(file, '.sql');\n const sql = await readFile(file, 'utf-8');\n const materialization = extractMaterialization(sql);\n const layer = detectLayer(relPath);\n const folder = dirname(relPath);\n\n models.push({ name, path: relPath, layer, materialization, folder });\n }\n }\n\n return models;\n}\n\nexport async function readDbtModel(\n projectPath: string,\n modelName: string\n): Promise<string | null> {\n const models = await listDbtModels(projectPath);\n const model = models.find((m) => m.name === modelName);\n if (!model) return null;\n\n const modelPaths = await getModelPaths(projectPath);\n for (const mp of modelPaths) {\n const fullPath = join(projectPath, mp, model.path);\n try {\n return await readFile(fullPath, 'utf-8');\n } catch {\n continue;\n }\n }\n return null;\n}\n","import type { ToolDefinition } from './providers/types.js';\nimport type { AdvisorContext } from './context.js';\nimport { loadKnowledge } from './dbt/knowledge.js';\nimport { readDbtModel } from './dbt/project.js';\n\nexport type { AdvisorContext } from './context.js';\n\nexport const TOOL_DEFINITIONS: ToolDefinition[] = [\n // Read-only tools\n {\n name: 'list_yamchart_models',\n description: 'List all yamchart SQL models with names, descriptions, parameters, and return columns',\n parameters: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'list_charts',\n description: 'List all yamchart charts showing which models they reference and their chart type',\n parameters: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'get_catalog',\n description: 'Get the dbt catalog (.yamchart/catalog.md) with upstream table schemas and column metadata',\n parameters: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'get_warehouse_table',\n description: 'Get detailed column metadata and sample rows for a specific warehouse table from the cached catalog. Instant — reads from local catalog, no live query. Use the fully-qualified name (e.g. RAW.PAYMENTS).',\n parameters: {\n type: 'object',\n properties: {\n table: { type: 'string', description: 'Fully-qualified table name (e.g. RAW.PAYMENTS, STAGING.CUSTOMERS)' },\n },\n required: ['table'],\n },\n },\n {\n name: 'get_warehouse_tables',\n description: 'Get detailed metadata for multiple warehouse tables at once from the cached catalog.',\n parameters: {\n type: 'object',\n properties: {\n tables: { type: 'array', items: { type: 'string' }, description: 'List of fully-qualified table names' },\n },\n required: ['tables'],\n },\n },\n {\n name: 'introspect_warehouse',\n description: 'Query the live warehouse. Use for discovering raw tables not yet modeled in dbt.',\n parameters: {\n type: 'object',\n properties: {\n sql: { type: 'string', description: 'SQL query to execute (e.g. SHOW TABLES, SELECT * FROM information_schema.columns)' },\n },\n required: ['sql'],\n },\n },\n {\n name: 'sample_data',\n description: 'Preview rows from a table in the warehouse',\n parameters: {\n type: 'object',\n properties: {\n table: { type: 'string', description: 'Table name (can be schema-qualified)' },\n limit: { type: 'number', description: 'Number of rows to return (default: 5)' },\n },\n required: ['table'],\n },\n },\n {\n name: 'read_dbt_model',\n description: 'Read the full SQL source of an existing dbt model',\n parameters: {\n type: 'object',\n properties: {\n model_name: { type: 'string', description: 'Name of the dbt model to read' },\n },\n required: ['model_name'],\n },\n },\n {\n name: 'list_dbt_models',\n description: 'List all dbt models with their layer (staging/intermediate/marts), materialization, and folder',\n parameters: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'get_knowledge',\n description: 'Look up dbt reference documentation on a topic: materializations, incremental, macros, testing, naming, performance',\n parameters: {\n type: 'object',\n properties: {\n topic: { type: 'string', description: 'Topic name: materializations, incremental, macros, testing, naming, performance' },\n },\n required: ['topic'],\n },\n },\n\n // Action tools\n {\n name: 'propose_model',\n description: 'Propose a new dbt model. Call this when you have a suggestion to present to the user.',\n parameters: {\n type: 'object',\n properties: {\n name: { type: 'string', description: 'Model name (snake_case)' },\n description: { type: 'string', description: 'One-line description of what this model does' },\n sql: { type: 'string', description: 'Full SQL for the model (may include Jinja)' },\n layer: { type: 'string', description: 'dbt layer: staging, intermediate, or marts' },\n materialization: { type: 'string', description: 'Materialization: view, table, or incremental' },\n explanation: { type: 'string', description: 'Why this model is useful (2-3 sentences)' },\n subfolder: { type: 'string', description: 'Optional subfolder within the layer (e.g. \"stripe\", \"core\")' },\n columns: {\n type: 'array',\n description: 'Column definitions for schema.yml',\n items: {\n type: 'object',\n properties: {\n name: { type: 'string' },\n description: { type: 'string' },\n tests: { type: 'array', items: { type: 'string' } },\n },\n required: ['name'],\n },\n },\n },\n required: ['name', 'description', 'sql', 'layer', 'materialization', 'explanation'],\n },\n },\n {\n name: 'write_dbt_model',\n description: 'Write a proposed dbt model SQL file to the dbt project. Only call after proposing and user confirmation.',\n parameters: {\n type: 'object',\n properties: {\n name: { type: 'string', description: 'Model name (must match a previous proposal)' },\n },\n required: ['name'],\n },\n },\n {\n name: 'update_schema_yml',\n description: 'Add or update the model entry in the appropriate schema.yml file. Only call after writing the model.',\n parameters: {\n type: 'object',\n properties: {\n name: { type: 'string', description: 'Model name (must match a written model)' },\n },\n required: ['name'],\n },\n },\n];\n\nexport async function executeToolCall(\n context: AdvisorContext,\n toolName: string,\n input: Record<string, unknown>\n): Promise<string> {\n switch (toolName) {\n case 'list_yamchart_models':\n return JSON.stringify(\n context.yamchart.models.map((m) => ({\n name: m.name,\n description: m.description,\n params: m.params,\n returns: m.returns,\n }))\n );\n\n case 'list_charts':\n return JSON.stringify(\n context.yamchart.charts.map((c) => ({\n name: c.name,\n title: c.title,\n model: c.model,\n type: c.type,\n }))\n );\n\n case 'get_catalog':\n return context.yamchart.catalog ?? 'No dbt catalog found. Run `yamchart sync-dbt` to create one.';\n\n case 'get_warehouse_table': {\n if (!context.yamchart.catalogJson) {\n return 'Warehouse catalog not available. Run `yamchart sync-warehouse` to populate it.';\n }\n const tableName = (input.table as string).toUpperCase();\n const match = context.yamchart.catalogJson.models.find(\n (m) => (m.table || '').toUpperCase() === tableName || m.name.toUpperCase() === tableName\n );\n if (!match) {\n return JSON.stringify({ error: `Table not found in catalog: ${input.table}. Use get_catalog to see available tables.` });\n }\n return JSON.stringify({\n name: match.name,\n table: match.table,\n tableType: match.tableType,\n source: match.source,\n columns: match.columns,\n sampleRows: match.sampleRows || [],\n });\n }\n\n case 'get_warehouse_tables': {\n if (!context.yamchart.catalogJson) {\n return 'Warehouse catalog not available. Run `yamchart sync-warehouse` to populate it.';\n }\n const tableNames = (input.tables as string[]).map((t) => t.toUpperCase());\n const results = tableNames.map((name) => {\n const match = context.yamchart.catalogJson!.models.find(\n (m) => (m.table || '').toUpperCase() === name || m.name.toUpperCase() === name\n );\n if (!match) {\n return { error: `Not found: ${name}` };\n }\n return {\n name: match.name,\n table: match.table,\n tableType: match.tableType,\n source: match.source,\n columns: match.columns,\n sampleRows: match.sampleRows || [],\n };\n });\n return JSON.stringify(results);\n }\n\n case 'introspect_warehouse': {\n if (!context.warehouse) {\n return 'Warehouse introspection not available. No database connection configured.';\n }\n const sql = input.sql as string;\n try {\n const result = await context.warehouse.executeSql(sql);\n return JSON.stringify(result);\n } catch (err) {\n return JSON.stringify({ error: err instanceof Error ? err.message : String(err) });\n }\n }\n\n case 'sample_data': {\n if (!context.warehouse) {\n return 'Warehouse not available. No database connection configured.';\n }\n const table = input.table as string;\n const limit = (input.limit as number) ?? 5;\n try {\n const result = await context.warehouse.executeSql(\n `SELECT * FROM ${table} LIMIT ${limit}`\n );\n return JSON.stringify(result);\n } catch (err) {\n return JSON.stringify({ error: err instanceof Error ? err.message : String(err) });\n }\n }\n\n case 'read_dbt_model': {\n const modelName = input.model_name as string;\n const sql = await readDbtModel(context.dbt.projectPath, modelName);\n if (!sql) {\n return JSON.stringify({ error: `Model not found: ${modelName}` });\n }\n return JSON.stringify({ name: modelName, sql });\n }\n\n case 'list_dbt_models':\n return JSON.stringify(\n context.dbt.models.map((m) => ({\n name: m.name,\n path: m.path,\n layer: m.layer,\n materialization: m.materialization,\n folder: m.folder,\n }))\n );\n\n case 'get_knowledge': {\n const topic = input.topic as string;\n return loadKnowledge(topic);\n }\n\n case 'propose_model':\n return JSON.stringify({\n status: 'proposed',\n name: input.name,\n description: input.description,\n sql: input.sql,\n layer: input.layer,\n materialization: input.materialization,\n explanation: input.explanation,\n subfolder: input.subfolder,\n columns: input.columns,\n });\n\n case 'write_dbt_model':\n // Actual writing happens in the CLI command, not here.\n // The agent tool just signals intent — the CLI confirms with the user.\n return JSON.stringify({\n status: 'pending_confirmation',\n name: input.name,\n message: 'Model write requested. Waiting for user confirmation.',\n });\n\n case 'update_schema_yml':\n return JSON.stringify({\n status: 'pending_confirmation',\n name: input.name,\n message: 'Schema.yml update requested. Waiting for user confirmation.',\n });\n\n default:\n return JSON.stringify({ error: `Unknown tool: ${toolName}` });\n }\n}\n","import type {\n LLMProvider,\n Message,\n ContentBlock,\n TextBlock,\n ToolUseBlock,\n ToolResultBlock,\n} from './providers/types.js';\nimport type { AdvisorContext } from './context.js';\nimport { TOOL_DEFINITIONS, executeToolCall } from './tools.js';\nimport { getKnowledgeOverview } from './dbt/knowledge.js';\n\nexport interface Proposal {\n name: string;\n description: string;\n sql: string;\n layer: string;\n materialization: string;\n explanation: string;\n subfolder?: string;\n columns?: Array<{ name: string; description?: string; tests?: string[] }>;\n}\n\nexport interface AgentResult {\n response: string;\n proposals: Proposal[];\n messages: Message[];\n}\n\nexport const SYSTEM_PROMPT = `You are a dbt advisor for yamchart projects. You analyze the BI layer (yamchart models, charts, dashboards) and the data engineering layer (dbt models, warehouse tables) to suggest improvements to dbt models.\n\nThink like a senior data engineer. Your goals:\n- Understand what the BI layer needs (charts, filters, drill-downs, date ranges)\n- Identify gaps in the dbt project (missing models, incomplete staging, opportunities for pre-aggregation)\n- Suggest new dbt models that serve the BI layer better\n- Follow the project's existing conventions (folder structure, naming, materializations)\n\nWhen proposing models:\n- Match the project's naming conventions (detect and follow existing prefixes like stg_, fct_, dim_)\n- Place models in the correct layer and folder\n- Choose appropriate materializations\n- Include column descriptions and tests in proposals\n- Explain WHY the model helps, not just WHAT it does\n- Use ref() and source() macros correctly\n\nWhen in audit mode:\n- Evaluate: coverage gaps, convention violations, materialization mismatches, missing tests\n- Rank suggestions by impact\n- Be concise — focus on actionable improvements\n\n${getKnowledgeOverview()}\n\nWhen you have a suggestion, call propose_model with the full SQL and metadata. Do NOT call write_dbt_model or update_schema_yml directly — the user will be prompted to confirm before writing.`;\n\nconst MAX_TOOL_ROUNDS = 15;\n\nexport class AdvisorAgent {\n private provider: LLMProvider;\n\n constructor(provider: LLMProvider) {\n this.provider = provider;\n }\n\n async run(\n context: AdvisorContext,\n userMessages: Message[]\n ): Promise<AgentResult> {\n const proposals: Proposal[] = [];\n const messages: Message[] = [...userMessages];\n\n // Build context-specific system prompt\n const systemPrompt = this.buildSystemPrompt(context);\n\n for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {\n const response = await this.provider.chat({\n system: systemPrompt,\n messages,\n tools: TOOL_DEFINITIONS,\n });\n\n const textBlocks = response.content.filter(\n (b): b is TextBlock => b.type === 'text'\n );\n const toolUseBlocks = response.content.filter(\n (b): b is ToolUseBlock => b.type === 'tool_use'\n );\n\n // No tool calls — return final text response\n if (toolUseBlocks.length === 0) {\n const responseText = textBlocks.map((b) => b.text).join('\\n');\n return { response: responseText, proposals, messages };\n }\n\n // Add assistant message with tool calls\n messages.push({ role: 'assistant', content: response.content });\n\n // Execute each tool call\n const toolResults: ContentBlock[] = [];\n for (const toolUse of toolUseBlocks) {\n const result = await executeToolCall(context, toolUse.name, toolUse.input);\n\n // Collect proposals\n if (toolUse.name === 'propose_model') {\n const parsed = JSON.parse(result) as Record<string, unknown>;\n proposals.push({\n name: parsed.name as string,\n description: parsed.description as string,\n sql: toolUse.input.sql as string,\n layer: toolUse.input.layer as string,\n materialization: toolUse.input.materialization as string,\n explanation: toolUse.input.explanation as string,\n subfolder: toolUse.input.subfolder as string | undefined,\n columns: toolUse.input.columns as Proposal['columns'],\n });\n }\n\n toolResults.push({\n type: 'tool_result',\n tool_use_id: toolUse.id,\n content: result,\n } as ToolResultBlock);\n }\n\n // Add tool results as user message\n messages.push({ role: 'user', content: toolResults });\n }\n\n // Hit max rounds — return what we have\n return {\n response: 'Reached maximum tool call rounds. Here are the suggestions gathered so far.',\n proposals,\n messages,\n };\n }\n\n private buildSystemPrompt(context: AdvisorContext): string {\n const parts = [SYSTEM_PROMPT];\n\n // Add project summary\n parts.push(`\\n## Current Project Summary`);\n parts.push(`- Yamchart: ${context.yamchart.models.length} SQL models, ${context.yamchart.charts.length} charts, ${context.yamchart.dashboards.length} dashboards`);\n parts.push(`- dbt project: \"${context.dbt.projectName}\" at ${context.dbt.projectPath}`);\n parts.push(`- dbt models: ${context.dbt.models.length} (layers: ${context.dbt.conventions.folderStructure.join(', ') || 'none detected'})`);\n\n if (Object.keys(context.dbt.conventions.namingPrefixes).length > 0) {\n const prefixes = Object.entries(context.dbt.conventions.namingPrefixes)\n .map(([layer, prefix]) => `${layer}: ${prefix}`)\n .join(', ');\n parts.push(`- Naming prefixes: ${prefixes}`);\n }\n\n if (Object.keys(context.dbt.conventions.commonMaterializations).length > 0) {\n const mats = Object.entries(context.dbt.conventions.commonMaterializations)\n .map(([layer, mat]) => `${layer}: ${mat}`)\n .join(', ');\n parts.push(`- Materializations: ${mats}`);\n }\n\n parts.push(`- Schema.yml pattern: ${context.dbt.conventions.schemaYmlPattern}`);\n parts.push(`- Catalog: ${context.yamchart.catalog ? 'available' : 'not synced'}`);\n parts.push(`- Warehouse: ${context.warehouse ? `connected (${context.warehouse.connectionType})` : 'not connected'}`);\n\n return parts.join('\\n');\n }\n}\n","import Anthropic from '@anthropic-ai/sdk';\nimport type {\n LLMProvider,\n LLMResponse,\n Message,\n ToolDefinition,\n ContentBlock,\n TextBlock,\n ToolUseBlock,\n} from './types.js';\n\nexport class AnthropicProvider implements LLMProvider {\n private client: Anthropic;\n private model: string;\n\n constructor(apiKey: string, model: string = 'claude-sonnet-4-5-20250929') {\n this.client = new Anthropic({ apiKey });\n this.model = model;\n }\n\n async chat(options: {\n system: string;\n messages: Message[];\n tools: ToolDefinition[];\n maxTokens?: number;\n }): Promise<LLMResponse> {\n const tools: Anthropic.Tool[] = options.tools.map((t) => ({\n name: t.name,\n description: t.description,\n input_schema: {\n type: 'object' as const,\n properties: t.parameters.properties,\n required: t.parameters.required ?? [],\n },\n }));\n\n const messages: Anthropic.MessageParam[] = options.messages.map((m) => {\n if (typeof m.content === 'string') {\n return { role: m.role, content: m.content };\n }\n const blocks: Anthropic.ContentBlockParam[] = m.content.map((block) => {\n if (block.type === 'text') {\n return { type: 'text' as const, text: (block as TextBlock).text };\n }\n if (block.type === 'tool_use') {\n const tu = block as ToolUseBlock;\n return { type: 'tool_use' as const, id: tu.id, name: tu.name, input: tu.input };\n }\n if (block.type === 'tool_result') {\n const tr = block as { type: 'tool_result'; tool_use_id: string; content: string };\n return { type: 'tool_result' as const, tool_use_id: tr.tool_use_id, content: tr.content };\n }\n throw new Error(`Unknown block type: ${block.type}`);\n });\n return { role: m.role, content: blocks };\n });\n\n const response = await this.client.messages.create({\n model: this.model,\n max_tokens: options.maxTokens ?? 4096,\n system: options.system,\n tools,\n messages,\n });\n\n const content: ContentBlock[] = response.content.map((block) => {\n if (block.type === 'text') {\n return { type: 'text', text: block.text } as TextBlock;\n }\n if (block.type === 'tool_use') {\n return {\n type: 'tool_use',\n id: block.id,\n name: block.name,\n input: block.input as Record<string, unknown>,\n } as ToolUseBlock;\n }\n throw new Error(`Unexpected block type: ${block.type}`);\n });\n\n return {\n content,\n stopReason: response.stop_reason === 'tool_use' ? 'tool_use' : 'end',\n };\n }\n}\n","import { readFile, writeFile, mkdir, access } from 'fs/promises';\nimport { join, dirname } from 'path';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport type { DbtConventions } from './project.js';\n\nexport function buildModelPath(\n modelName: string,\n layer: string | undefined,\n conventions: DbtConventions,\n subfolder?: string\n): string {\n const effectiveLayer = layer ?? (conventions.folderStructure.includes('marts') ? 'marts' : conventions.folderStructure[0] ?? 'models');\n const parts = ['models', effectiveLayer];\n if (subfolder) parts.push(subfolder);\n parts.push(`${modelName}.sql`);\n return parts.join('/');\n}\n\nexport function formatModelSql(options: {\n materialization: string;\n tags?: string[];\n sql: string;\n description?: string;\n}): string {\n const lines: string[] = [];\n\n // Only add config block for non-default materializations\n if (options.materialization !== 'view') {\n const configParts: string[] = [`materialized='${options.materialization}'`];\n if (options.tags && options.tags.length > 0) {\n configParts.push(`tags=[${options.tags.map((t) => `'${t}'`).join(', ')}]`);\n }\n lines.push(`{{\\n config(\\n ${configParts.join(',\\n ')}\\n )\\n}}\\n`);\n }\n\n lines.push(options.sql);\n return lines.join('\\n');\n}\n\nexport interface SchemaColumn {\n name: string;\n description?: string;\n tests?: string[];\n}\n\nexport function buildSchemaYmlEntry(options: {\n name: string;\n description: string;\n columns?: SchemaColumn[];\n}): string {\n const model: Record<string, unknown> = {\n name: options.name,\n description: options.description,\n };\n\n if (options.columns && options.columns.length > 0) {\n model.columns = options.columns.map((col) => {\n const entry: Record<string, unknown> = { name: col.name };\n if (col.description) entry.description = col.description;\n if (col.tests && col.tests.length > 0) entry.tests = col.tests;\n return entry;\n });\n }\n\n return stringifyYaml({ models: [model] }, { indent: 2 });\n}\n\nexport async function writeDbtModel(\n projectPath: string,\n modelPath: string,\n sql: string\n): Promise<string> {\n const fullPath = join(projectPath, modelPath);\n await mkdir(dirname(fullPath), { recursive: true });\n await writeFile(fullPath, sql + '\\n', 'utf-8');\n return fullPath;\n}\n\nexport async function updateSchemaYml(\n projectPath: string,\n schemaPath: string,\n entry: { name: string; description: string; columns?: SchemaColumn[] }\n): Promise<string> {\n const fullPath = join(projectPath, schemaPath);\n\n let existing: { version?: number; models?: Array<Record<string, unknown>> } = {\n version: 2,\n models: [],\n };\n\n try {\n await access(fullPath);\n const content = await readFile(fullPath, 'utf-8');\n existing = parseYaml(content) as typeof existing;\n if (!existing.models) existing.models = [];\n } catch {\n // File doesn't exist, start fresh\n await mkdir(dirname(fullPath), { recursive: true });\n }\n\n // Check if model already exists\n const idx = existing.models!.findIndex(\n (m) => m.name === entry.name\n );\n\n const modelEntry: Record<string, unknown> = {\n name: entry.name,\n description: entry.description,\n };\n if (entry.columns && entry.columns.length > 0) {\n modelEntry.columns = entry.columns.map((col) => {\n const c: Record<string, unknown> = { name: col.name };\n if (col.description) c.description = col.description;\n if (col.tests && col.tests.length > 0) c.tests = col.tests;\n return c;\n });\n }\n\n if (idx >= 0) {\n existing.models![idx] = modelEntry;\n } else {\n existing.models!.push(modelEntry);\n }\n\n await writeFile(fullPath, stringifyYaml(existing, { indent: 2 }), 'utf-8');\n return fullPath;\n}\n"],"mappings":"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","names":["readFile","join","dirname","readFile","join","dirname","parseYaml"]}