@overscore/cli 0.12.0 → 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/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
|
}
|