@graphit/cli 0.1.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 (62) hide show
  1. package/dist/api/client.d.ts +16 -0
  2. package/dist/api/client.js +67 -0
  3. package/dist/api/client.js.map +1 -0
  4. package/dist/auth/credentials.d.ts +15 -0
  5. package/dist/auth/credentials.js +34 -0
  6. package/dist/auth/credentials.js.map +1 -0
  7. package/dist/auth/login.d.ts +2 -0
  8. package/dist/auth/login.js +229 -0
  9. package/dist/auth/login.js.map +1 -0
  10. package/dist/auth/token.d.ts +5 -0
  11. package/dist/auth/token.js +42 -0
  12. package/dist/auth/token.js.map +1 -0
  13. package/dist/commands/auth.d.ts +2 -0
  14. package/dist/commands/auth.js +68 -0
  15. package/dist/commands/auth.js.map +1 -0
  16. package/dist/commands/connector.d.ts +2 -0
  17. package/dist/commands/connector.js +97 -0
  18. package/dist/commands/connector.js.map +1 -0
  19. package/dist/commands/dashboard.d.ts +2 -0
  20. package/dist/commands/dashboard.js +124 -0
  21. package/dist/commands/dashboard.js.map +1 -0
  22. package/dist/commands/ds.d.ts +2 -0
  23. package/dist/commands/ds.js +53 -0
  24. package/dist/commands/ds.js.map +1 -0
  25. package/dist/commands/kb.d.ts +2 -0
  26. package/dist/commands/kb.js +259 -0
  27. package/dist/commands/kb.js.map +1 -0
  28. package/dist/commands/query.d.ts +2 -0
  29. package/dist/commands/query.js +61 -0
  30. package/dist/commands/query.js.map +1 -0
  31. package/dist/commands/setup.d.ts +2 -0
  32. package/dist/commands/setup.js +173 -0
  33. package/dist/commands/setup.js.map +1 -0
  34. package/dist/index.d.ts +2 -0
  35. package/dist/index.js +24 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/output/format.d.ts +8 -0
  38. package/dist/output/format.js +30 -0
  39. package/dist/output/format.js.map +1 -0
  40. package/dist/output/json.d.ts +1 -0
  41. package/dist/output/json.js +4 -0
  42. package/dist/output/json.js.map +1 -0
  43. package/dist/output/table.d.ts +2 -0
  44. package/dist/output/table.js +17 -0
  45. package/dist/output/table.js.map +1 -0
  46. package/package.json +38 -0
  47. package/skill/SKILL.md +163 -0
  48. package/skill/cursor/graphit-chart-patterns.mdc +148 -0
  49. package/skill/cursor/graphit-chart-selection.mdc +89 -0
  50. package/skill/cursor/graphit-dashboard-planning.mdc +100 -0
  51. package/skill/cursor/graphit-domain-lenses.mdc +120 -0
  52. package/skill/cursor/graphit-kb-exploration.mdc +75 -0
  53. package/skill/cursor/graphit-sql-reference.mdc +127 -0
  54. package/skill/cursor/graphit-style.mdc +123 -0
  55. package/skill/graphit.mdc +135 -0
  56. package/skill/references/chart-patterns.md +142 -0
  57. package/skill/references/chart-selection.md +83 -0
  58. package/skill/references/dashboard-planning.md +94 -0
  59. package/skill/references/domain-lenses.md +114 -0
  60. package/skill/references/graphit-style.md +117 -0
  61. package/skill/references/kb-exploration.md +69 -0
  62. package/skill/references/sql-reference.md +121 -0
