@overscore/cli 0.11.7 → 0.11.11

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
@@ -1171,6 +1171,9 @@ async function syncHubManagedRules(targetDir, baseUrl, projectSlug, apiKey) {
1171
1171
  catch {
1172
1172
  // Non-fatal
1173
1173
  }
1174
+ // Platform rules (skills, capabilities) — same sync that dashboard pull/deploy gets.
1175
+ await syncPlatformRules(targetDir, baseUrl, apiKey);
1176
+ syncClaudeMdImports(targetDir);
1174
1177
  }
1175
1178
  /**
1176
1179
  * Sync the project knowledge base from the Hub into .claude/knowledge/.
@@ -1259,15 +1262,49 @@ async function uploadKnowledgeBase(targetDir, baseUrl, projectSlug, apiKey) {
1259
1262
  // Non-fatal
1260
1263
  }
1261
1264
  }
1265
+ const DASHBOARD_IMPORTS = [
1266
+ "@.claude/rules/this-dashboard.md",
1267
+ "@.claude/knowledge/INDEX.md",
1268
+ "@.claude/platform-capabilities.md",
1269
+ ];
1270
+ const ANALYSIS_IMPORTS = [
1271
+ "@this-analysis.md",
1272
+ "@.claude/knowledge/INDEX.md",
1273
+ "@.claude/platform-capabilities.md",
1274
+ ];
1275
+ /**
1276
+ * Ensures the project's CLAUDE.md has the standard @import lines at the top.
1277
+ * Detects dashboard vs analysis by reading the CLAUDE.md heading. Injects only
1278
+ * what's missing — never overwrites existing content. Returns true if modified.
1279
+ */
1280
+ function syncClaudeMdImports(targetDir) {
1281
+ const claudeMdPath = path.join(targetDir, ".claude", "CLAUDE.md");
1282
+ if (!fs.existsSync(claudeMdPath))
1283
+ return false;
1284
+ const content = fs.readFileSync(claudeMdPath, "utf-8");
1285
+ const firstHeading = content.match(/^#\s+Overscore\s+(Dashboard|Analysis)/im);
1286
+ if (!firstHeading)
1287
+ return false;
1288
+ const isAnalysis = firstHeading[1].toLowerCase() === "analysis";
1289
+ const requiredImports = isAnalysis ? ANALYSIS_IMPORTS : DASHBOARD_IMPORTS;
1290
+ const missing = requiredImports.filter((imp) => !content.includes(imp));
1291
+ if (missing.length === 0)
1292
+ return false;
1293
+ // Inject missing imports right after the first # heading line
1294
+ const lines = content.split("\n");
1295
+ const headingIdx = lines.findIndex((l) => l.startsWith("# "));
1296
+ if (headingIdx === -1)
1297
+ return false;
1298
+ lines.splice(headingIdx + 1, 0, "", ...missing);
1299
+ const updated = lines.join("\n").replace(/\n{3,}/g, "\n\n");
1300
+ fs.writeFileSync(claudeMdPath, updated);
1301
+ return true;
1302
+ }
1262
1303
  /**
1263
1304
  * Fetch the latest platform-owned Claude Code rules from the Hub and write
1264
- * them into the project's .claude/ directory. This keeps rules like
1265
- * data-fetching.md, query-performance.md, and skills up to date even in
1266
- * projects that were scaffolded months ago.
1267
- *
1268
- * Called from `deploy` (before uploading) and `pull` (after extracting).
1269
- * User-owned files (company-context.md, this-dashboard.md, knowledge/)
1270
- * are NOT touched — those sync via separate mechanisms.
1305
+ * them into the project's .claude/ directory. Called from `deploy` (before
1306
+ * uploading), `pull` (after extracting), and `syncHubManagedRules` (analysis
1307
+ * paths). User-owned files (knowledge/, this-dashboard.md) are NOT touched.
1271
1308
  */
1272
1309
  async function syncPlatformRules(targetDir, baseUrl, apiKey) {
1273
1310
  try {
@@ -1286,14 +1323,85 @@ async function syncPlatformRules(targetDir, baseUrl, apiKey) {
1286
1323
  fs.writeFileSync(fullPath, content);
1287
1324
  count++;
1288
1325
  }
1289
- if (count > 0) {
1290
- console.log(` Platform rules synced (${count} files).`);
1326
+ const claudeUpdated = syncClaudeMdImports(targetDir);
1327
+ const total = count + (claudeUpdated ? 1 : 0);
1328
+ if (total > 0) {
1329
+ console.log(` Platform rules synced (${count} files${claudeUpdated ? " + CLAUDE.md updated" : ""}).`);
1291
1330
  }
1292
1331
  }
1293
1332
  catch {
1294
1333
  // Non-fatal — existing rules stay in place
1295
1334
  }
1296
1335
  }
1336
+ /**
1337
+ * Explicit on-demand platform rules sync. Reports what changed so Claude
1338
+ * can tell the user what's new. Called by `npx @overscore/cli platform update`.
1339
+ */
1340
+ async function platformUpdate() {
1341
+ const { apiUrl, apiKey } = await loadEnv();
1342
+ const baseUrl = apiUrl.replace(/\/api$/, "");
1343
+ console.log("\n Syncing platform rules from Hub...\n");
1344
+ const capPath = path.resolve(process.cwd(), ".claude", "platform-capabilities.md");
1345
+ const prevContent = fs.existsSync(capPath) ? fs.readFileSync(capPath, "utf-8") : null;
1346
+ const prevVersion = prevContent?.match(/^version:\s*(.+)$/m)?.[1]?.trim() ?? null;
1347
+ const res = await fetch(`${baseUrl}/api/projects/platform-rules`, {
1348
+ headers: { Authorization: `Bearer ${apiKey}` },
1349
+ });
1350
+ if (!res.ok) {
1351
+ console.error(`\n Error: Could not reach Hub (HTTP ${res.status})\n`);
1352
+ process.exit(1);
1353
+ }
1354
+ const data = (await res.json());
1355
+ if (!data.files) {
1356
+ console.error("\n Error: No files in platform-rules response.\n");
1357
+ process.exit(1);
1358
+ }
1359
+ const updated = [];
1360
+ for (const [relPath, content] of Object.entries(data.files)) {
1361
+ const fullPath = path.join(process.cwd(), ".claude", relPath);
1362
+ const existing = fs.existsSync(fullPath) ? fs.readFileSync(fullPath, "utf-8") : null;
1363
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
1364
+ fs.writeFileSync(fullPath, content);
1365
+ if (existing !== content)
1366
+ updated.push(relPath);
1367
+ }
1368
+ const claudeUpdated = syncClaudeMdImports(process.cwd());
1369
+ if (claudeUpdated)
1370
+ updated.push(".claude/CLAUDE.md (@imports updated)");
1371
+ if (updated.length === 0) {
1372
+ console.log(" Already up to date — no changes.\n");
1373
+ return;
1374
+ }
1375
+ console.log(` Updated ${updated.length} file${updated.length === 1 ? "" : "s"}:\n`);
1376
+ for (const f of updated)
1377
+ console.log(` ${f}`);
1378
+ if (updated.includes("platform-capabilities.md")) {
1379
+ const newContent = data.files["platform-capabilities.md"];
1380
+ const newVersion = newContent.match(/^version:\s*(.+)$/m)?.[1]?.trim() ?? "unknown";
1381
+ if (prevVersion && prevVersion !== newVersion) {
1382
+ console.log(`\n Capabilities updated: ${prevVersion} → ${newVersion}`);
1383
+ const changelog = extractChangelogSince(newContent, prevVersion);
1384
+ if (changelog)
1385
+ console.log(`\n What's new:\n\n${changelog}`);
1386
+ }
1387
+ else if (!prevVersion) {
1388
+ console.log(`\n Platform capabilities synced (v${newVersion}).`);
1389
+ console.log(` Re-read .claude/platform-capabilities.md in your Claude session to see available commands.`);
1390
+ }
1391
+ }
1392
+ console.log();
1393
+ }
1394
+ function extractChangelogSince(content, sinceVersion) {
1395
+ const match = content.match(/## Changelog\n([\s\S]*?)(?=\n## |\s*$)/);
1396
+ if (!match)
1397
+ return null;
1398
+ const blocks = match[1].split(/(?=### )/);
1399
+ const newer = blocks.filter((b) => {
1400
+ const v = b.match(/### (\d{4}-\d{2}-\d{2})/)?.[1];
1401
+ return v && v > sinceVersion;
1402
+ });
1403
+ return newer.length ? newer.join("").trim() : null;
1404
+ }
1297
1405
  /**
1298
1406
  * Recursively copies a template directory to a target directory, substituting
1299
1407
  * `{{KEY}}` placeholders in text files. Mirrors directory structure.
@@ -1616,6 +1724,9 @@ async function analysisPublish() {
1616
1724
  console.log(`\n URL: ${data.url}\n`);
1617
1725
  // Upload knowledge base so insights from this analysis persist
1618
1726
  await uploadKnowledgeBase(process.cwd(), baseUrl, meta.project_slug, cfg.apiKey);
1727
+ // Sync platform rules so skills and capabilities stay current
1728
+ await syncPlatformRules(process.cwd(), baseUrl, cfg.apiKey);
1729
+ syncClaudeMdImports(process.cwd());
1619
1730
  }
1620
1731
  /**
1621
1732
  * Ensure analysis.json has a valid id by fetching from the Hub.
@@ -2281,6 +2392,22 @@ else if (command === "analysis") {
2281
2392
  `);
2282
2393
  }
2283
2394
  }
2395
+ else if (command === "platform") {
2396
+ if (subcommand === "update") {
2397
+ platformUpdate();
2398
+ }
2399
+ else {
2400
+ console.log(`
2401
+ overscore platform — Manage platform rules and capabilities
2402
+
2403
+ Commands:
2404
+ update Sync latest platform rules, skills, and capabilities from Hub
2405
+
2406
+ Example:
2407
+ npx @overscore/cli platform update
2408
+ `);
2409
+ }
2410
+ }
2284
2411
  else if (command === "query") {
2285
2412
  if (subcommand === "list") {
2286
2413
  queryList();
@@ -2339,6 +2466,7 @@ else {
2339
2466
  pull <slug> Pull source code from the hub
2340
2467
  versions List deploy history
2341
2468
  query <subcommand> Manage dashboard queries
2469
+ platform <subcommand> Sync platform rules and capabilities
2342
2470
  analysis <subcommand> Manage analyses (frozen investigations)
2343
2471
  install-skills Install the Overscore skill library into ~/.claude/skills/
2344
2472
 
@@ -2350,6 +2478,7 @@ else {
2350
2478
  npx @overscore/cli pull <slug>
2351
2479
  npx @overscore/cli pull <slug> --version 3
2352
2480
  npx @overscore/cli versions
2481
+ npx @overscore/cli platform update
2353
2482
  npx @overscore/cli query list
2354
2483
  npx @overscore/cli query add <name> "<sql>"
2355
2484
  npx @overscore/cli query update <name> "<sql>"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@overscore/cli",
3
- "version": "0.11.7",
3
+ "version": "0.11.11",
4
4
  "description": "CLI for deploying Overscore dashboards and publishing analyses",
5
5
  "bin": {
6
6
  "overscore": "dist/index.js"
@@ -2,6 +2,7 @@
2
2
 
3
3
  @this-analysis.md
4
4
  @.claude/knowledge/INDEX.md
5
+ @.claude/platform-capabilities.md
5
6
 
6
7
  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.
7
8