@overscore/cli 0.11.0 → 0.11.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
@@ -268,27 +268,20 @@ async function deploy() {
268
268
  const baseUrl = apiUrl.replace(/\/api$/, "");
269
269
  // Upload knowledge base (project-level, shared across all artifacts)
270
270
  await uploadKnowledgeBase(process.cwd(), baseUrl, projectSlug, apiKey);
271
- // Sync company context (with conflict detection) legacy path for
272
- // projects that haven't migrated to the knowledge base yet.
273
- const companyCtxPath = path.resolve(process.cwd(), ".claude/rules/company-context.md");
274
- if (fs.existsSync(companyCtxPath)) {
275
- const content = fs.readFileSync(companyCtxPath, "utf-8");
276
- const hashPath = path.resolve(process.cwd(), ".claude/rules/.context-hash");
277
- const storedHash = fs.existsSync(hashPath) ? fs.readFileSync(hashPath, "utf-8").trim() : null;
278
- try {
279
- // Check if Hub version has changed since we pulled
280
- const checkRes = await fetch(`${baseUrl}/api/projects/${projectSlug}/context`, {
281
- headers: { Authorization: `Bearer ${apiKey}` },
282
- });
283
- const checkData = checkRes.ok ? (await checkRes.json()) : null;
284
- const hubHash = checkData?.context_md_hash || null;
285
- if (storedHash && hubHash && storedHash !== hubHash) {
286
- console.log(" ⚠ Company context was updated in the Hub since you pulled.");
287
- console.log(` Review at: ${baseUrl}/projects/${projectSlug}/context`);
288
- console.log(" Your local changes were NOT uploaded to avoid overwriting.");
289
- }
290
- else {
291
- const putRes = await fetch(`${baseUrl}/api/projects/${projectSlug}/context`, {
271
+ // Sync dashboard context skip stub files (they have no real content)
272
+ const dashCtxPath = path.resolve(process.cwd(), ".claude/rules/this-dashboard.md");
273
+ const legacyDashCtxPath = path.resolve(process.cwd(), ".claude/rules/dashboard-context.md");
274
+ const activeDashCtxPath = fs.existsSync(dashCtxPath)
275
+ ? dashCtxPath
276
+ : fs.existsSync(legacyDashCtxPath)
277
+ ? legacyDashCtxPath
278
+ : null;
279
+ if (activeDashCtxPath) {
280
+ const content = fs.readFileSync(activeDashCtxPath, "utf-8").trim();
281
+ const isStub = !content || content.startsWith("<!--") || content === `# ${dashboardSlug}`;
282
+ if (!isStub) {
283
+ try {
284
+ const res = await fetch(`${baseUrl}/api/dashboards/${dashboardSlug}/context`, {
292
285
  method: "PUT",
293
286
  headers: {
294
287
  Authorization: `Bearer ${apiKey}`,
@@ -296,40 +289,17 @@ async function deploy() {
296
289
  },
297
290
  body: JSON.stringify({ context_md: content }),
298
291
  });
299
- if (putRes.ok) {
300
- const putData = (await putRes.json());
301
- // Update stored hash
302
- if (putData.context_md_hash) {
303
- fs.mkdirSync(path.dirname(hashPath), { recursive: true });
304
- fs.writeFileSync(hashPath, putData.context_md_hash);
305
- }
306
- console.log(" Company context synced.");
292
+ if (res.ok) {
293
+ const lines = content.split("\n").length;
294
+ console.log(` Dashboard context synced (${lines} lines).`);
307
295
  }
308
296
  }
297
+ catch {
298
+ // Non-fatal
299
+ }
309
300
  }
310
- catch {
311
- // Non-fatal
312
- }
313
- }
314
- // Sync dashboard context (no conflict detection needed — per-dashboard)
315
- const dashCtxPath = path.resolve(process.cwd(), ".claude/rules/this-dashboard.md");
316
- const legacyDashCtxPath = path.resolve(process.cwd(), ".claude/rules/dashboard-context.md");
317
- const activeDashCtxPath = fs.existsSync(dashCtxPath) ? dashCtxPath : fs.existsSync(legacyDashCtxPath) ? legacyDashCtxPath : null;
318
- if (activeDashCtxPath) {
319
- const content = fs.readFileSync(activeDashCtxPath, "utf-8");
320
- try {
321
- await fetch(`${baseUrl}/api/dashboards/${dashboardSlug}/context`, {
322
- method: "PUT",
323
- headers: {
324
- Authorization: `Bearer ${apiKey}`,
325
- "Content-Type": "application/json",
326
- },
327
- body: JSON.stringify({ context_md: content }),
328
- });
329
- console.log(" Dashboard context synced.");
330
- }
331
- catch {
332
- // Non-fatal
301
+ else {
302
+ console.log(" Dashboard context: stub, not synced — run save-knowledge to populate it.");
333
303
  }
334
304
  }
335
305
  // Pull latest platform rules so the local project stays up to date
@@ -736,51 +706,85 @@ VITE_OVERSCORE_DASHBOARD_SLUG=${dashboardSlug}
736
706
  VITE_OVERSCORE_API_URL=https://overscore.dev/api
737
707
  `;
738
708
  fs.writeFileSync(path.join(targetDir, ".env"), envContent, { mode: 0o600 });
739
- // Fetch latest project context from Hub (may be newer than what's in the source zip)
740
- try {
741
- const ctxRes = await fetch(`${baseUrl}/api/projects/${projectSlug}/context`, {
742
- headers: { Authorization: `Bearer ${apiKey}` },
743
- });
744
- if (ctxRes.ok) {
745
- const ctxData = (await ctxRes.json());
746
- if (ctxData.context_md) {
747
- const rulesDir = path.join(targetDir, ".claude", "rules");
748
- fs.mkdirSync(rulesDir, { recursive: true });
749
- fs.writeFileSync(path.join(rulesDir, "company-context.md"), ctxData.context_md);
750
- // Store hash for conflict detection on next deploy
751
- if (ctxData.context_md_hash) {
752
- fs.writeFileSync(path.join(rulesDir, ".context-hash"), ctxData.context_md_hash);
709
+ const rulesDir = path.join(targetDir, ".claude", "rules");
710
+ fs.mkdirSync(rulesDir, { recursive: true });
711
+ // Sync project knowledge base (preferred) — or fall back to legacy company-context.md
712
+ const hasKnowledge = await syncKnowledgeBase(targetDir, baseUrl, projectSlug, apiKey);
713
+ if (!hasKnowledge) {
714
+ try {
715
+ const ctxRes = await fetch(`${baseUrl}/api/projects/${projectSlug}/context`, {
716
+ headers: { Authorization: `Bearer ${apiKey}` },
717
+ });
718
+ if (ctxRes.ok) {
719
+ const ctxData = (await ctxRes.json());
720
+ if (ctxData.context_md) {
721
+ fs.writeFileSync(path.join(rulesDir, "company-context.md"), ctxData.context_md);
722
+ if (ctxData.context_md_hash) {
723
+ fs.writeFileSync(path.join(rulesDir, ".context-hash"), ctxData.context_md_hash);
724
+ }
753
725
  }
754
- console.log(" Updated company context from Hub.");
755
726
  }
756
727
  }
728
+ catch {
729
+ // Non-fatal
730
+ }
757
731
  }
758
- catch {
759
- // Non-fatal the version from the source zip is fine
760
- }
761
- // Pull dashboard context from Hub (may be newer than the source zip)
732
+ // Pull dashboard context from Hub
733
+ let dashboardContextLoaded = false;
762
734
  try {
763
735
  const dashCtxRes = await fetch(`${baseUrl}/api/dashboards/${dashboardSlug}/context`, {
764
736
  headers: { Authorization: `Bearer ${apiKey}` },
765
737
  });
766
738
  if (dashCtxRes.ok) {
767
739
  const dashCtxData = (await dashCtxRes.json());
768
- if (dashCtxData.context_md) {
769
- const rulesDir = path.join(targetDir, ".claude", "rules");
770
- fs.mkdirSync(rulesDir, { recursive: true });
740
+ if (dashCtxData.context_md && dashCtxData.context_md.trim()) {
771
741
  fs.writeFileSync(path.join(rulesDir, "this-dashboard.md"), dashCtxData.context_md);
772
- console.log(" Updated dashboard context from Hub.");
742
+ dashboardContextLoaded = true;
773
743
  }
774
744
  }
775
745
  }
776
746
  catch {
777
747
  // Non-fatal
778
748
  }
779
- // Pull latest platform rules
749
+ // Create a stub this-dashboard.md if none came from the Hub so Claude knows to fill it in
750
+ if (!dashboardContextLoaded) {
751
+ const stubPath = path.join(rulesDir, "this-dashboard.md");
752
+ if (!fs.existsSync(stubPath) || fs.readFileSync(stubPath, "utf-8").trim() === "") {
753
+ fs.writeFileSync(stubPath, `# ${slug}\n\n<!-- Claude: fill this in as you build. What does this dashboard answer? Who is the audience? Which queries power which charts? Any non-obvious filter logic or design decisions? -->\n`);
754
+ }
755
+ }
756
+ // Pull latest platform rules (includes save-knowledge skill)
780
757
  await syncPlatformRules(targetDir, baseUrl, apiKey);
758
+ // Print context summary so the developer knows what Claude has access to
759
+ const knowledgeDir = path.join(targetDir, ".claude", "knowledge");
760
+ const knowledgeFiles = fs.existsSync(knowledgeDir)
761
+ ? fs.readdirSync(knowledgeDir).filter((f) => f.endsWith(".md") && f !== "INDEX.md")
762
+ : [];
763
+ const dashCtxPath = path.join(rulesDir, "this-dashboard.md");
764
+ const dashCtxContent = fs.existsSync(dashCtxPath) ? fs.readFileSync(dashCtxPath, "utf-8").trim() : "";
765
+ const dashCtxIsStub = dashCtxContent.startsWith("<!--") || dashCtxContent === `# ${slug}`;
766
+ const sqlStylePath = path.join(rulesDir, "sql-style.md");
781
767
  console.log(`
782
768
  Pulled v${version}${commitMsg ? ` — "${commitMsg}"` : ""}
783
769
 
770
+ Context loaded:`);
771
+ if (knowledgeFiles.length > 0) {
772
+ console.log(` Knowledge base: ${knowledgeFiles.length} file${knowledgeFiles.length !== 1 ? "s" : ""} (${knowledgeFiles.join(", ")})`);
773
+ }
774
+ else {
775
+ console.log(` Knowledge base: empty — Claude will build it as you work`);
776
+ }
777
+ if (dashCtxIsStub || !dashCtxContent) {
778
+ console.log(` Dashboard context: none yet — stub created, Claude will fill it in`);
779
+ }
780
+ else {
781
+ const lines = dashCtxContent.split("\n").length;
782
+ console.log(` Dashboard context: this-dashboard.md (${lines} lines)`);
783
+ }
784
+ if (fs.existsSync(sqlStylePath)) {
785
+ console.log(` SQL style: project override active`);
786
+ }
787
+ console.log(`
784
788
  Next steps:
785
789
 
786
790
  cd ${slug}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@overscore/cli",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "CLI for deploying Overscore dashboards and publishing analyses",
5
5
  "bin": {
6
6
  "overscore": "dist/index.js"
@@ -5,26 +5,32 @@ description: Reviews data insights discovered during dashboard or analysis work
5
5
 
6
6
  # Save Knowledge
7
7
 
8
- Run this skill before deploying a dashboard or when explicitly asked.
9
-
10
- ## When to invoke
11
- - Before `npx @overscore/cli deploy`
12
- - When the user says "save what you learned" or similar
8
+ Run before deploying a dashboard, before publishing an analysis, or whenever you discover something non-obvious about the data.
13
9
 
14
10
  ## Process
15
11
 
16
- 1. Review the dashboard code and any exploratory queries you ran.
12
+ 1. Review the code you've written and queries you've run this session.
13
+
14
+ 2. **Read the existing files before proposing anything.** Read `.claude/knowledge/INDEX.md`, then read the specific knowledge files relevant to what you learned. Read `.claude/rules/this-dashboard.md` (or `analysis-log.md` for analyses) in full. You need to know what's already there before you can know what's new.
15
+
16
+ 3. For each insight from this session, decide what action to take:
17
+
18
+ | Situation | Action |
19
+ |---|---|
20
+ | New topic, no existing file covers it | Create a new `.claude/knowledge/{topic}.md`; add entry to INDEX.md |
21
+ | New fact that belongs in an existing knowledge file | Append a new line or section to that file — do not rewrite what's there |
22
+ | Fact that refines or corrects something already documented | Edit that specific line/section only — do not duplicate it |
23
+ | Already documented accurately | Skip entirely |
24
+ | Dashboard/analysis-specific insight not yet captured | Append to `this-dashboard.md` or `analysis-log.md` |
25
+ | Insight that updates something already there | Edit that specific part only |
17
26
 
18
- 2. Read `.claude/knowledge/INDEX.md` to see what's already documented.
27
+ **Never overwrite a file wholesale. Never duplicate a fact that's already accurate. Only write what's genuinely new or changed.**
19
28
 
20
- 3. For each new insight, decide:
21
- - **Project knowledge** (helps ALL future artifacts) → propose addition to `.claude/knowledge/`
22
- - **Dashboard-specific** (design decision, query note) → append to `.claude/rules/this-dashboard.md`
23
- - **Already known** → skip
29
+ 4. Present proposed changes to the user. Be specific — show the exact text you'd add or the exact edit you'd make. "I should note something about X" is not acceptable — show the line.
24
30
 
25
- 4. Present proposed additions to the user for approval before writing any files.
31
+ 5. After the user approves, write the files. If you created a new knowledge file, update INDEX.md.
26
32
 
27
- ## What qualifies as project knowledge vs dashboard-specific
33
+ ## What qualifies as project knowledge vs artifact-specific
28
34
  - "dates are UTC-7 no DST" → project knowledge (helps all artifacts)
29
35
  - "affiliate_id = 0 is unattributed" → project knowledge
30
36
  - "Revenue chart excludes refunds per Finance definition" → dashboard-specific
@@ -5,11 +5,7 @@ description: Reviews data insights discovered during analysis work and proposes
5
5
 
6
6
  # Save Knowledge
7
7
 
8
- Run this skill before publishing an analysis or when explicitly asked.
9
-
10
- ## When to invoke
11
- - Before `analysis publish` (review what you learned first)
12
- - When the user says "save what you learned" or similar
8
+ Run before publishing an analysis or whenever you discover something non-obvious about the data.
13
9
 
14
10
  ## Process
15
11
 
@@ -19,14 +15,21 @@ Run this skill before publishing an analysis or when explicitly asked.
19
15
  - Metric definitions or business rules
20
16
  - Data quality issues or known gaps
21
17
 
22
- 2. Read `.claude/knowledge/INDEX.md` to see what's already documented.
18
+ 2. **Read the existing knowledge files before proposing anything.** Read `.claude/knowledge/INDEX.md`, then read the specific files relevant to what you discovered. You need to know what's already there before you can know what's new.
19
+
20
+ 3. For each insight, decide what action to take:
21
+
22
+ | Situation | Action |
23
+ |---|---|
24
+ | New topic, no existing file covers it | Create a new `.claude/knowledge/{topic}.md`; add entry to INDEX.md |
25
+ | New fact that belongs in an existing knowledge file | Append a new line or section to that file — do not rewrite what's there |
26
+ | Fact that refines or corrects something already documented | Edit that specific line/section only — do not duplicate it |
27
+ | Already documented accurately | Skip entirely |
28
+ | Analysis-specific (only relevant to this investigation) | Leave in analysis-log.md — don't promote to project knowledge |
23
29
 
24
- 3. For each new insight, decide:
25
- - **Project knowledge** (helps ALL future artifacts) → propose a new file or addition to `.claude/knowledge/`
26
- - **Already known** (in an existing knowledge file) → skip
27
- - **Analysis-specific** (only relevant to this investigation) → leave in analysis-log.md
30
+ **Never overwrite a file wholesale. Never duplicate a fact that's already accurate. Only write what's genuinely new or changed.**
28
31
 
29
- 4. Present proposed additions to the user for approval before writing any files.
32
+ 4. Present proposed changes to the user. Show the exact text you'd add or the exact edit you'd make — not a summary of what you intend to write.
30
33
 
31
34
  5. After approval, write the files and update INDEX.md.
32
35
 
@@ -34,5 +37,5 @@ Run this skill before publishing an analysis or when explicitly asked.
34
37
  - "unique_page_views is already COUNT(DISTINCT domain_userid)" → YES
35
38
  - "date_az is America/Phoenix UTC-7, no DST" → YES
36
39
  - "affiliate_id = 0 is unattributed, usually exclude" → YES
37
- - "Q1 churn spiked because of the pricing change" → NO (analysis conclusion, not data fact)
40
+ - "Q1 churn spiked because of the pricing change" → NO (analysis conclusion, not a data fact)
38
41
  - "I used a 90-day window for this analysis" → NO (analysis-specific decision)