@@ -0,0 +1,148 @@
1
+ ---
2
+ alwaysApply: false
3
+ description: "Graphit: Inline CSS/SVG/canvas implementations for dashboards. No external libraries - the iframe CSP blocks all external resources."
4
+ globs: []
5
+ ---
6
+
7
+ # Chart Patterns
8
+
9
+ Inline CSS/SVG/canvas implementations for dashboards. No external libraries - the iframe CSP blocks all external resources.
10
+
11
+ ## CSS Horizontal Bar
12
+ ```js
13
+ var maxVal = Math.max.apply(null, data.map(function(d){return d.value}));
14
+ data.forEach(function(d) {
15
+ var pct = (d.value / maxVal * 100).toFixed(1);
16
+ container.innerHTML += '<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px">'
17
+ + '<span style="width:100px;font-size:13px;font-weight:500;text-align:right">' + d.name + '</span>'
18
+ + '<div style="flex:1;height:28px;background:var(--graphit-surface-sunken);border-radius:6px;overflow:hidden">'
19
+ + '<div style="width:' + pct + '%;height:100%;background:var(--graphit-accent);border-radius:6px;min-width:2px"></div></div>'
20
+ + '<span style="width:70px;font-size:13px;font-weight:600;text-align:right">' + fmt(d.value) + '</span></div>';
21
+ });
22
+ ```
23
+
24
+ ## CSS Vertical Bar (Column)
25
+ ```js
26
+ var maxVal = Math.max.apply(null, data.map(function(d){return d.value}));
27
+ container.style.cssText = 'display:flex;align-items:flex-end;gap:8px;height:200px;padding-top:20px';
28
+ data.forEach(function(d) {
29
+ var pct = (d.value / maxVal * 100).toFixed(1);
30
+ container.innerHTML += '<div style="flex:1;height:' + pct + '%;background:var(--graphit-accent);border-radius:4px 4px 0 0;'
31
+ + 'position:relative;min-width:30px">'
32
+ + '<span style="position:absolute;top:-20px;left:50%;transform:translateX(-50%);font-size:12px;font-weight:600">'
33
+ + d.value + '</span>'
34
+ + '<span style="position:absolute;bottom:-22px;left:50%;transform:translateX(-50%);font-size:11px;color:var(--graphit-fg-subtle);white-space:nowrap">'
35
+ + d.name + '</span></div>';
36
+ });
37
+ ```
38
+
39
+ ## CSS Stacked Bar
40
+ ```js
41
+ var colors = ['var(--graphit-accent)','#80CBC4','#26A69A','#5FA39B'];
42
+ data.forEach(function(row) {
43
+ var total = row.segments.reduce(function(a,s){return a+s.value},0);
44
+ var html = '<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px">'
45
+ + '<span style="width:100px;font-size:13px;font-weight:500;text-align:right">' + row.name + '</span>'
46
+ + '<div style="flex:1;height:28px;display:flex;border-radius:6px;overflow:hidden">';
47
+ row.segments.forEach(function(s, i) {
48
+ html += '<div style="width:' + (s.value/total*100).toFixed(1) + '%;height:100%;background:' + colors[i%colors.length] + '"></div>';
49
+ });
50
+ html += '</div><span style="width:70px;font-size:13px;font-weight:600;text-align:right">' + total + '</span></div>';
51
+ container.innerHTML += html;
52
+ });
53
+ ```
54
+
55
+ ## SVG Line/Area Chart
56
+ ```js
57
+ var w = 600, h = 200, pad = 20;
58
+ var maxVal = Math.max.apply(null, data.map(function(d){return d.value}));
59
+ var points = data.map(function(d, i) {
60
+ var x = pad + (i / (data.length - 1)) * (w - 2*pad);
61
+ var y = h - pad - ((d.value / maxVal) * (h - 2*pad));
62
+ return x + ',' + y;
63
+ });
64
+ svg.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
65
+ svg.innerHTML =
66
+ '<path d="M' + points.join(' L') + ' L' + (w-pad) + ',' + (h-pad) + ' L' + pad + ',' + (h-pad) + ' Z" fill="rgba(77,182,172,0.15)"/>'
67
+ + '<path d="M' + points.join(' L') + '" fill="none" stroke="var(--graphit-accent)" stroke-width="2"/>';
68
+ ```
69
+
70
+ ## SVG Multi-Series Line
71
+ ```js
72
+ var colors = ['var(--graphit-accent)','#FF7043','#7E57C2','#42A5F5'];
73
+ series.forEach(function(s, si) {
74
+ var pts = s.values.map(function(v, i) {
75
+ var x = pad + (i / (s.values.length - 1)) * (w - 2*pad);
76
+ var y = h - pad - ((v / maxAll) * (h - 2*pad));
77
+ return x + ',' + y;
78
+ });
79
+ svg.innerHTML += '<path d="M' + pts.join(' L') + '" fill="none" stroke="' + colors[si] + '" stroke-width="2"/>';
80
+ });
81
+ ```
82
+
83
+ ## SVG Sparkline (for KPI cards)
84
+ ```js
85
+ var vals = [10,15,12,18,22,19,25];
86
+ var max = Math.max.apply(null,vals), min = Math.min.apply(null,vals);
87
+ var pts = vals.map(function(v,i){
88
+ return (i/(vals.length-1))*120 + ',' + (30 - ((v-min)/(max-min||1))*26 - 2);
89
+ }).join(' L');
90
+ sparkSvg.innerHTML = '<path d="M' + pts + '" fill="none" stroke="var(--graphit-accent)" stroke-width="1.5"/>';
91
+ ```
92
+
93
+ ## Canvas Donut Chart
94
+ ```js
95
+ var ctx = canvas.getContext('2d');
96
+ var slices = [{v:40,c:'var(--graphit-accent)'},{v:25,c:'#80CBC4'},{v:20,c:'#26A69A'},{v:15,c:'#009688'}];
97
+ var total = slices.reduce(function(a,s){return a+s.v},0);
98
+ var cx=150, cy=150, r=120, hole=70, angle = -Math.PI/2;
99
+ slices.forEach(function(s) {
100
+ var arc = (s.v/total)*Math.PI*2;
101
+ ctx.beginPath(); ctx.moveTo(cx,cy);
102
+ ctx.arc(cx,cy,r,angle,angle+arc);
103
+ ctx.fillStyle=s.c; ctx.fill(); angle+=arc;
104
+ });
105
+ ctx.beginPath(); ctx.arc(cx,cy,hole,0,Math.PI*2);
106
+ ctx.fillStyle='white'; ctx.fill();
107
+ ```
108
+
109
+ ## CSS Heatmap Grid
110
+ ```js
111
+ var max = Math.max.apply(null, cells.map(function(c){return c.value}));
112
+ container.style.cssText = 'display:grid;grid-template-columns:repeat(' + cols + ',1fr);gap:2px';
113
+ cells.forEach(function(c) {
114
+ var intensity = c.value / max;
115
+ var bg = 'rgba(77,182,172,' + (0.1 + intensity*0.9).toFixed(2) + ')';
116
+ var fg = intensity > 0.6 ? 'white' : 'var(--graphit-fg)';
117
+ container.innerHTML += '<div style="aspect-ratio:1;border-radius:3px;display:flex;align-items:center;'
118
+ + 'justify-content:center;font-size:11px;font-weight:600;background:' + bg + ';color:' + fg + '">'
119
+ + c.value + '</div>';
120
+ });
121
+ ```
122
+
123
+ ## CSS Funnel
124
+ ```js
125
+ var maxVal = data[0].value;
126
+ data.forEach(function(d, i) {
127
+ var pct = (d.value / maxVal * 100).toFixed(1);
128
+ var rate = i > 0 ? (d.value / data[i-1].value * 100).toFixed(1) + '%' : '';
129
+ container.innerHTML += '<div style="display:flex;align-items:center;gap:12px;margin-bottom:6px">'
130
+ + '<span style="width:120px;font-size:13px;text-align:right">' + d.name + '</span>'
131
+ + '<div style="width:' + pct + '%;height:36px;background:var(--graphit-accent);border-radius:4px;'
132
+ + 'display:flex;align-items:center;padding:0 12px;color:white;font-weight:600;font-size:13px">'
133
+ + d.value.toLocaleString() + '</div>'
134
+ + '<span style="font-size:12px;color:var(--graphit-fg-subtle)">' + rate + '</span></div>';
135
+ });
136
+ ```
137
+
138
+ ## CSS Gauge / Progress
139
+ ```html
140
+ <div style="position:relative;width:200px;height:100px;overflow:hidden">
141
+ <div id="gauge-arc" style="width:200px;height:200px;border-radius:50%;
142
+ background:conic-gradient(var(--graphit-accent) 0% 72%, var(--graphit-surface-sunken) 72% 100%);
143
+ clip-path:inset(0 0 50% 0)"></div>
144
+ <div style="position:absolute;bottom:0;left:50%;transform:translateX(-50%);
145
+ font-size:28px;font-weight:700">72%</div>
146
+ </div>
147
+ ```
148
+ Dynamic: set `background: conic-gradient(var(--graphit-accent) 0% ${pct}%, var(--graphit-surface-sunken) ${pct}% 100%)`.
@@ -0,0 +1,89 @@
1
+ ---
2
+ alwaysApply: false
3
+ description: "Graphit: Consult when choosing chart types for dashboard elements. Matches data shapes to the right visualization."
4
+ globs: []
5
+ ---
6
+
7
+ # Chart Selection
8
+
9
+ Consult when choosing chart types for dashboard elements. Matches data shapes to the right visualization.
10
+
11
+ ## Dimension/Measure Defaults
12
+
13
+ | Dimensions | Measures | Default chart |
14
+ |---|---|---|
15
+ | 1 temporal | 1+ | line |
16
+ | 1 categorical | 1 | bar |
17
+ | 1 categorical | 2+ | bar (grouped or dual axis) |
18
+ | 2 categorical | 1 | bar with series on 2nd dim |
19
+ | 0 | 1 | KPI card |
20
+ | 1 categorical (50+ values) | 1 | table |
21
+
22
+ When ambiguous, propose 2-3 options and ask the user. Do not guess.
23
+
24
+ ## Full Chart Type Table
25
+
26
+ | Data shape | Chart type | Required columns |
27
+ |---|---|---|
28
+ | Time series | line | 1 temporal + 1 numeric |
29
+ | Categories | bar | 1 categorical + 1 numeric |
30
+ | Stacked areas | area | 1 temporal/categorical + 1 numeric |
31
+ | Part-whole (max 5 slices) | pie | 1 categorical + 1 numeric |
32
+ | Single metric | KPI card | 1 numeric |
33
+ | Stages | funnel | 1 categorical + 1 numeric |
34
+ | Target/progress | gauge | 1 numeric |
35
+ | Matrix (unpivoted) | heatmap | 2 categorical + 1 numeric |
36
+ | Hierarchy | treemap | 1 categorical + 1 numeric |
37
+ | Flows | sankey | 2 categorical + 1 numeric |
38
+ | Correlation | scatter | 2 numeric |
39
+ | 3 variables | bubble | 3 numeric |
40
+ | Countries | choropleth map | 1 categorical + 1 numeric |
41
+ | Lat/lng | scatter map | 2 numeric (lat + lng) |
42
+ | Detail/raw data | table | any columns |
43
+
44
+ ## Perception Ranking (Cleveland-McGill)
45
+
46
+ Position > length > angle > area > color.
47
+
48
+ | Goal | First choice | Never |
49
+ |---|---|---|
50
+ | Trend (8+ points) | line | bar, pie |
51
+ | Trend (2-7 points) | bar (column) | line with dots |
52
+ | Compare (12 or fewer categories) | sorted bar | pie, radar |
53
+ | Compare (13-30 categories) | horizontal bar + top-N | column |
54
+ | Part-to-whole | stacked bar, treemap | pie with 6+ |
55
+ | Distribution | histogram, box | pie, line |
56
+ | Correlation | scatter, bubble | dual-axis |
57
+ | Ranking | sorted bar + min-N | pie, treemap |
58
+ | Funnel | funnel + stage% | pie |
59
+ | KPI vs target | big-number + sparkline | gauge (unbounded) |
60
+ | 2D matrix / cohort | heatmap | bar with 100+ |
61
+
62
+ ## Cardinality Guards
63
+
64
+ | Cardinality of categorical dim | Action |
65
+ |---|---|
66
+ | 1-5 | Show data labels, no rotation |
67
+ | 6-20 | Default styling |
68
+ | 21-50 | Rotate labels 45 deg, consider Top-N + Other |
69
+ | 50+ | Use table chart or require a filter |
70
+
71
+ COALESCE categorical dimensions in SQL (`COALESCE(region, 'Other')`) to prevent blank axis labels. For 21+ categories on a bar/line, use a "Top N + Other" window-function pattern:
72
+
73
+ ```sql
74
+ WITH ranked AS (
75
+ SELECT *, ROW_NUMBER() OVER (ORDER BY metric DESC) AS rn
76
+ FROM data
77
+ )
78
+ SELECT CASE WHEN rn <= 10 THEN category ELSE 'Other' END AS category,
79
+ SUM(metric) AS metric
80
+ FROM ranked GROUP BY 1 ORDER BY 2 DESC
81
+ ```
82
+
83
+ ## Hard Caps
84
+
85
+ - Pie: max 5 slices (3 preferred)
86
+ - Line series: max 5-7 (else use small multiples)
87
+ - Stacked bar segments: max 4
88
+ - Categorical colors: max 7 distinct
89
+ - Sort by value DESC unless the axis is ordinal or temporal
@@ -0,0 +1,100 @@
1
+ ---
2
+ alwaysApply: false
3
+ description: "Graphit: Consult before building any multi-chart dashboard. Transforms vague requests into structured, cohesive dashboards."
4
+ globs: []
5
+ ---
6
+
7
+ # Dashboard Planning
8
+
9
+ Consult before building any multi-chart dashboard. Transforms vague requests into structured, cohesive dashboards.
10
+
11
+ ## Frame the Question
12
+
13
+ Every dashboard answers ONE primary question. Name the shape first:
14
+
15
+ | Shape | Lead chart | Example question |
16
+ |---|---|---|
17
+ | Trend | line/area | "How is revenue changing?" |
18
+ | Comparison | sorted bar | "Revenue by segment?" |
19
+ | Ranking | sorted bar + top-N | "Top 10 customers?" |
20
+ | Composition | stacked bar/treemap | "Revenue share by product?" |
21
+ | Distribution | histogram | "Spread of order values?" |
22
+ | Correlation | scatter | "Spend vs conversion?" |
23
+ | Funnel | funnel + stage% | "Signup-to-purchase conversion?" |
24
+ | Cohort | heatmap/overlay lines | "D1/D7/D30 retention?" |
25
+ | Deviation | diverging bar | "Budget vs actual variance?" |
26
+
27
+ Compound intent ("what and why") - sequence trend then root-cause as two charts.
28
+
29
+ ## Pick the Archetype
30
+
31
+ | Audience | Archetype | KPIs | Charts | Interactivity |
32
+ |---|---|---|---|---|
33
+ | Exec / "are we on track" | Strategic | 4-6 hero | 5-9 | Low |
34
+ | Analyst / "why is X" | Analytical | 3-5 | 6-12 + detail | High |
35
+ | Ops / "what's broken now" | Operational | 8-16 dense | 6-12 | Medium |
36
+
37
+ Never mix archetypes on a single page.
38
+
39
+ ## Mandatory Rules
40
+
41
+ - **Time-series**: if data has a date column, include at least 1 line/area trend. A dashboard without a trend is a snapshot - it cannot answer "is it improving?"
42
+ - **Cohort**: if data has install/signup date + retention/LTV, add cohort curves (D0/D3/D7/D30). Period-over-period is NOT a substitute.
43
+ - **Diversity**: 4+ charts must use 3+ distinct chart types. All-bar or all-line wastes the visual encoding.
44
+ - **KPI-first**: lead with 1-3 KPI cards before detail charts unless the user says otherwise.
45
+ - **Distribution over averages**: heavy-tailed metrics (spend, session length, deal size) need percentile bands or tier breakdowns. Median + p90, never bare mean.
46
+ - **Insight titles**: state the takeaway ("Revenue grew 23% Q3"), not the metric name.
47
+ - **Reference lines**: goal-oriented metrics carry target/prior-period/benchmark lines. KPI cards need delta + comparison label.
48
+
49
+ ## Metric Contract
50
+
51
+ Every KPI or measure needs all 6 properties:
52
+
53
+ 1. **Definition** - what counts, what's excluded (bots, refunds, internal)
54
+ 2. **Grain** - per-user-per-day, per-cohort, per-org-per-month
55
+ 3. **Window** - rolling 7d/28d, MTD/QTD/YTD, cohort-anchored
56
+ 4. **Population** - all users, payers, certified, segment
57
+ 5. **Baseline** - vs prior period / target / cohort avg / benchmark
58
+ 6. **Direction** - up=good or up=bad (drives color meaning)
59
+
60
+ Pair lagging + leading: revenue (lagging) needs retention (leading). Replace vanity cumulative counts ("total signups") with cohort/period rates. Never show AVG of a rate column - rates must be weighted. Show denominators on rates (or hide when N < 30).
61
+
62
+ ## Anti-Patterns to Block
63
+
64
+ - Pie with 6+ slices (max 5, prefer 3)
65
+ - 3D charts of any kind
66
+ - Dual Y-axis without clear justification (use small multiples)
67
+ - Truncated bar baselines (always start at 0)
68
+ - Stacked bar with 5+ segments
69
+ - Gauge for unbounded KPIs (use bullet chart)
70
+ - Rainbow palette on ordinal data
71
+ - Red-green without shape encoding (accessibility)
72
+ - Aggregate-only without segment drill-down (Simpson's paradox)
73
+ - Rate without denominator visible
74
+ - KPI without baseline comparison
75
+ - Vanity cumulative counts ("total signups since launch")
76
+
77
+ ## Asking Good Questions
78
+
79
+ Purpose before data. The first response should mirror the user's intent and ask ONE narrowing question - never start querying immediately.
80
+
81
+ **Batch related questions** - ask multiple things at once instead of sequential single questions. Each option should lead to a different path, not variations of the same thing.
82
+
83
+ **Use open questions for exploration** - "What business decision will this dashboard support?" beats presenting a restrictive multiple-choice.
84
+
85
+ **Clarification triggers:**
86
+ - User says "revenue" - ask: bookings, ARR, or GAAP recognized?
87
+ - User says "conversion" - ask: what's the start and end event?
88
+ - User says "active users" - ask: what defines active? (logged in? performed action? within what window?)
89
+ - User mentions MQL/SQL - ask: how does your org define the handoff? (Same column can mean 13% or 40%)
90
+
91
+ ## Pre-Build Checklist
92
+
93
+ Before generating the HTML:
94
+ - [ ] Question shape stated (trend, comparison, funnel, etc.)
95
+ - [ ] Archetype + audience identified
96
+ - [ ] Time-series present if date column exists
97
+ - [ ] Cohort view present if install/signup + retention data
98
+ - [ ] 3+ chart types for 4+ graphs
99
+ - [ ] Every KPI has definition + baseline + direction
100
+ - [ ] No anti-patterns present
@@ -0,0 +1,120 @@
1
+ ---
2
+ alwaysApply: false
3
+ description: "Graphit: Consult when the user's data signals match a specific business domain. Each lens provides domain-specific metrics, chart types, and anti-patterns that make dashboards feel expert-built."
4
+ globs: []
5
+ ---
6
+
7
+ # Domain Lenses
8
+
9
+ Consult when the user's data signals match a specific business domain. Each lens provides domain-specific metrics, chart types, and anti-patterns that make dashboards feel expert-built.
10
+
11
+ Detect the domain from column names and user intent. Apply universal planning rules first (see dashboard-planning.md), then layer domain-specific patterns.
12
+
13
+ ---
14
+
15
+ ## Marketing & Attribution
16
+
17
+ **Signals:** utm_*, channel, creative_id, spend, cpi, impressions, clicks, roas, cac, ltv, attribution_*, skan_*, conversions, ctr.
18
+
19
+ **Key metrics:**
20
+
21
+ | Metric | Formula pattern | Chart |
22
+ |---|---|---|
23
+ | Blended CAC / Paid CAC | spend / new_customers | bar/KPI, target + YoY |
24
+ | ROAS D7/D30/D90 | revenue_D0-N / spend | cumulative cohort line + 100% breakeven |
25
+ | MER | revenue / marketing_spend | KPI |
26
+ | LTV:CAC Ratio | LTV / CAC | bar, floor 3:1 |
27
+ | Payback Period | days to recoup CAC | cumulative line |
28
+
29
+ **Must-have charts:** Cumulative cohort ROAS curve with 100% breakeven line. Channel-mix sorted bar (never pie for 6+). Creative scorecard table. Attribution model comparison stacked bar.
30
+
31
+ **Anti-patterns:** Mismatched attribution windows across channels (7d vs 30d = 30% artifact). ROAS without payback context. Summing self-reported channel conversions without dedup (totals 110-180%). Spend without incrementality context. Creative table without refresh ratio.
32
+
33
+ **Clarify first:** Attribution model + window? ROAS gross or margin-adjusted?
34
+
35
+ ---
36
+
37
+ ## Financial / Executive
38
+
39
+ **Signals:** revenue, mrr, arr, cogs, gross_margin, opex, ebitda, budget, forecast, actual, variance, runway, burn, cash_balance, gl_account.
40
+
41
+ **Key metrics:**
42
+
43
+ | Metric | Formula pattern | Chart |
44
+ |---|---|---|
45
+ | P&L Cascade | Rev->COGS->GP->Opex->EBITDA->Net | waterfall |
46
+ | Variance $/% | Actual-Plan, sign-aware per line type | semantic-color bar |
47
+ | GM% by segment | (Rev-COGS)/Rev per product/cohort | line + small multiples |
48
+ | Burn Multiple | net_burn / net_new_ARR | KPI + trend |
49
+ | Runway Months | Cash / abs(Net Burn) | KPI color-banded |
50
+ | Rule of 40 | Growth% + EBITDA margin% | KPI + trend |
51
+
52
+ **Must-have charts:** Waterfall (P&L, variance bridge, ARR bridge). Bullet chart for KPI vs target. Plan vs Forecast vs Actual (3 series, never 2). Variance bars with semantic color (green=favorable, red=unfavorable regardless of direction).
53
+
54
+ **Anti-patterns:** Direction-based green/red instead of favorable/unfavorable. Blended GM hiding AI inference cost compression. YTD comparison in Jan/Dec (use TTM). Cash burn without runway. Revenue without ASC 606 distinction. "Three-line" missing Forecast (Plan vs Actual vs Forecast = 3).
55
+
56
+ **Clarify first:** Revenue = bookings, ARR, or GAAP recognized?
57
+
58
+ ---
59
+
60
+ ## Product / Growth
61
+
62
+ **Signals:** install_date, signup_at, cohort_*, retention_d*, dau, mau, activation_*, payer_*, funnel_step, session_*, ltv, arpu, arpdau.
63
+
64
+ **Key metrics:**
65
+
66
+ | Metric | Formula pattern | Chart |
67
+ |---|---|---|
68
+ | D1/D7/D30 Retention | active_dN / cohort_size | heatmap or overlay |
69
+ | Stickiness | DAU/MAU | line + ref bands |
70
+ | ARPDAU | revenue_day / DAU_day | line + 7d MA |
71
+ | Payer Conversion D7 | first_payers_d7 / cohort | cumulative line |
72
+ | Whale Share | sum(rev) top-X% / total_rev | Lorenz / decile bar |
73
+ | Activation Rate | reached_aha / signups | cohort heatmap |
74
+
75
+ **Must-have charts:** Cohort retention heatmap (X=days since signup, Y=cohort, color=retention%). Cumulative cohort overlay (max 6 series). Lorenz/decile bar for revenue concentration. FTUE funnel with per-cohort drift.
76
+
77
+ **Anti-patterns:** Period retention instead of cohort retention. Bare ARPU without distribution (whales break every mean). Mean on heavy-tailed metrics (use median + p90). LTV without cohort horizon day. Vanity cumulative tiles.
78
+
79
+ ---
80
+
81
+ ## Operational Monitoring
82
+
83
+ **Signals:** latency, p50/p95/p99, error_rate, slo, uptime, mttr, throughput, oee, cycle_time, otif, fill_rate, stockout, deployment_frequency.
84
+
85
+ **Key metrics:**
86
+
87
+ | Metric | Formula pattern | Chart |
88
+ |---|---|---|
89
+ | Latency p50/p95/p99 | percentile buckets | multi-percentile line + SLO |
90
+ | Error Budget | 100% - (errors/total) rolling 28d | bullet chart |
91
+ | DORA Quartet | deploy freq, lead time, CFR, recovery | 4-tile KPI |
92
+ | OEE | Availability x Performance x Quality | KPI + 3-tile decomp |
93
+ | OTIF | on-time AND in-full | trend + decomp stacked bar |
94
+
95
+ **Must-have charts:** Multi-percentile line + SLO threshold band. Bullet chart (not gauge). Latency heatmap. Error sources Pareto.
96
+
97
+ **Anti-patterns:** Averages without percentiles. KPI without threshold band. Vanity uptime % (use error-budget framing). Aggregating across segments (global 0.4% hides a region at 8%).
98
+
99
+ ---
100
+
101
+ ## Sales / Pipeline
102
+
103
+ **Signals:** opp_id, stage, amount, close_date, forecast_category, mql, sql, arr, acv, tcv, quota, attainment, loss_reason, expansion_arr.
104
+
105
+ **Key metrics:**
106
+
107
+ | Metric | Formula pattern | Chart |
108
+ |---|---|---|
109
+ | Pipeline Coverage | open_pipe / quota_remaining | KPI, target=1/win_rate |
110
+ | Sales Velocity | (opps x win_rate x avg_acv) / cycle_days | KPI + trend |
111
+ | Win Rate by ACV | won / (won+lost) per band | sorted bar, min-N 30 |
112
+ | Quota Attainment | closed_won / quota | histogram (bimodal!) |
113
+ | Net New ARR | new + expansion - contraction - churn | waterfall |
114
+ | Forecast MAPE | mean(abs(actual-forecast)/actual) | line + target |
115
+
116
+ **Must-have charts:** Net New ARR waterfall (start + new + expansion - contraction - churn = end). Forecast convergence curve (13 weeks). Coverage curve by quarter-week. Win rate by ACV band.
117
+
118
+ **Anti-patterns:** Fixed 3x coverage instead of 1/win_rate per segment. ACV/ARR/TCV/bookings conflated. Quota attainment as bare mean (bimodal - use histogram). Stage-weighted pipe using CRM defaults (30-50% overstate). "No decision" lumped (40-60% of B2B - split it).
119
+
120
+ **Clarify first:** MQL/SQL definitions (same column = 13-40% conversion depending on org). Revenue = bookings vs ARR vs GAAP. Coverage target: derive from historical win rate per segment, not fixed multiple.
@@ -0,0 +1,75 @@
1
+ ---
2
+ alwaysApply: false
3
+ description: "Graphit: Consult when starting a dashboard build. The Knowledge Base contains reusable metrics, dimensions, rules, and table schemas that ensure consistent formulas across dashboards."
4
+ globs: []
5
+ ---
6
+
7
+ # KB Exploration
8
+
9
+ Consult when starting a dashboard build. The Knowledge Base contains reusable metrics, dimensions, rules, and table schemas that ensure consistent formulas across dashboards.
10
+
11
+ ## KB-First Discovery
12
+
13
+ Before writing raw SQL with inline aggregations or column references:
14
+
15
+ 1. List what exists: `graphit kb list metrics`, `graphit kb list dimensions`, `graphit kb list rules`
16
+ 2. If a KB metric matches the user's request, use its formula: `graphit kb get metric REVENUE`
17
+ 3. If no match exists, consider whether to build the dashboard from raw columns or suggest KB asset creation first
18
+ 4. Explore relationships: `graphit kb explore metric REVENUE` shows which tables and dimensions connect to a metric
19
+
20
+ KB assets ensure consistent formulas across dashboards. If 3 charts all need `SUM(ORDERS.AMOUNT)`, one TOTAL_REVENUE metric is better than 3 inline aggregations.
21
+
22
+ ## Metric vs Dimension
23
+
24
+ | Property | Metric | Dimension |
25
+ |---|---|---|
26
+ | Formula | Aggregation required (SUM, COUNT, AVG, MIN, MAX) | Row-level only (no aggregates) |
27
+ | Table scope | Can reference multiple tables | Exactly one table |
28
+ | Purpose | Measures - what you count/sum | Grouping axes - how you slice |
29
+ | Example | `SUM(ORDERS.AMOUNT)` | `DATE_TRUNC('month', EVENTS.EVENT_TS)` |
30
+ | Invalid | `ORDERS.AMOUNT` (no aggregate) | `SUM(EVENTS.DURATION)` (has aggregate) |
31
+
32
+ ## Naming Conventions
33
+
34
+ All KB assets use UPPER_SNAKE_CASE. The names are auto-sanitized:
35
+
36
+ | Pattern | Example | Use when |
37
+ |---|---|---|
38
+ | `TOTAL_*` | `TOTAL_REVENUE`, `TOTAL_ORDERS` | Sum aggregations |
39
+ | `AVG_*` | `AVG_ORDER_VALUE` | Average metrics |
40
+ | `COUNT_*` | `COUNT_ACTIVE_USERS` | Count metrics |
41
+ | `*_RATE` | `CONVERSION_RATE`, `CHURN_RATE` | Ratios/percentages |
42
+
43
+ ## Reuse Over Reinvention
44
+
45
+ When building multi-chart dashboards, identify shared concepts:
46
+ - If 3 charts use `SUM(ORDERS.AMOUNT)`, reference one TOTAL_REVENUE metric
47
+ - If 2 charts group by `DATE_TRUNC('month', TS)`, reference one MONTHLY dimension
48
+ - Discover existing assets: `graphit kb search "revenue"`
49
+
50
+ ## When to Suggest KB Asset Creation
51
+
52
+ | Signal | Propose |
53
+ |---|---|
54
+ | User requests a business metric with no KB match | Metric - reusable formula |
55
+ | User groups by a derived expression | Dimension - consistent grouping |
56
+ | User describes a business rule ("active = logged in within 30d") | Rule - applied to future queries |
57
+ | User uses a business term not in KB | Synonym - maps colloquial to defined |
58
+
59
+ ## Formula Syntax
60
+
61
+ Metrics require `TABLE.COLUMN` references with UPPERCASE naming:
62
+
63
+ ```sql
64
+ -- Valid metric formulas
65
+ SUM(ORDERS.AMOUNT)
66
+ COUNT(DISTINCT EVENTS.USER_ID) WHERE EVENTS.EVENT_TS >= DATEADD(day, -30, CURRENT_DATE)
67
+ SUM(ORDERS.REVENUE) / NULLIF(SUM(ORDERS.COST), 0)
68
+
69
+ -- Valid dimension formulas (no aggregates)
70
+ EVENTS.PLATFORM
71
+ DATE_TRUNC('month', EVENTS.EVENT_TS)
72
+ CASE WHEN USERS.AGE >= 18 THEN 'adult' ELSE 'minor' END
73
+ ```
74
+
75
+ Conditionals use CASE WHEN (not FILTER WHERE - Snowflake doesn't support it). Always guard division with NULLIF.
@@ -0,0 +1,127 @@
1
+ ---
2
+ alwaysApply: false
3
+ description: "Graphit: Consult when writing queries. Data source queries (`graphit query --ds`) run DuckDB. Warehouse queries (`graphit query --warehouse`) run Snowflake. You MUST use the correct dialect."
4
+ globs: []
5
+ ---
6
+
7
+ # SQL Reference
8
+
9
+ Consult when writing queries. Data source queries (`graphit query --ds`) run DuckDB. Warehouse queries (`graphit query --warehouse`) run Snowflake. You MUST use the correct dialect.
10
+
11
+ ## DuckDB vs Snowflake Translation
12
+
13
+ | Snowflake | DuckDB equivalent |
14
+ |---|---|
15
+ | `DATEADD(day, N, date)` | `date + INTERVAL N DAY` |
16
+ | `DATEDIFF(day, a, b)` | `DATE_DIFF('day', a, b)` |
17
+ | `NVL(a, b)` | `COALESCE(a, b)` |
18
+ | `NVL2(a, b, c)` | `CASE WHEN a IS NOT NULL THEN b ELSE c END` |
19
+ | `IFF(cond, a, b)` | `CASE WHEN cond THEN a ELSE b END` |
20
+ | `TO_CHAR(date, fmt)` | `strftime(date, fmt)` |
21
+ | `ARRAY_AGG(x) WITHIN GROUP (ORDER BY y)` | `LIST(x ORDER BY y)` |
22
+ | `LISTAGG(col, sep)` | `STRING_AGG(col, sep ORDER BY ...)` |
23
+ | `COUNT_IF(cond)` | `COUNT(*) FILTER (WHERE cond)` |
24
+ | `GENERATOR(ROWCOUNT => N)` | `generate_series(0, N-1)` |
25
+ | `CONVERT_TIMEZONE('tz', ts)` | `timezone('tz', ts)` |
26
+
27
+ ### JSON / VARIANT Access
28
+
29
+ | Engine | Syntax | Example |
30
+ |---|---|---|
31
+ | Snowflake | Colon notation | `col:field::STRING`, `col:parent:child::NUMBER` |
32
+ | DuckDB | Arrow operators | `col->>'field'` (text), `col->'field'` (JSON) |
33
+
34
+ NEVER use `->>` in Snowflake or `:field::STRING` in DuckDB.
35
+
36
+ ### Timezone
37
+
38
+ | Engine | Correct | Wrong |
39
+ |---|---|---|
40
+ | DuckDB | `timezone('Asia/Jerusalem', ts_col)` | `AT TIME ZONE` chains (reverses direction on TIMESTAMPTZ) |
41
+ | Snowflake | `CONVERT_TIMEZONE('Asia/Jerusalem', ts_col)` (2-arg for _TZ) | `AT TIME ZONE` |
42
+
43
+ ## DuckDB Superpowers (not in Snowflake)
44
+
45
+ | Feature | Example |
46
+ |---|---|
47
+ | `GROUP BY ALL` | `SELECT region, SUM(sales) FROM t GROUP BY ALL` |
48
+ | `SELECT * EXCLUDE` | `SELECT * EXCLUDE (internal_id) FROM t` |
49
+ | `FILTER (WHERE)` | `COUNT(*) FILTER (WHERE status='active')` |
50
+ | `UNION BY NAME` | `SELECT ... UNION ALL BY NAME SELECT ...` |
51
+
52
+ ## Snowflake Notes
53
+
54
+ - Use `DATE_TRUNC('month', date)` for grouping (not EXTRACT/MONTH/YEAR)
55
+ - Snowflake does NOT support `FILTER (WHERE)` - use `CASE WHEN` instead
56
+ - `COUNT_IF` MUST receive a boolean expression, not a raw INT column. Use `COUNT_IF(is_active = 1)`, not `COUNT_IF(is_active)`
57
+ - String matching: prefer `ILIKE` (case-insensitive) over `LIKE`
58
+
59
+ ## SQL Formatting Standards (Both Engines)
60
+
61
+ - Keywords UPPERCASE: `SELECT`, `FROM`, `WHERE`, `JOIN`, `GROUP BY`, `ORDER BY`
62
+ - Table/column names UPPERCASE: `ORDERS`, `CUSTOMER_ID`, `TOTAL_REVENUE`
63
+ - Qualify every column with its table alias: `o.ORDER_DATE`, not bare `ORDER_DATE`
64
+ - Use descriptive aliases: `total_revenue`, not `sum1`
65
+ - String literals in single quotes: `'active'`, `'2024-01-01'`
66
+ - Non-ASCII identifiers MUST be double-quoted: `SELECT * FROM "hebrew_table"`
67
+
68
+ ## CTE Pattern
69
+
70
+ Use CTEs for queries with 3+ JOINs or complex subqueries:
71
+
72
+ ```sql
73
+ WITH MONTHLY_ORDERS AS (
74
+ SELECT o.CUSTOMER_ID, DATE_TRUNC('month', o.ORDER_DATE) AS MONTH,
75
+ SUM(o.AMOUNT) AS MONTHLY_REVENUE
76
+ FROM ORDERS o WHERE o.STATUS = 'complete'
77
+ GROUP BY o.CUSTOMER_ID, DATE_TRUNC('month', o.ORDER_DATE)
78
+ )
79
+ SELECT c.SEGMENT, AVG(mo.MONTHLY_REVENUE) AS AVG_MONTHLY_REVENUE
80
+ FROM MONTHLY_ORDERS mo JOIN CUSTOMERS c ON mo.CUSTOMER_ID = c.ID
81
+ GROUP BY c.SEGMENT ORDER BY AVG_MONTHLY_REVENUE DESC
82
+ ```
83
+
84
+ ## ORDER BY Rules (Always Include)
85
+
86
+ | Query type | ORDER BY |
87
+ |---|---|
88
+ | Time series | `ORDER BY date_column ASC` |
89
+ | Category breakdowns | `ORDER BY metric_column DESC` |
90
+ | Rankings / top N | `ORDER BY metric_column DESC LIMIT N` |
91
+
92
+ Do NOT add LIMIT unless the user requests it or the query is a ranking.
93
+
94
+ ## Gap-Filling Pattern (DuckDB)
95
+
96
+ For heatmaps or continuous time-series needing every cell (even zeros):
97
+
98
+ ```sql
99
+ WITH grid AS (
100
+ SELECT UNNEST(generate_series(0, 23)) AS hour_of_day
101
+ )
102
+ SELECT g.hour_of_day, COALESCE(t.count, 0) AS count
103
+ FROM grid g
104
+ LEFT JOIN (SELECT hour, COUNT(*) AS count FROM data_source GROUP BY 1) t
105
+ ON g.hour_of_day = t.hour
106
+ ORDER BY 1
107
+ ```
108
+
109
+ Date series:
110
+ ```sql
111
+ SELECT UNNEST(generate_series(
112
+ CURRENT_DATE - INTERVAL 30 DAY, CURRENT_DATE, INTERVAL 1 DAY
113
+ ))::DATE AS date
114
+ ```
115
+
116
+ ## Subquery Column Scope
117
+
118
+ The outer SELECT can ONLY reference columns the subquery exposes (its aliases). Base-table columns aggregated away in the subquery do NOT exist at the outer level.
119
+
120
+ ## Data Source Routing
121
+
122
+ | Situation | Command | Speed |
123
+ |---|---|---|
124
+ | Table has a cached data source | `graphit query "SQL" --ds <id>` | ~100ms, DuckDB |
125
+ | No data source | `graphit query "SQL" --warehouse --connection <id>` | ~10s, Snowflake |
126
+
127
+ Always prefer cached data sources. Check with `graphit ds list`. If no data source covers the table, suggest creating one for future speed.