@overscore/cli 0.11.13 → 0.13.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.
- package/dist/index.js +117 -19
- package/package.json +1 -1
- package/templates/analysis/.claude/CLAUDE.md +4 -3
- package/templates/analysis/.claude/rules/report-components.md +6 -2
- package/templates/analysis/.claude/rules/report-html.md +32 -19
- package/templates/analysis/.claude/skills/customize-aesthetic/SKILL.md +57 -0
- package/templates/analysis/.claude/skills/start-analysis/SKILL.md +4 -7
- package/templates/analysis/.claude/skills/start-analysis/interview-template.md +6 -2
- package/templates/analysis/report.html +5 -2
package/dist/index.js
CHANGED
|
@@ -1300,11 +1300,52 @@ function syncClaudeMdImports(targetDir) {
|
|
|
1300
1300
|
fs.writeFileSync(claudeMdPath, updated);
|
|
1301
1301
|
return true;
|
|
1302
1302
|
}
|
|
1303
|
+
function platformMetaPath(targetDir) {
|
|
1304
|
+
return path.join(targetDir, ".claude", ".platform-meta.json");
|
|
1305
|
+
}
|
|
1306
|
+
function readPlatformMeta(targetDir) {
|
|
1307
|
+
const p = platformMetaPath(targetDir);
|
|
1308
|
+
if (!fs.existsSync(p))
|
|
1309
|
+
return { version: 1, last_synced: {} };
|
|
1310
|
+
try {
|
|
1311
|
+
const parsed = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
1312
|
+
if (parsed?.version === 1 && parsed.last_synced)
|
|
1313
|
+
return parsed;
|
|
1314
|
+
}
|
|
1315
|
+
catch {
|
|
1316
|
+
// fall through
|
|
1317
|
+
}
|
|
1318
|
+
return { version: 1, last_synced: {} };
|
|
1319
|
+
}
|
|
1320
|
+
function writePlatformMeta(targetDir, meta) {
|
|
1321
|
+
const p = platformMetaPath(targetDir);
|
|
1322
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
1323
|
+
fs.writeFileSync(p, JSON.stringify(meta, null, 2) + "\n");
|
|
1324
|
+
}
|
|
1325
|
+
function md5(s) {
|
|
1326
|
+
return createHash("md5").update(s).digest("hex");
|
|
1327
|
+
}
|
|
1328
|
+
function decidePlatformWrite(fullPath, serverContent, serverHash, lastSyncedHash, force) {
|
|
1329
|
+
if (force)
|
|
1330
|
+
return { action: "write", reason: "force" };
|
|
1331
|
+
if (!fs.existsSync(fullPath))
|
|
1332
|
+
return { action: "write", reason: "new" };
|
|
1333
|
+
const localContent = fs.readFileSync(fullPath, "utf-8");
|
|
1334
|
+
if (localContent === serverContent)
|
|
1335
|
+
return { action: "skip", reason: "unchanged" };
|
|
1336
|
+
const localHash = md5(localContent);
|
|
1337
|
+
if (lastSyncedHash && localHash === lastSyncedHash) {
|
|
1338
|
+
return { action: "write", reason: "clean-update" };
|
|
1339
|
+
}
|
|
1340
|
+
return { action: "skip", reason: "user-modified" };
|
|
1341
|
+
}
|
|
1303
1342
|
/**
|
|
1304
1343
|
* Fetch the latest platform-owned Claude Code rules from the Hub and write
|
|
1305
1344
|
* them into the project's .claude/ directory. Called from `deploy` (before
|
|
1306
1345
|
* uploading), `pull` (after extracting), and `syncHubManagedRules` (analysis
|
|
1307
1346
|
* paths). User-owned files (knowledge/, this-dashboard.md) are NOT touched.
|
|
1347
|
+
*
|
|
1348
|
+
* Preserves user edits via .claude/.platform-meta.json hash tracking.
|
|
1308
1349
|
*/
|
|
1309
1350
|
async function syncPlatformRules(targetDir, baseUrl, apiKey) {
|
|
1310
1351
|
try {
|
|
@@ -1316,17 +1357,39 @@ async function syncPlatformRules(targetDir, baseUrl, apiKey) {
|
|
|
1316
1357
|
const data = (await res.json());
|
|
1317
1358
|
if (!data.files)
|
|
1318
1359
|
return;
|
|
1319
|
-
|
|
1360
|
+
const meta = readPlatformMeta(targetDir);
|
|
1361
|
+
const written = [];
|
|
1362
|
+
const preserved = [];
|
|
1320
1363
|
for (const [relPath, content] of Object.entries(data.files)) {
|
|
1321
1364
|
const fullPath = path.join(targetDir, ".claude", relPath);
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1365
|
+
const serverHash = data.hashes?.[relPath] ?? md5(content);
|
|
1366
|
+
const lastSyncedHash = meta.last_synced[relPath] ?? null;
|
|
1367
|
+
const decision = decidePlatformWrite(fullPath, content, serverHash, lastSyncedHash, false);
|
|
1368
|
+
if (decision.action === "write") {
|
|
1369
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
1370
|
+
fs.writeFileSync(fullPath, content);
|
|
1371
|
+
meta.last_synced[relPath] = serverHash;
|
|
1372
|
+
written.push(relPath);
|
|
1373
|
+
}
|
|
1374
|
+
else if (decision.reason === "unchanged") {
|
|
1375
|
+
// Bootstrap: record hash even if we didn't write, so future
|
|
1376
|
+
// user-edits are detected against the right baseline.
|
|
1377
|
+
meta.last_synced[relPath] = serverHash;
|
|
1378
|
+
}
|
|
1379
|
+
else {
|
|
1380
|
+
preserved.push(relPath);
|
|
1381
|
+
}
|
|
1325
1382
|
}
|
|
1383
|
+
writePlatformMeta(targetDir, meta);
|
|
1326
1384
|
const claudeUpdated = syncClaudeMdImports(targetDir);
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1385
|
+
if (written.length > 0 || claudeUpdated) {
|
|
1386
|
+
console.log(` Platform rules synced (${written.length} file${written.length === 1 ? "" : "s"}${claudeUpdated ? " + CLAUDE.md updated" : ""}).`);
|
|
1387
|
+
}
|
|
1388
|
+
if (preserved.length > 0) {
|
|
1389
|
+
console.log(` Preserved ${preserved.length} locally-modified file${preserved.length === 1 ? "" : "s"} (Hub updates skipped):`);
|
|
1390
|
+
for (const f of preserved)
|
|
1391
|
+
console.log(` ${f}`);
|
|
1392
|
+
console.log(` To accept Hub updates and discard your local edits, run: npx @overscore/cli platform update --force`);
|
|
1330
1393
|
}
|
|
1331
1394
|
}
|
|
1332
1395
|
catch {
|
|
@@ -1338,13 +1401,16 @@ async function syncPlatformRules(targetDir, baseUrl, apiKey) {
|
|
|
1338
1401
|
* can tell the user what's new. Called by `npx @overscore/cli platform update`.
|
|
1339
1402
|
*
|
|
1340
1403
|
* With `--check`: read-only mode. Reports what *would* change without writing.
|
|
1404
|
+
* With `--force`: discard local edits and accept the Hub version verbatim.
|
|
1341
1405
|
*/
|
|
1342
|
-
async function platformUpdate(checkOnly = false) {
|
|
1406
|
+
async function platformUpdate(checkOnly = false, force = false) {
|
|
1343
1407
|
const { apiUrl, apiKey } = await loadEnv();
|
|
1344
1408
|
const baseUrl = apiUrl.replace(/\/api$/, "");
|
|
1345
1409
|
console.log(checkOnly
|
|
1346
1410
|
? "\n Checking platform rules (no changes will be written)...\n"
|
|
1347
|
-
:
|
|
1411
|
+
: force
|
|
1412
|
+
? "\n Syncing platform rules from Hub (--force: local edits will be overwritten)...\n"
|
|
1413
|
+
: "\n Syncing platform rules from Hub...\n");
|
|
1348
1414
|
const capPath = path.resolve(process.cwd(), ".claude", "platform-capabilities.md");
|
|
1349
1415
|
const prevContent = fs.existsSync(capPath) ? fs.readFileSync(capPath, "utf-8") : null;
|
|
1350
1416
|
const prevVersion = prevContent?.match(/^version:\s*(.+)$/m)?.[1]?.trim() ?? null;
|
|
@@ -1360,18 +1426,34 @@ async function platformUpdate(checkOnly = false) {
|
|
|
1360
1426
|
console.error("\n Error: No files in platform-rules response.\n");
|
|
1361
1427
|
process.exit(1);
|
|
1362
1428
|
}
|
|
1429
|
+
const meta = readPlatformMeta(process.cwd());
|
|
1363
1430
|
const updated = [];
|
|
1431
|
+
const preserved = [];
|
|
1364
1432
|
for (const [relPath, content] of Object.entries(data.files)) {
|
|
1365
1433
|
const fullPath = path.join(process.cwd(), ".claude", relPath);
|
|
1366
|
-
const
|
|
1367
|
-
|
|
1434
|
+
const serverHash = data.hashes?.[relPath] ?? md5(content);
|
|
1435
|
+
const lastSyncedHash = meta.last_synced[relPath] ?? null;
|
|
1436
|
+
const decision = decidePlatformWrite(fullPath, content, serverHash, lastSyncedHash, force);
|
|
1437
|
+
if (decision.action === "write") {
|
|
1368
1438
|
updated.push(relPath);
|
|
1369
1439
|
if (!checkOnly) {
|
|
1370
1440
|
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
1371
1441
|
fs.writeFileSync(fullPath, content);
|
|
1442
|
+
meta.last_synced[relPath] = serverHash;
|
|
1372
1443
|
}
|
|
1373
1444
|
}
|
|
1445
|
+
else if (decision.reason === "unchanged") {
|
|
1446
|
+
// Bootstrap: in non-check mode, record hash so future user-edits
|
|
1447
|
+
// are detected against the right baseline.
|
|
1448
|
+
if (!checkOnly)
|
|
1449
|
+
meta.last_synced[relPath] = serverHash;
|
|
1450
|
+
}
|
|
1451
|
+
else {
|
|
1452
|
+
preserved.push(relPath);
|
|
1453
|
+
}
|
|
1374
1454
|
}
|
|
1455
|
+
if (!checkOnly)
|
|
1456
|
+
writePlatformMeta(process.cwd(), meta);
|
|
1375
1457
|
// syncClaudeMdImports only makes sense if we're actually writing files.
|
|
1376
1458
|
// In check mode, compute whether imports would be added by reading current state.
|
|
1377
1459
|
let claudeWouldChange = false;
|
|
@@ -1393,14 +1475,26 @@ async function platformUpdate(checkOnly = false) {
|
|
|
1393
1475
|
if (claudeUpdated)
|
|
1394
1476
|
updated.push(".claude/CLAUDE.md (@imports updated)");
|
|
1395
1477
|
}
|
|
1396
|
-
if (updated.length === 0) {
|
|
1478
|
+
if (updated.length === 0 && preserved.length === 0) {
|
|
1397
1479
|
console.log(" Already up to date — no changes.\n");
|
|
1398
1480
|
return;
|
|
1399
1481
|
}
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1482
|
+
if (updated.length > 0) {
|
|
1483
|
+
const verb = checkOnly ? "Would update" : "Updated";
|
|
1484
|
+
console.log(` ${verb} ${updated.length} file${updated.length === 1 ? "" : "s"}:\n`);
|
|
1485
|
+
for (const f of updated)
|
|
1486
|
+
console.log(` ${f}`);
|
|
1487
|
+
}
|
|
1488
|
+
if (preserved.length > 0) {
|
|
1489
|
+
if (updated.length > 0)
|
|
1490
|
+
console.log();
|
|
1491
|
+
const verb = checkOnly ? "Would preserve" : "Preserved";
|
|
1492
|
+
console.log(` ${verb} ${preserved.length} locally-modified file${preserved.length === 1 ? "" : "s"} (Hub updates skipped):\n`);
|
|
1493
|
+
for (const f of preserved)
|
|
1494
|
+
console.log(` ${f}`);
|
|
1495
|
+
console.log(`\n To accept the Hub version and discard your local edits, re-run with --force:`);
|
|
1496
|
+
console.log(` npx @overscore/cli platform update --force`);
|
|
1497
|
+
}
|
|
1404
1498
|
if (updated.includes("platform-capabilities.md")) {
|
|
1405
1499
|
const newContent = data.files["platform-capabilities.md"];
|
|
1406
1500
|
const newVersion = newContent.match(/^version:\s*(.+)$/m)?.[1]?.trim() ?? "unknown";
|
|
@@ -2421,19 +2515,23 @@ else if (command === "analysis") {
|
|
|
2421
2515
|
else if (command === "platform") {
|
|
2422
2516
|
if (subcommand === "update") {
|
|
2423
2517
|
const checkOnly = process.argv.includes("--check");
|
|
2424
|
-
|
|
2518
|
+
const force = process.argv.includes("--force");
|
|
2519
|
+
platformUpdate(checkOnly, force);
|
|
2425
2520
|
}
|
|
2426
2521
|
else {
|
|
2427
2522
|
console.log(`
|
|
2428
2523
|
overscore platform — Manage platform rules and capabilities
|
|
2429
2524
|
|
|
2430
2525
|
Commands:
|
|
2431
|
-
update
|
|
2432
|
-
|
|
2526
|
+
update Sync latest platform rules, skills, and capabilities from Hub
|
|
2527
|
+
(preserves locally-modified rule files via hash tracking)
|
|
2528
|
+
update --check Show what would change without writing anything
|
|
2529
|
+
update --force Overwrite locally-modified rule files with the Hub version
|
|
2433
2530
|
|
|
2434
2531
|
Examples:
|
|
2435
2532
|
npx @overscore/cli platform update
|
|
2436
2533
|
npx @overscore/cli platform update --check
|
|
2534
|
+
npx @overscore/cli platform update --force
|
|
2437
2535
|
`);
|
|
2438
2536
|
}
|
|
2439
2537
|
}
|
package/package.json
CHANGED
|
@@ -99,7 +99,7 @@ npx @overscore/cli analysis publish
|
|
|
99
99
|
|
|
100
100
|
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.**
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
**Immediately after the publish command exits successfully and you've shared the link**, invoke the **`save-knowledge`** skill. This is mandatory, not optional. Do not wait for the user to ask "save what we learned" — the debrief always runs after a successful publish. If nothing new came up this session, the skill will say so in one line and you're done.
|
|
103
103
|
|
|
104
104
|
## When the user wants to turn this into a dashboard
|
|
105
105
|
|
|
@@ -110,7 +110,8 @@ Analyses are static. If the user asks to "make this a real dashboard" or "turn t
|
|
|
110
110
|
The following files in `.claude/rules/` load automatically when you touch matching files — you don't need to read them now:
|
|
111
111
|
|
|
112
112
|
- **`sql-style.md`** — BigQuery dialect and SQL formatting (loads when editing SQL or `analysis-log.md`)
|
|
113
|
-
- **`report-html.md`** — Self-contained HTML rules
|
|
114
|
-
- **`report-components.md`** —
|
|
113
|
+
- **`report-html.md`** — Self-contained HTML rules (theme-agnostic) (loads when editing `report.html`)
|
|
114
|
+
- **`report-components.md`** — Optional copy-paste snippets in Overscore's dark theme; skip if your aesthetic differs (loads when editing `report.html`)
|
|
115
115
|
- **`analysis-methodology.md`** — Investigation loop, when to ask vs query (loads when editing `analysis-log.md`)
|
|
116
|
+
- **`.claude/skills/customize-aesthetic/SKILL.md`** — drives intentional aesthetic choices for the report
|
|
116
117
|
- **`knowledge/INDEX.md`** — Project knowledge base catalog (auto-loaded every session via @import above)
|
|
@@ -3,9 +3,13 @@ paths:
|
|
|
3
3
|
- "report.html"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Report components —
|
|
6
|
+
# Report components — optional starter snippets
|
|
7
7
|
|
|
8
|
-
These are ready-made HTML/CSS/SVG components for `report.html
|
|
8
|
+
These are ready-made HTML/CSS/SVG components for `report.html` in **one specific aesthetic** — Overscore's dark theme (near-black background, white text, violet/blue/emerald/amber accents). They're useful when you want polished components fast and the analysis aesthetic happens to match.
|
|
9
|
+
|
|
10
|
+
**This file is not the rule for how reports should look.** If the analysis is going in a different direction (editorial print, high-contrast light, warm earth tones, terminal-dense, brand-matched, etc.), skip this file entirely — write components from scratch following the structural and information-design rules in `report-html.md` and `data-viz.md`.
|
|
11
|
+
|
|
12
|
+
Run the `customize-aesthetic` skill to pick a distinctive direction before writing the report. If that direction matches dark/violet, the snippets below give you a head start. Otherwise, they're just one example of how the parts can fit together.
|
|
9
13
|
|
|
10
14
|
---
|
|
11
15
|
|
|
@@ -9,21 +9,23 @@ paths:
|
|
|
9
9
|
|
|
10
10
|
## Hard rules
|
|
11
11
|
|
|
12
|
-
- **Inline all CSS.** No `<link rel="stylesheet">`, no Tailwind CDN, no Google Fonts. Use a single `<style>` block in the `<head>`.
|
|
12
|
+
- **Inline all CSS.** No `<link rel="stylesheet">`, no Tailwind CDN, no Google Fonts (or use a `data:` URI / system stack). Use a single `<style>` block in the `<head>`.
|
|
13
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
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
15
|
- **No `useQuery`, no `@overscore/client`, no React.** This is a static HTML file, not a React app.
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## Aesthetic direction
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
This file does **not** prescribe a visual style. Pick one intentionally with the `customize-aesthetic` skill — analyses are share-once artifacts where distinctive design matters more than consistency.
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
24
|
-
- **
|
|
25
|
-
|
|
26
|
-
-
|
|
21
|
+
A polished report commits to:
|
|
22
|
+
- **A tone** (editorial print, high-contrast minimal, warm earth tones, terminal-dense, etc.)
|
|
23
|
+
- **Typography** (a specific display face + body face — not "system-ui" by default)
|
|
24
|
+
- **A palette** (named hex codes, one or two saturated accents, the rest muted)
|
|
25
|
+
|
|
26
|
+
If `this-analysis.md` has a `## Visual style` section, use it. Otherwise, run the `customize-aesthetic` skill before writing the report skeleton.
|
|
27
|
+
|
|
28
|
+
Avoid the AI default: `#0a0a0f` background, `rgba(255,255,255,0.6)` text, violet/blue accents on glassmorphic cards. It's recognizable as "generated."
|
|
27
29
|
|
|
28
30
|
## Structure
|
|
29
31
|
|
|
@@ -48,38 +50,49 @@ A good analysis report has these sections in order:
|
|
|
48
50
|
<title>The headline finding goes here</title>
|
|
49
51
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
50
52
|
<style>
|
|
53
|
+
:root {
|
|
54
|
+
/* Replace with palette from this-analysis.md ## Visual style */
|
|
55
|
+
--bg: #fafaf7;
|
|
56
|
+
--fg: #1a1a1a;
|
|
57
|
+
--muted: rgba(26,26,26,0.55);
|
|
58
|
+
--subtle: rgba(26,26,26,0.08);
|
|
59
|
+
--accent: #3a3a3a;
|
|
60
|
+
--card: rgba(0,0,0,0.02);
|
|
61
|
+
}
|
|
51
62
|
body {
|
|
52
|
-
background:
|
|
53
|
-
color:
|
|
54
|
-
font-family: -
|
|
63
|
+
background: var(--bg);
|
|
64
|
+
color: var(--fg);
|
|
65
|
+
font-family: ui-serif, Georgia, serif; /* Replace with your chosen face */
|
|
55
66
|
max-width: 900px;
|
|
56
67
|
margin: 0 auto;
|
|
57
68
|
padding: 4rem 2rem;
|
|
58
69
|
line-height: 1.6;
|
|
59
70
|
}
|
|
60
71
|
.card {
|
|
61
|
-
background:
|
|
62
|
-
border: 1px solid
|
|
63
|
-
border-radius:
|
|
72
|
+
background: var(--card);
|
|
73
|
+
border: 1px solid var(--subtle);
|
|
74
|
+
border-radius: 12px;
|
|
64
75
|
padding: 1.5rem;
|
|
65
76
|
margin: 1.5rem 0;
|
|
66
77
|
}
|
|
67
|
-
.kpi { font-size: 2.5rem; font-weight: 600; }
|
|
68
|
-
.label { color:
|
|
78
|
+
.kpi { font-size: 2.5rem; font-weight: 600; font-feature-settings: "tnum"; }
|
|
79
|
+
.label { color: var(--muted); font-size: 0.875rem; text-transform: uppercase; letter-spacing: 0.05em; }
|
|
69
80
|
h1, h2, h3 { font-weight: 600; }
|
|
70
81
|
h1 { font-size: 2rem; margin-bottom: 0.5rem; }
|
|
71
|
-
.footer { margin-top: 4rem; color:
|
|
82
|
+
.footer { margin-top: 4rem; color: var(--muted); font-size: 0.75rem; }
|
|
72
83
|
</style>
|
|
73
84
|
</head>
|
|
74
85
|
<body>
|
|
75
86
|
<h1>Headline finding here</h1>
|
|
76
|
-
<p style="color:
|
|
87
|
+
<p style="color: var(--muted);">Executive summary in 2-3 sentences.</p>
|
|
77
88
|
<!-- KPI cards, charts, conclusions, etc. -->
|
|
78
89
|
<div class="footer">Generated YYYY-MM-DD · {{TITLE}}</div>
|
|
79
90
|
</body>
|
|
80
91
|
</html>
|
|
81
92
|
```
|
|
82
93
|
|
|
94
|
+
The CSS variables are placeholders — replace them with the palette and fonts from `this-analysis.md`. The skeleton above is in a neutral editorial direction; pick what fits the analysis.
|
|
95
|
+
|
|
83
96
|
**Mobile responsiveness.** Reports are often opened on phones from Slack links. The skeleton's `max-width: 900px` and viewport meta tag handle the basics, but also:
|
|
84
97
|
- Use `clamp()` or media queries for KPI font sizes (`font-size: clamp(1.5rem, 5vw, 2.5rem)`) so they don't overflow on 375px screens.
|
|
85
98
|
- KPI card grids should wrap: `display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr))`.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: customize-aesthetic
|
|
3
|
+
description: Drives intentional visual design choices for an Overscore analysis report. Use when the user wants a custom look, says "redesign this", "change the theme", "make it [adjective]", or when the start-analysis interview reaches the aesthetic step.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Customize aesthetic
|
|
7
|
+
|
|
8
|
+
Most AI-generated reports look the same — dark with violet accents, system fonts, glassmorphic cards, generic everything. Resist that default. A report that gets reopened weeks later commits to a specific aesthetic direction.
|
|
9
|
+
|
|
10
|
+
## When to invoke
|
|
11
|
+
|
|
12
|
+
- The user asks to "redesign", "restyle", "make it [adjective]", or says they don't like the current look
|
|
13
|
+
- The `start-analysis` interview has reached the aesthetic step
|
|
14
|
+
- The user describes a tone, brand, or reference ("like a McKinsey deck", "newspaper feel", "warm earth tones")
|
|
15
|
+
|
|
16
|
+
## What this skill does NOT change
|
|
17
|
+
|
|
18
|
+
The data visualization rules in `.claude/rules/data-viz.md` apply regardless of aesthetic:
|
|
19
|
+
- No pie charts, no 3D, no dual y-axes
|
|
20
|
+
- KPI cards with comparisons (green up, red down)
|
|
21
|
+
- 5-9 components max
|
|
22
|
+
- Bars start at zero
|
|
23
|
+
- Mobile-responsive at 375px
|
|
24
|
+
|
|
25
|
+
The report structural rules in `.claude/rules/report-html.md` also stand: inline CSS, baked data, no network calls, self-contained.
|
|
26
|
+
|
|
27
|
+
Aesthetic is the *visual treatment* — typography, palette, density, surface treatment. The *information design* and the *technical envelope* are both settled.
|
|
28
|
+
|
|
29
|
+
## How to choose a direction
|
|
30
|
+
|
|
31
|
+
Before changing any code, get clear answers to three things:
|
|
32
|
+
|
|
33
|
+
1. **Tone in one phrase** — "editorial print", "high-contrast minimal", "warm earth tones", "brutalist data", "Bloomberg terminal", "Notion clean", "Stripe modern". One phrase, not a list of adjectives.
|
|
34
|
+
2. **Typography** — serif/sans/mono? Display face? Body weight? Numbers in tabular figures? Don't accept "Inter" as an answer — that's the default that produces same-looking output. If the user shrugs, suggest a specific face that fits the tone (e.g., Söhne Mono for terminal, Fraunces for editorial).
|
|
35
|
+
3. **Palette** — one or two saturated accents, the rest muted. Pick *named* colors (paper white #fafaf7, ink charcoal #1a1a1a, terracotta #c45a3e) so they read as a system, not as defaults.
|
|
36
|
+
|
|
37
|
+
If the user can't answer, suggest 2-3 distinct directions with a one-line characterization each. Pick one before writing code.
|
|
38
|
+
|
|
39
|
+
## Common failure modes
|
|
40
|
+
|
|
41
|
+
- Reaching for `#0a0a0f` + white text as a "neutral" default — that's the AI default with the brand stripped off, not a distinct aesthetic
|
|
42
|
+
- Mixing two directions ("modern but also editorial") — pick one
|
|
43
|
+
- Adding glassmorphic blur and backdrop filters by default — that's 2023 SaaS, not a choice
|
|
44
|
+
|
|
45
|
+
## After picking a direction
|
|
46
|
+
|
|
47
|
+
Write the aesthetic decision to `this-analysis.md` under a `## Visual style` heading. Include:
|
|
48
|
+
- The tone phrase
|
|
49
|
+
- The typography choice (font families, key sizes)
|
|
50
|
+
- The palette (with hex codes)
|
|
51
|
+
- Any density and surface rules (spacing, borders)
|
|
52
|
+
|
|
53
|
+
This keeps future sessions consistent without re-deciding the same choices.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
Inspired by Anthropic's `frontend-design` skill (github.com/anthropics/claude-code, MIT-licensed) — adapted for Overscore's analysis report context.
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: start-analysis
|
|
3
3
|
description: Interviews the user before querying data at the start of a new Overscore analysis. Captures the real question, known tables, success criteria, hypotheses, time window, and data quirks, then writes this-analysis.md and proposes a query plan for approval. Use when this-analysis.md is empty, or when the user says "investigate", "look into", or "analyze" something for the first time.
|
|
4
|
-
allowed-tools:
|
|
5
|
-
- Read
|
|
6
|
-
- Write
|
|
7
|
-
- Edit
|
|
8
|
-
- Bash(npx @overscore/cli query run *)
|
|
9
4
|
---
|
|
10
5
|
|
|
11
6
|
# Starting an analysis the right way
|
|
@@ -22,7 +17,7 @@ This skill exists to make sure that conversation happens before any SQL runs.
|
|
|
22
17
|
|
|
23
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.
|
|
24
19
|
|
|
25
|
-
## The interview —
|
|
20
|
+
## The interview — 7 questions, 2 minutes
|
|
26
21
|
|
|
27
22
|
Read `interview-template.md` for the literal prompts. The interview covers:
|
|
28
23
|
|
|
@@ -32,8 +27,9 @@ Read `interview-template.md` for the literal prompts. The interview covers:
|
|
|
32
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.
|
|
33
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.
|
|
34
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
|
+
7. **Aesthetic direction for the final report** — ask if they have a tone in mind (editorial print, high-contrast minimal, warm earth tones, terminal-dense, brand-matched) or want you to pick one. Distinctive design matters more on share-once reports than on internal dashboards. If they name a direction, invoke the `customize-aesthetic` skill when you start `report.html`. If they're open, pick one yourself before writing the report — don't default to the AI dark+violet look.
|
|
35
31
|
|
|
36
|
-
Ask these one or two at a time conversationally — don't dump all
|
|
32
|
+
Ask these one or two at a time conversationally — don't dump all 7 at once like a form. The user will respond more openly to a real conversation.
|
|
37
33
|
|
|
38
34
|
## After the interview
|
|
39
35
|
|
|
@@ -47,6 +43,7 @@ Ask these one or two at a time conversationally — don't dump all 6 at once lik
|
|
|
47
43
|
- **Success Criteria** — quote the user on what "done" looks like
|
|
48
44
|
- **Time Window & Filters** — the date range, cohort, what to include/exclude
|
|
49
45
|
- **Known Quirks** — anything about the data the user flagged upfront
|
|
46
|
+
- **Visual style** (if the user named an aesthetic direction): tone phrase, typography, palette with hex codes. See the `customize-aesthetic` skill. Skip if not specified yet — fill in when you start the report.
|
|
50
47
|
|
|
51
48
|
This is the context file that loads automatically every future session. Don't skip it.
|
|
52
49
|
|
|
@@ -24,8 +24,12 @@ These are the literal questions to ask the user during the start-analysis interv
|
|
|
24
24
|
|
|
25
25
|
## 6. Anything weird about this data
|
|
26
26
|
|
|
27
|
-
>
|
|
27
|
+
> 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
|
+
## 7. Aesthetic direction for the report
|
|
30
|
+
|
|
31
|
+
> Last one — when we eventually write the report, any tone in mind? Examples: editorial print, high-contrast minimal, warm earth tones, terminal-dense, your company brand. If you have a reference (a deck, a blog post, an article you liked the look of), tell me. Or just say "pick one for me" and I'll commit to a distinctive direction. (We don't default to the generic AI dark-with-violet look — it reads as "generated.") You can answer this now or when we start the report; either is fine.
|
|
28
32
|
|
|
29
33
|
---
|
|
30
34
|
|
|
31
|
-
After all
|
|
35
|
+
After all 7 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. If the user named an aesthetic direction, note it in `this-analysis.md` so it's ready when you start `report.html` (the `customize-aesthetic` skill will fire then).
|
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
<title>{{TITLE}}</title>
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
7
|
<style>
|
|
8
|
-
|
|
8
|
+
/* Starter — replace with the palette and fonts from this-analysis.md ## Visual style.
|
|
9
|
+
Don't default to the AI look (dark + violet + glassmorphic). Pick a distinctive
|
|
10
|
+
direction first; see the customize-aesthetic skill. */
|
|
11
|
+
body { font-family: ui-sans-serif, system-ui, sans-serif; padding: 4rem 2rem; max-width: 800px; margin: 0 auto; line-height: 1.6; }
|
|
9
12
|
h1 { font-weight: 600; }
|
|
10
|
-
p { color:
|
|
13
|
+
p { color: #555; }
|
|
11
14
|
</style>
|
|
12
15
|
</head>
|
|
13
16
|
<body>
|