@overscore/cli 0.7.0 → 0.8.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.
package/dist/index.js CHANGED
@@ -711,97 +711,87 @@ async function analysisApiRequest(cfg, method, urlPath, body) {
711
711
  }
712
712
  return data;
713
713
  }
714
- const ANALYSIS_CLAUDE_MD = `# Overscore Analysis: {{TITLE}}
715
-
716
- This is an **Overscore Analysis** a one-off, frozen investigation. Analyses are NOT dashboards:
717
-
718
- - **No React project, no \`package.json\`, no \`useQuery\` hook.**
719
- - **No scheduled refreshes.** Numbers are hard-coded into \`report.html\` at publish time.
720
- - The deliverable is a single self-contained HTML file that anyone can open in a browser.
721
- - Analyses are meant to be shared (Slack, email) and reopened later for refresh-and-republish.
722
-
723
- ## CRITICAL: Always maintain analysis-log.md
724
-
725
- \`analysis-log.md\` is the **recoverable session state** for this analysis. As you work, you MUST append to it. Future sessions (including yourself, weeks from now, after the user runs \`analysis pull\`) read this file to know:
726
-
727
- - What question the analysis is investigating
728
- - Which BigQuery tables are involved
729
- - Every SQL query you ran (paste the exact SQL)
730
- - Conclusions reached and how you got there
731
- - Open questions still being chased
732
-
733
- Use this rough structure:
734
-
735
- \`\`\`markdown
736
- ## YYYY-MM-DD — Initial exploration
737
-
738
- **Question:** <the user's question>
739
-
740
- ### Tables involved
741
- - \`dataset.table_name\` — what it contains, key columns
742
-
743
- ### Queries run
744
- 1. <one-line description of what this query investigated>
745
- \`\`\`sql
746
- SELECT ...
747
- \`\`\`
748
-
749
- ### Conclusions
750
- - ...
751
-
752
- ### Open questions
753
- - ...
754
- \`\`\`
755
-
756
- Update it **as you go**, not at the end. Treat every meaningful exploration step as a new entry.
757
-
758
- ## Where data context lives
759
-
760
- \`.claude/rules/company-context.md\` was pulled from this analysis's parent project at scaffold time. It contains the project's table definitions, business logic, and data quirks shared across all dashboards and analyses in this project. **Read it first** before exploring blindly.
761
-
762
- ## Generating report.html
763
-
764
- When the user is happy with the work, generate \`report.html\` as a **fully self-contained** static HTML file:
765
-
766
- - Inline all CSS (no external stylesheets, no Tailwind CDN at runtime).
767
- - Hard-code the actual numbers and chart data into the HTML — **no \`fetch()\`, no API calls, no external scripts that depend on data being available at view time.**
768
- - Charts: inline SVG, or embedded base64 PNG, or a self-contained chart library bundled inline. Recharts/D3 from CDN is OK only if the data is already baked into the page.
769
- - Dark theme to match Overscore's brand: near-black background (\`#0a0a0f\`), white text, violet/blue/emerald accents, generous spacing.
770
- - Title at top, executive summary, KPI cards, charts, supporting tables, conclusions.
771
- - Footer with the date the analysis was generated.
772
-
773
- The HTML is what gets shared in Slack and what stakeholders see. The numbers must be visible to anyone who opens the file, with no logins, no JS data fetching, no broken external dependencies.
774
-
775
- ## Publishing
776
-
777
- When the analysis is ready, **tell the user to run** (do not run it yourself without asking):
778
-
779
- \`\`\`bash
780
- npx @overscore/cli analysis publish
781
- \`\`\`
782
-
783
- This uploads \`report.html\` and the entire bundle (this CLAUDE.md, \`analysis-log.md\`, etc.) to Overscore. The analysis becomes available at \`https://<project>.overscore.dev/analysis/{{SLUG}}\`. Re-publishing overwrites the previous version.
784
-
785
- ## When the user wants to turn this into a dashboard
786
-
787
- Analyses are static. They have **no React project**, **no scheduled refreshes**, and **no \`useQuery\`**. If the user asks to "make this a real dashboard" or "turn this into something that updates," **do NOT add React code to this folder.** Instead:
788
-
789
- 1. Tell them: "Analyses are frozen snapshots. To make this a tracked dashboard, we need to scaffold a new project — the analysis stays as the original investigation."
790
- 2. Tell them to run, from the parent directory:
791
- \`\`\`bash
792
- cd .. && npx create-overscore
793
- \`\`\`
794
- Use the same project slug this analysis belongs to (found in \`.overscore/analysis.json\`).
795
- 3. Once they're in the new dashboard folder, hand off a seed message that includes: this analysis's question, the tables explored (from \`analysis-log.md\`), the key queries that mattered, and the conclusions reached. The new dashboard's Claude session can use this as a starting point.
796
-
797
- ## Do NOT
798
-
799
- - Add \`package.json\`, \`node_modules\`, or any React/Vite scaffolding to this folder.
800
- - Use \`useQuery()\` or any Overscore client hooks — they don't exist here.
801
- - Call BigQuery from inside \`report.html\` at view time. Bake the numbers in.
802
- - Publish without showing the user \`report.html\` in a browser first and getting their approval.
803
- - Forget to update \`analysis-log.md\` as you work.
804
- `;
714
+ /**
715
+ * Fetch hub-managed rule files (company-context, sql-style) for a project
716
+ * and write them into the target analysis folder, overwriting whatever's
717
+ * there. Best-effort and non-fatal — if the hub is unreachable, the existing
718
+ * files (template defaults or bundle contents) stay in place.
719
+ *
720
+ * Called from both `analysis new` (after the template walker) and `analysis
721
+ * pull` (after the bundle extraction) so hub overrides always reflect the
722
+ * latest state regardless of how the analysis was created.
723
+ */
724
+ async function syncHubManagedRules(targetDir, baseUrl, projectSlug, apiKey) {
725
+ const authHeader = { Authorization: `Bearer ${apiKey}` };
726
+ const rulesDir = path.join(targetDir, ".claude", "rules");
727
+ fs.mkdirSync(rulesDir, { recursive: true });
728
+ // Company context null means "no company context set yet"; leave any
729
+ // existing file alone in that case.
730
+ try {
731
+ const ctxRes = await fetch(`${baseUrl}/api/projects/${projectSlug}/context`, { headers: authHeader });
732
+ if (ctxRes.ok) {
733
+ const ctxData = (await ctxRes.json());
734
+ if (ctxData.context_md) {
735
+ fs.writeFileSync(path.join(rulesDir, "company-context.md"), ctxData.context_md);
736
+ }
737
+ }
738
+ }
739
+ catch {
740
+ // Non-fatal
741
+ }
742
+ // SQL style override — null means "use the CLI default"; leave the
743
+ // template/bundle file alone in that case.
744
+ try {
745
+ const sqlRes = await fetch(`${baseUrl}/api/projects/${projectSlug}/sql-style`, { headers: authHeader });
746
+ if (sqlRes.ok) {
747
+ const sqlData = (await sqlRes.json());
748
+ if (sqlData.sql_style_md) {
749
+ fs.writeFileSync(path.join(rulesDir, "sql-style.md"), sqlData.sql_style_md);
750
+ }
751
+ }
752
+ }
753
+ catch {
754
+ // Non-fatal
755
+ }
756
+ }
757
+ /**
758
+ * Recursively copies a template directory to a target directory, substituting
759
+ * `{{KEY}}` placeholders in text files. Mirrors directory structure.
760
+ *
761
+ * Substitutions are applied to .md, .html, .json, .yml, .yaml files. Other
762
+ * file types are copied byte-for-byte.
763
+ */
764
+ function scaffoldFromTemplate(templateDir, targetDir, substitutions) {
765
+ const TEXT_EXTENSIONS = new Set([".md", ".html", ".json", ".yml", ".yaml"]);
766
+ function substitute(content) {
767
+ let out = content;
768
+ for (const [key, value] of Object.entries(substitutions)) {
769
+ out = out.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
770
+ }
771
+ return out;
772
+ }
773
+ function walk(srcDir, destDir) {
774
+ fs.mkdirSync(destDir, { recursive: true });
775
+ for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
776
+ const srcPath = path.join(srcDir, entry.name);
777
+ const destPath = path.join(destDir, entry.name);
778
+ if (entry.isDirectory()) {
779
+ walk(srcPath, destPath);
780
+ }
781
+ else if (entry.isFile()) {
782
+ const ext = path.extname(entry.name).toLowerCase();
783
+ if (TEXT_EXTENSIONS.has(ext)) {
784
+ const content = fs.readFileSync(srcPath, "utf8");
785
+ fs.writeFileSync(destPath, substitute(content));
786
+ }
787
+ else {
788
+ fs.copyFileSync(srcPath, destPath);
789
+ }
790
+ }
791
+ }
792
+ }
793
+ walk(templateDir, targetDir);
794
+ }
805
795
  async function analysisNew(title) {
806
796
  if (!title) {
807
797
  console.error('\n Usage: npx @overscore/cli analysis new "<title>"\n');
@@ -819,10 +809,18 @@ async function analysisNew(title) {
819
809
  console.error(`\n Error: Directory "${folderName}" already exists.\n`);
820
810
  process.exit(1);
821
811
  }
812
+ // Scaffold from the template tree shipped with the package.
813
+ // __dirname after build is dist/, so the templates dir is one level up.
814
+ const distDir = path.dirname(new URL(import.meta.url).pathname);
815
+ const pkgRoot = path.resolve(distDir, "..");
816
+ const templateDir = path.join(pkgRoot, "templates", "analysis");
817
+ if (!fs.existsSync(templateDir)) {
818
+ console.error(`\n Error: Bundled analysis template not found at ${templateDir}\n`);
819
+ process.exit(1);
820
+ }
822
821
  fs.mkdirSync(targetDir, { recursive: true });
823
- fs.mkdirSync(path.join(targetDir, ".claude", "rules"), { recursive: true });
824
822
  fs.mkdirSync(path.join(targetDir, ".overscore"), { recursive: true });
825
- // Write analysis.json metadata
823
+ // Write analysis.json metadata (not part of the template — has dynamic id)
826
824
  fs.writeFileSync(path.join(targetDir, ".overscore", "analysis.json"), JSON.stringify({
827
825
  id: data.id,
828
826
  slug: data.slug,
@@ -830,65 +828,18 @@ async function analysisNew(title) {
830
828
  project_slug: data.project_slug,
831
829
  created_at: new Date().toISOString(),
832
830
  }, null, 2));
833
- // Write CLAUDE.md from template
834
- const claudeMd = ANALYSIS_CLAUDE_MD
835
- .replace(/\{\{TITLE\}\}/g, data.title)
836
- .replace(/\{\{SLUG\}\}/g, data.slug);
837
- fs.writeFileSync(path.join(targetDir, ".claude", "CLAUDE.md"), claudeMd);
838
- // Empty analysis log seeded with the question
831
+ // Walk the template tree, substituting placeholders
839
832
  const today = new Date().toISOString().slice(0, 10);
840
- const analysisLog = `# ${data.title}
841
-
842
- ## ${today} — Initial exploration
843
-
844
- **Question:** ${data.title}
845
-
846
- ### Tables involved
847
- - _(populate as you explore see \`.claude/rules/company-context.md\` for what's available)_
848
-
849
- ### Queries run
850
- - _(append every meaningful query here, with one-line description + SQL)_
851
-
852
- ### Conclusions
853
- - _(empty so far)_
854
-
855
- ### Open questions
856
- - _(empty so far)_
857
- `;
858
- fs.writeFileSync(path.join(targetDir, "analysis-log.md"), analysisLog);
859
- // Empty report.html stub
860
- fs.writeFileSync(path.join(targetDir, "report.html"), `<!doctype html>
861
- <html lang="en">
862
- <head>
863
- <meta charset="utf-8" />
864
- <title>${data.title}</title>
865
- <meta name="viewport" content="width=device-width, initial-scale=1" />
866
- <style>
867
- body { background: #0a0a0f; color: #fff; font-family: -apple-system, BlinkMacSystemFont, sans-serif; padding: 4rem 2rem; max-width: 800px; margin: 0 auto; }
868
- h1 { font-weight: 600; }
869
- p { color: rgba(255,255,255,0.6); }
870
- </style>
871
- </head>
872
- <body>
873
- <h1>${data.title}</h1>
874
- <p>This analysis is empty. Have Claude do the work and regenerate this file.</p>
875
- </body>
876
- </html>
877
- `);
878
- // Pull project's company-context.md (best-effort, non-fatal)
879
- try {
880
- const baseUrl = cfg.apiUrl.replace(/\/api$/, "");
881
- const ctxRes = await fetch(`${baseUrl}/api/projects/${data.project_slug}/context`, { headers: { Authorization: `Bearer ${cfg.apiKey}` } });
882
- if (ctxRes.ok) {
883
- const ctxData = (await ctxRes.json());
884
- if (ctxData.context_md) {
885
- fs.writeFileSync(path.join(targetDir, ".claude", "rules", "company-context.md"), ctxData.context_md);
886
- }
887
- }
888
- }
889
- catch {
890
- // Non-fatal
891
- }
833
+ scaffoldFromTemplate(templateDir, targetDir, {
834
+ TITLE: data.title,
835
+ SLUG: data.slug,
836
+ PROJECT_SLUG: data.project_slug,
837
+ DATE: today,
838
+ });
839
+ // Pull hub-managed rules (company context + sql style override). Best-effort
840
+ // and non-fatal template defaults stay in place if the hub is unreachable.
841
+ const baseUrl = cfg.apiUrl.replace(/\/api$/, "");
842
+ await syncHubManagedRules(targetDir, baseUrl, data.project_slug, cfg.apiKey);
892
843
  console.log(` Analysis created: ${data.title}
893
844
  Slug: ${data.slug}
894
845
  Project: ${data.project_slug}
@@ -1029,6 +980,11 @@ async function analysisPull(slug) {
1029
980
  // Non-fatal
1030
981
  }
1031
982
  }
983
+ // Refresh hub-managed rule files after restoring from the bundle. The
984
+ // bundle contains whatever was set at publish time; this overwrites them
985
+ // with the latest hub state so company context and SQL style stay in
986
+ // sync across pulls. Best-effort and non-fatal.
987
+ await syncHubManagedRules(targetDir, baseUrl, projectSlug, cfg.apiKey);
1032
988
  console.log(`
1033
989
  Pulled "${analysisTitle}"
1034
990
 
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@overscore/cli",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "CLI for deploying Overscore dashboards and publishing analyses",
5
5
  "bin": {
6
6
  "overscore": "dist/index.js"
7
7
  },
8
8
  "files": [
9
9
  "dist",
10
- "skills"
10
+ "skills",
11
+ "templates"
11
12
  ],
12
13
  "scripts": {
13
14
  "build": "tsc"
@@ -0,0 +1,51 @@
1
+ # Overscore Analysis: {{TITLE}}
2
+
3
+ This is an **Overscore Analysis** — a one-off, frozen investigation that produces a single self-contained `report.html` you can share in Slack and reopen later to refresh.
4
+
5
+ Analyses are NOT dashboards. No React, no `package.json`, no `useQuery`, no scheduled refreshes. The numbers are baked into `report.html` at publish time.
6
+
7
+ ## First steps — interview before you query
8
+
9
+ **Before running any SQL, check `analysis-log.md`.** If it has no queries logged yet (this is a fresh analysis), invoke the **`start-analysis`** skill to interview the user first. Do NOT query data blindly — the user almost always knows things about the data that will save you 80% of the exploration work.
10
+
11
+ The interview takes 2 minutes and gives you:
12
+ - The real question (often different from the title)
13
+ - Which BigQuery tables matter (so you don't probe them all)
14
+ - What success looks like (a number? a chart? a go/no-go?)
15
+ - 1–3 hypotheses to test
16
+ - Time window and filters
17
+
18
+ After the interview, draft an initial entry in `analysis-log.md` and propose a query plan for the user to approve.
19
+
20
+ ## Always maintain analysis-log.md
21
+
22
+ `analysis-log.md` is the **recoverable session state** for this analysis. Future sessions (including yourself, weeks from now, after the user runs `analysis pull`) read it to know what's been done. **Append to it as you work**, not at the end.
23
+
24
+ Every meaningful exploration step is a new entry: the question being investigated, the SQL you ran (paste it verbatim), what you observed, the conclusion you drew, and any open questions for the next pass.
25
+
26
+ ## Where data context lives
27
+
28
+ `.claude/rules/company-context.md` was pulled from this analysis's parent project at scaffold time. It contains the project's table definitions, business logic, and data quirks shared across all dashboards and analyses in this project. **Read it first** before exploring blindly.
29
+
30
+ ## Publishing
31
+
32
+ When the analysis is ready, **tell the user to run** (do not run it yourself without asking):
33
+
34
+ ```bash
35
+ npx @overscore/cli analysis publish
36
+ ```
37
+
38
+ This uploads `report.html` and the entire bundle (this CLAUDE.md, `analysis-log.md`, the `.claude/` tree, etc.) to Overscore. The analysis becomes available at `https://<project>.overscore.dev/analysis/{{SLUG}}`. Re-publishing overwrites the previous version. **Always preview `report.html` in a browser and get the user's approval before publishing.**
39
+
40
+ ## When the user wants to turn this into a dashboard
41
+
42
+ Analyses are static. If the user asks to "make this a real dashboard" or "turn this into something that updates," **do NOT add React code to this folder** — it would corrupt the artifact. Use the global `scaffold-overscore-artifact` skill to route them to a fresh dashboard scaffold that lives as a sibling of this analysis.
43
+
44
+ ## See also
45
+
46
+ The following files in `.claude/rules/` load automatically when you touch matching files — you don't need to read them now:
47
+
48
+ - **`sql-style.md`** — BigQuery dialect and SQL formatting (loads when editing SQL or `analysis-log.md`)
49
+ - **`report-html.md`** — Self-contained HTML rules and dark-theme spec (loads when editing `report.html`)
50
+ - **`analysis-methodology.md`** — Investigation loop, when to ask vs query (loads when editing `analysis-log.md`)
51
+ - **`company-context.md`** — Project-level data context (loaded every session)
@@ -0,0 +1,63 @@
1
+ ---
2
+ paths:
3
+ - "analysis-log.md"
4
+ ---
5
+
6
+ # Analysis methodology
7
+
8
+ These rules apply when you're updating `analysis-log.md` — i.e., when you're actively investigating. They exist to keep you from drifting into "let me just run more queries" mode and to make sure every query you run actually moves the analysis forward.
9
+
10
+ ## The investigation loop
11
+
12
+ Every analysis is a loop, not a straight line:
13
+
14
+ 1. **Hypothesis** — write down what you think you'll find before running the query. One sentence.
15
+ 2. **Query** — write the SQL, log it in `analysis-log.md` with the hypothesis.
16
+ 3. **Observe** — read the result. Note the actual outcome, not just "the query ran."
17
+ 4. **Revise** — was the hypothesis right? If yes, you've moved one step. If no, what does the surprise tell you? Update your mental model.
18
+ 5. **Repeat** — pick the next hypothesis from what you just learned, not from a fixed list.
19
+
20
+ If you're running queries without a hypothesis attached, you're probing, not investigating. Stop and ask the user what they actually want to know.
21
+
22
+ ## Ask before you query
23
+
24
+ **Always ask the user, don't guess, when:**
25
+
26
+ - The question is ambiguous ("what's our churn?" — over what time window? by cohort or overall? for which product?)
27
+ - A time window isn't stated (last 7 days? last quarter? all time?)
28
+ - The data shape suggests multiple interpretations (does "active user" mean "logged in" or "performed an action"?)
29
+ - A filter could go multiple ways (include or exclude trial users? include or exclude test accounts?)
30
+ - You're about to run a query that will scan a lot of data (>100GB)
31
+
32
+ The 30-second cost of asking is always cheaper than the 5-minute cost of running the wrong query and having to redo it.
33
+
34
+ ## One hypothesis per query batch
35
+
36
+ Don't run 8 exploratory queries to "see what's there." Pick one hypothesis, run the 1-3 queries that test it, log them, observe, and then pick the next hypothesis based on what you learned. Batching by hypothesis keeps `analysis-log.md` readable and keeps you from accumulating noise.
37
+
38
+ ## Escalation: summary → detail
39
+
40
+ Always start with summary queries before detail queries:
41
+
42
+ 1. **Sizing pass:** `SELECT COUNT(*), MIN(date), MAX(date) FROM table` — how big is it, what time range does it cover?
43
+ 2. **Distribution pass:** `SELECT category, COUNT(*) FROM table GROUP BY 1 ORDER BY 2 DESC LIMIT 50` — what are the top values?
44
+ 3. **Trend pass:** `SELECT DATE_TRUNC(date, WEEK), COUNT(*) FROM table GROUP BY 1` — what's the shape over time?
45
+ 4. **Detail pass:** `SELECT * FROM table WHERE specific_thing LIMIT 100` — only after you know what to look for.
46
+
47
+ Jumping straight to detail queries before you understand the shape is the most common way to waste an investigation.
48
+
49
+ ## When to stop
50
+
51
+ You're done investigating when:
52
+
53
+ - You've answered the user's original question with enough confidence to act on it
54
+ - You've documented the answer in `analysis-log.md` and you can write `report.html` from the log alone
55
+ - Further queries are returning increasingly marginal information
56
+
57
+ You're NOT done when:
58
+
59
+ - You've found "interesting things" but haven't answered the original question
60
+ - Your conclusion is "more analysis needed" — that means you stopped too early; pick a hypothesis and keep going
61
+ - The user is going to ask "but what about X?" and you don't have an answer
62
+
63
+ When you think you're done, re-read the original question (it's at the top of `analysis-log.md`) and ask yourself: "If I sent this report to the user right now, would they have what they need?" If no, keep going. If yes, write `report.html`.
@@ -0,0 +1,83 @@
1
+ ---
2
+ paths:
3
+ - "report.html"
4
+ ---
5
+
6
+ # report.html — self-contained HTML report
7
+
8
+ `report.html` is the deliverable. It's what gets shared in Slack, opened by stakeholders, and reopened weeks later. It must work standalone with **no network requests at view time**.
9
+
10
+ ## Hard rules
11
+
12
+ - **Inline all CSS.** No `<link rel="stylesheet">`, no Tailwind CDN, no Google Fonts. Use a single `<style>` block in the `<head>`.
13
+ - **Bake the data into the HTML.** No `fetch()`, no `XMLHttpRequest`, no API calls. The numbers must be visible to anyone who opens the file with no logins, no tokens, no broken external dependencies.
14
+ - **Charts are inline.** Inline SVG is the safest option. Embedded base64 PNGs are OK. A self-contained chart library (Recharts/D3 from CDN) is acceptable **only if the data is already baked into the page** and the script tag is inline-loaded — but inline SVG is preferred because it survives indefinitely.
15
+ - **No `useQuery`, no `@overscore/client`, no React.** This is a static HTML file, not a React app.
16
+
17
+ ## Dark theme — Overscore brand
18
+
19
+ Match the Overscore visual style:
20
+
21
+ - **Background:** `#0a0a0f` (near-black)
22
+ - **Cards:** `rgba(255,255,255,0.03)` background with `1px solid rgba(255,255,255,0.06)` border, generous padding, `border-radius: 16px`
23
+ - **Text:** `#fff` for headlines and numbers, `rgba(255,255,255,0.6)` for body, `rgba(255,255,255,0.4)` for secondary
24
+ - **Accents:** violet `#8b5cf6`, blue `#3b82f6`, emerald `#10b981`, amber `#f59e0b`
25
+ - **Font:** `-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`
26
+ - **Spacing:** generous — `padding: 4rem 2rem` on the body, `max-width: 900px`, `margin: 0 auto` to center
27
+
28
+ ## Structure
29
+
30
+ A good analysis report has these sections in order:
31
+
32
+ 1. **Title** — the question being answered, not just the topic
33
+ 2. **Executive summary** — 2-3 sentences with the headline finding
34
+ 3. **KPI cards** — 3-5 of the most important numbers, each with context (% change, comparison, etc.)
35
+ 4. **Primary chart** — the main visualization that tells the story
36
+ 5. **Supporting charts** — 1-3 secondary visuals that drill into the why
37
+ 6. **Detail tables** — only if the reader needs precise numbers
38
+ 7. **Conclusions** — what the data says, in plain language
39
+ 8. **Methodology footer** — date generated, time window analyzed, any caveats
40
+
41
+ ## Skeleton
42
+
43
+ ```html
44
+ <!doctype html>
45
+ <html lang="en">
46
+ <head>
47
+ <meta charset="utf-8" />
48
+ <title>The headline finding goes here</title>
49
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
50
+ <style>
51
+ body {
52
+ background: #0a0a0f;
53
+ color: #fff;
54
+ font-family: -apple-system, BlinkMacSystemFont, sans-serif;
55
+ max-width: 900px;
56
+ margin: 0 auto;
57
+ padding: 4rem 2rem;
58
+ line-height: 1.6;
59
+ }
60
+ .card {
61
+ background: rgba(255,255,255,0.03);
62
+ border: 1px solid rgba(255,255,255,0.06);
63
+ border-radius: 16px;
64
+ padding: 1.5rem;
65
+ margin: 1.5rem 0;
66
+ }
67
+ .kpi { font-size: 2.5rem; font-weight: 600; }
68
+ .label { color: rgba(255,255,255,0.4); font-size: 0.875rem; text-transform: uppercase; letter-spacing: 0.05em; }
69
+ h1, h2, h3 { font-weight: 600; }
70
+ h1 { font-size: 2rem; margin-bottom: 0.5rem; }
71
+ .footer { margin-top: 4rem; color: rgba(255,255,255,0.3); font-size: 0.75rem; }
72
+ </style>
73
+ </head>
74
+ <body>
75
+ <h1>Headline finding here</h1>
76
+ <p style="color: rgba(255,255,255,0.6);">Executive summary in 2-3 sentences.</p>
77
+ <!-- KPI cards, charts, conclusions, etc. -->
78
+ <div class="footer">Generated YYYY-MM-DD · {{TITLE}}</div>
79
+ </body>
80
+ </html>
81
+ ```
82
+
83
+ **Always preview `report.html` in a browser and get the user's approval before publishing.**
@@ -0,0 +1,43 @@
1
+ ---
2
+ paths:
3
+ - "analysis-log.md"
4
+ - "*.sql"
5
+ - "**/*.sql"
6
+ ---
7
+
8
+ # SQL style — BigQuery
9
+
10
+ These rules apply when you're drafting SQL queries to paste into `analysis-log.md` or to test against the warehouse. Every query you run should also be logged verbatim in `analysis-log.md`.
11
+
12
+ ## Dialect — BigQuery Standard SQL
13
+
14
+ - Backtick fully-qualified table refs: `` `project.dataset.table` ``. Always use the project name when querying — it's explicit and unambiguous.
15
+ - Cast safely: `SAFE_CAST(col AS INT64)`, `SAFE_CAST(col AS DATE)`. A bad cast in vanilla SQL kills the query; `SAFE_CAST` returns `NULL`.
16
+ - Parse dates with `PARSE_DATE("%Y-%m-%d", col)` and timestamps with `PARSE_TIMESTAMP("%Y-%m-%d %H:%M:%S", col)`. Don't trust automatic coercion.
17
+ - For window functions, prefer `QUALIFY` over a subquery wrapper: `SELECT ... QUALIFY ROW_NUMBER() OVER (...) = 1`.
18
+ - Use `COUNTIF(condition)` instead of `SUM(CASE WHEN condition THEN 1 ELSE 0 END)`.
19
+ - Date arithmetic: `DATE_ADD(date, INTERVAL 7 DAY)`, `DATE_DIFF(d1, d2, DAY)`, `DATE_TRUNC(date, MONTH)`.
20
+
21
+ ## Formatting
22
+
23
+ - Uppercase keywords: `SELECT`, `FROM`, `WHERE`, `GROUP BY`, `ORDER BY`, `WITH`, `JOIN`, `ON`, `QUALIFY`.
24
+ - One clause per line. Don't cram `SELECT a, b FROM t WHERE x = 1` onto one line if it has more than ~3 columns.
25
+ - Trailing commas in column lists are fine in BigQuery and make diffs cleaner.
26
+ - Use CTEs (`WITH name AS (...)`) for any query with more than one logical step. Easier to read, easier to debug, easier to log.
27
+ - Two-space indentation inside parens and CTEs.
28
+ - Inline comments with `--` for non-obvious filters: `-- exclude test users`.
29
+
30
+ ## Safety on exploratory queries
31
+
32
+ - **Always include `LIMIT`** on initial exploratory queries unless you're aggregating. `SELECT * FROM big_table LIMIT 100` lets you see the shape without burning a query slot.
33
+ - For aggregations, use `GROUP BY ... ORDER BY count DESC LIMIT 50` to find the top contributors before you go wide.
34
+ - When you're scanning a table for the first time, run `SELECT COUNT(*), MIN(date_col), MAX(date_col) FROM ...` to learn the size and time range before doing anything else.
35
+
36
+ ## Logging discipline
37
+
38
+ **Every query you run goes into `analysis-log.md`** with:
39
+ 1. A one-line description of what it was investigating
40
+ 2. The exact SQL (paste it inside a fenced code block — don't paraphrase)
41
+ 3. A one-line summary of what you observed in the result
42
+
43
+ If you ran a query and didn't log it, you wasted the query — future-you can't reproduce or learn from it.
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: start-analysis
3
+ description: Use at the start of any new Overscore analysis BEFORE querying data. Interviews the user about the question, known data sources, success criteria, and hypotheses, then writes an initial analysis-log.md entry and proposes a query plan for approval. Trigger when analysis-log.md has no queries logged yet.
4
+ ---
5
+
6
+ # Starting an analysis the right way
7
+
8
+ Most analyses fail because Claude jumps straight into querying every BigQuery table to "see what's there." That wastes 80% of the time on exploration the user could have skipped with a 2-minute conversation.
9
+
10
+ This skill exists to make sure that conversation happens before any SQL runs.
11
+
12
+ ## When to invoke this
13
+
14
+ - The user has just scaffolded a fresh analysis (`analysis-log.md` only has the seed entry, no queries logged)
15
+ - The user is asking you to "look at" or "investigate" something for the first time on this analysis
16
+ - You're about to write your first query and you're not sure what tables matter
17
+
18
+ Do NOT invoke this on a re-pull of an existing analysis with prior queries logged — that's a continuation, not a fresh start, and the prior log already has the context.
19
+
20
+ ## The interview — 6 questions, 2 minutes
21
+
22
+ Read `interview-template.md` for the literal prompts. The interview covers:
23
+
24
+ 1. **Restate the question** — confirm you understand what the user actually wants to know. The analysis title is often a shorthand; the real question is usually more specific.
25
+ 2. **Known tables** — ask what BigQuery tables the user already knows are relevant. Most users know at least 1-3 tables that matter. This saves you from probing every dataset.
26
+ 3. **Success criteria** — ask what "done" looks like. A single number? A chart? A go/no-go decision? Knowing this shapes everything downstream.
27
+ 4. **Hypotheses** — ask the user for 1-3 things they suspect might be true. Even rough hypotheses give you a query plan to test against.
28
+ 5. **Time window and filters** — ask what time range, what cohort, what to include/exclude. These are the most common sources of wasted queries when wrong.
29
+ 6. **Anything else weird** — ask "is there anything I should know about this data?" Users usually surface 1-2 known quirks ("dates before 2024 are unreliable," "we have a test_user flag we exclude").
30
+
31
+ Ask these one or two at a time conversationally — don't dump all 6 at once like a form. The user will respond more openly to a real conversation.
32
+
33
+ ## After the interview
34
+
35
+ 1. **Read `.claude/rules/company-context.md`** to cross-reference what the user said against what's already documented about the project's data.
36
+ 2. **Draft an initial entry in `analysis-log.md`** with:
37
+ - The clarified question (not the original title)
38
+ - The tables the user named, with what each contains
39
+ - The success criteria (literally — quote the user)
40
+ - The hypotheses (numbered)
41
+ - The time window and any filters
42
+ - Any quirks the user mentioned
43
+ 3. **Propose a prioritized query plan** — 3-5 queries that test the highest-priority hypothesis first. For each query, write the SQL, what hypothesis it tests, and what result would confirm or refute it. Do NOT run any of them yet.
44
+ 4. **Get approval.** Show the user the plan and ask them to confirm or revise before you run anything.
45
+
46
+ ## What this skill does NOT do
47
+
48
+ - It does not run any SQL. Querying happens after the user approves the plan.
49
+ - It does not write `report.html`. That happens at the end of the investigation, not the beginning.
50
+ - It does not skip the interview because the analysis title looks "obvious." Even obvious-sounding analyses benefit from the 2-minute conversation.
51
+
52
+ ## If the user resists the interview
53
+
54
+ Some users will say "just look at the data, I'll tell you if it's wrong." That's a trap — you'll spend 30 minutes probing tables and still need the interview at the end. Push back once: "I can save us a lot of time if you tell me what tables matter and what success looks like — it's a 2-minute conversation." If they still refuse, do a single sizing pass on the parent project's known tables (from `company-context.md`), then come back and ask narrower questions based on what you found.
@@ -0,0 +1,31 @@
1
+ # Interview prompts
2
+
3
+ These are the literal questions to ask the user during the start-analysis interview. Ask them one or two at a time, conversationally — don't dump them all at once. Wait for an answer before moving to the next.
4
+
5
+ ## 1. Restate the question
6
+
7
+ > Before I touch any data — let me make sure I understand what you actually want to know. The analysis title is "{{TITLE}}" — is the real question more specific than that? For example, are you looking at a particular time window, a particular cohort, or trying to make a specific decision?
8
+
9
+ ## 2. Known tables
10
+
11
+ > Which BigQuery tables do you already know are relevant here? Even if you only know one or two, that's a huge head start — I won't have to probe everything.
12
+
13
+ ## 3. Success criteria
14
+
15
+ > What does "done" look like for this analysis? Are we trying to land on a single number, build a chart, or make a go/no-go decision? Knowing the shape of the answer helps me know when to stop digging.
16
+
17
+ ## 4. Hypotheses
18
+
19
+ > What do you suspect might be true here? Even rough guesses help — I'd rather test 2-3 specific hypotheses than wander around looking for patterns. What's your gut telling you?
20
+
21
+ ## 5. Time window and filters
22
+
23
+ > What time window should we be looking at? And are there any filters I should apply by default — like excluding test users, internal accounts, or specific products?
24
+
25
+ ## 6. Anything weird about this data
26
+
27
+ > Last one: is there anything about this data I should know upfront? Quirks, known issues, things that look suspicious but are actually fine, conventions you use? Those are the things that bite if I find out about them three queries in instead of right now.
28
+
29
+ ---
30
+
31
+ After all 6 answers, summarize back what you heard and ask the user to confirm. Then write the initial entry in `analysis-log.md` and propose the query plan.
@@ -0,0 +1,17 @@
1
+ # {{TITLE}}
2
+
3
+ ## {{DATE}} — Initial exploration
4
+
5
+ **Question:** {{TITLE}}
6
+
7
+ ### Tables involved
8
+ - _(populate as you explore — see `.claude/rules/company-context.md` for what's available)_
9
+
10
+ ### Queries run
11
+ - _(append every meaningful query here, with one-line description + SQL)_
12
+
13
+ ### Conclusions
14
+ - _(empty so far)_
15
+
16
+ ### Open questions
17
+ - _(empty so far)_
@@ -0,0 +1,17 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>{{TITLE}}</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <style>
8
+ body { background: #0a0a0f; color: #fff; font-family: -apple-system, BlinkMacSystemFont, sans-serif; padding: 4rem 2rem; max-width: 800px; margin: 0 auto; }
9
+ h1 { font-weight: 600; }
10
+ p { color: rgba(255,255,255,0.6); }
11
+ </style>
12
+ </head>
13
+ <body>
14
+ <h1>{{TITLE}}</h1>
15
+ <p>This analysis is empty. Have Claude do the work and regenerate this file.</p>
16
+ </body>
17
+ </html>