@overscore/cli 0.13.16 → 0.13.17

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.
Files changed (2) hide show
  1. package/dist/index.js +137 -2
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1581,6 +1581,123 @@ function syncClaudeMdImports(targetDir) {
1581
1581
  fs.writeFileSync(claudeMdPath, updated);
1582
1582
  return true;
1583
1583
  }
1584
+ /** Recursively copy a directory (files + subdirs), overwriting at the destination. */
1585
+ function copyDirRecursive(src, dest) {
1586
+ fs.mkdirSync(dest, { recursive: true });
1587
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
1588
+ const s = path.join(src, entry.name);
1589
+ const d = path.join(dest, entry.name);
1590
+ if (entry.isDirectory())
1591
+ copyDirRecursive(s, d);
1592
+ else if (entry.isFile())
1593
+ fs.copyFileSync(s, d);
1594
+ }
1595
+ }
1596
+ /**
1597
+ * Derive a cross-tool AGENTS.md from an existing .claude/CLAUDE.md. Codex,
1598
+ * Cursor, Copilot, and Gemini read AGENTS.md but do NOT resolve Claude Code's
1599
+ * `@import` lines, so the leading @import block becomes prose "read when
1600
+ * relevant" pointers, a reference-guides list of .claude/rules/*.md is added,
1601
+ * and the rest of the CLAUDE.md body is kept verbatim. Returns null if the
1602
+ * CLAUDE.md doesn't look like an Overscore artifact.
1603
+ */
1604
+ function deriveAgentsMdFromClaudeMd(targetDir, content) {
1605
+ const lines = content.split("\n");
1606
+ const headingIdx = lines.findIndex((l) => /^#\s+Overscore\s+(Dashboard|Analysis)/i.test(l));
1607
+ if (headingIdx === -1)
1608
+ return null;
1609
+ // The @import lines follow the heading (blank lines allowed between); the
1610
+ // first other non-blank line begins the body.
1611
+ const importTargets = [];
1612
+ let bodyStart = headingIdx + 1;
1613
+ for (let i = headingIdx + 1; i < lines.length; i++) {
1614
+ const t = lines[i].trim();
1615
+ if (t === "") {
1616
+ bodyStart = i + 1;
1617
+ continue;
1618
+ }
1619
+ if (t.startsWith("@")) {
1620
+ importTargets.push(t.slice(1));
1621
+ bodyStart = i + 1;
1622
+ continue;
1623
+ }
1624
+ bodyStart = i;
1625
+ break;
1626
+ }
1627
+ const body = lines.slice(bodyStart).join("\n").replace(/^\n+/, "");
1628
+ const memBullets = importTargets.length
1629
+ ? importTargets.map((p) => `- **${p}** — load when relevant to your task.`).join("\n")
1630
+ : "- (this project has no shared context files yet)";
1631
+ // Reference guides: every .claude/rules/*.md except the per-artifact memory
1632
+ // file (already listed above as a memory pointer).
1633
+ const memBasenames = new Set(importTargets.map((p) => path.basename(p)));
1634
+ const rulesDir = path.join(targetDir, ".claude", "rules");
1635
+ let refBlock = "";
1636
+ if (fs.existsSync(rulesDir)) {
1637
+ const ruleFiles = fs
1638
+ .readdirSync(rulesDir)
1639
+ .filter((f) => f.endsWith(".md") &&
1640
+ !memBasenames.has(f) &&
1641
+ f !== "this-dashboard.md" &&
1642
+ f !== "this-analysis.md")
1643
+ .sort();
1644
+ if (ruleFiles.length > 0) {
1645
+ refBlock =
1646
+ "\n## Reference guides — read the relevant one when the task calls for it\n\n" +
1647
+ "This project's authoring rules live in these files. Read the relevant one on demand; you don't need to read them all every session.\n\n" +
1648
+ ruleFiles.map((f) => `- **.claude/rules/${f}**`).join("\n") +
1649
+ "\n";
1650
+ }
1651
+ }
1652
+ return `${lines[headingIdx]}
1653
+
1654
+ ## Project instructions and memory
1655
+
1656
+ This project keeps its context in Markdown files. You don't need to read all of them every session; load the ones relevant to your task:
1657
+
1658
+ ${memBullets}
1659
+ ${refBlock}
1660
+ ${body}`;
1661
+ }
1662
+ /**
1663
+ * Ensure the cross-tool layout (Codex / Cursor / Copilot / Gemini) exists
1664
+ * alongside Claude Code's .claude/:
1665
+ * - .agents/skills/ — the cross-tool Agent Skills location, mirrored from
1666
+ * .claude/skills so it stays current as the Hub OTA updates skills.
1667
+ * - AGENTS.md — a root instruction file derived from .claude/CLAUDE.md,
1668
+ * created only if one doesn't already exist (never clobbers user edits).
1669
+ * This is what retrofits an existing (Claude-only) project for Codex on the
1670
+ * next deploy / pull / `platform update`. Returns paths newly created, for
1671
+ * reporting. Best-effort: never throws.
1672
+ */
1673
+ function ensureCodexLayout(targetDir) {
1674
+ const created = [];
1675
+ try {
1676
+ const claudeSkills = path.join(targetDir, ".claude", "skills");
1677
+ if (fs.existsSync(claudeSkills)) {
1678
+ const agentsSkills = path.join(targetDir, ".agents", "skills");
1679
+ const existed = fs.existsSync(agentsSkills);
1680
+ // Re-mirror every sync so new/updated skills propagate; only report the
1681
+ // first-time creation to keep steady-state syncs quiet.
1682
+ copyDirRecursive(claudeSkills, agentsSkills);
1683
+ if (!existed)
1684
+ created.push(".agents/skills/");
1685
+ }
1686
+ const agentsMdPath = path.join(targetDir, "AGENTS.md");
1687
+ const claudeMdPath = path.join(targetDir, ".claude", "CLAUDE.md");
1688
+ if (!fs.existsSync(agentsMdPath) && fs.existsSync(claudeMdPath)) {
1689
+ const agents = deriveAgentsMdFromClaudeMd(targetDir, fs.readFileSync(claudeMdPath, "utf-8"));
1690
+ if (agents) {
1691
+ fs.writeFileSync(agentsMdPath, agents);
1692
+ created.push("AGENTS.md");
1693
+ }
1694
+ }
1695
+ }
1696
+ catch {
1697
+ // Non-fatal — cross-tool layout is best-effort.
1698
+ }
1699
+ return created;
1700
+ }
1584
1701
  function platformMetaPath(targetDir) {
1585
1702
  return path.join(targetDir, ".claude", ".platform-meta.json");
1586
1703
  }
@@ -1696,8 +1813,14 @@ async function syncPlatformRules(targetDir, baseUrl, apiKey) {
1696
1813
  }
1697
1814
  writePlatformMeta(targetDir, meta);
1698
1815
  const claudeUpdated = syncClaudeMdImports(targetDir);
1699
- if (written.length > 0 || claudeUpdated) {
1700
- console.log(` Platform rules synced (${written.length} file${written.length === 1 ? "" : "s"}${claudeUpdated ? " + CLAUDE.md updated" : ""}).`);
1816
+ const codexCreated = ensureCodexLayout(targetDir);
1817
+ if (written.length > 0 || claudeUpdated || codexCreated.length > 0) {
1818
+ const extras = [];
1819
+ if (claudeUpdated)
1820
+ extras.push("CLAUDE.md updated");
1821
+ if (codexCreated.length > 0)
1822
+ extras.push(codexCreated.join(" + ") + " added for Codex");
1823
+ console.log(` Platform rules synced (${written.length} file${written.length === 1 ? "" : "s"}${extras.length ? " + " + extras.join(" + ") : ""}).`);
1701
1824
  }
1702
1825
  if (preserved.length > 0) {
1703
1826
  console.log(` Preserved ${preserved.length} locally-modified file${preserved.length === 1 ? "" : "s"} (Hub updates skipped):`);
@@ -1784,11 +1907,23 @@ async function platformUpdate(checkOnly = false, force = false) {
1784
1907
  }
1785
1908
  if (claudeWouldChange)
1786
1909
  updated.push(".claude/CLAUDE.md (@imports would be added)");
1910
+ // Cross-tool (Codex/Cursor/Copilot/Gemini) layout a real run would create.
1911
+ if (!fs.existsSync(path.join(process.cwd(), "AGENTS.md")) &&
1912
+ fs.existsSync(path.join(process.cwd(), ".claude", "CLAUDE.md"))) {
1913
+ updated.push("AGENTS.md (would be created for Codex/cross-tool)");
1914
+ }
1915
+ if (fs.existsSync(path.join(process.cwd(), ".claude", "skills")) &&
1916
+ !fs.existsSync(path.join(process.cwd(), ".agents", "skills"))) {
1917
+ updated.push(".agents/skills/ (would be created for Codex/cross-tool)");
1918
+ }
1787
1919
  }
1788
1920
  else {
1789
1921
  const claudeUpdated = syncClaudeMdImports(process.cwd());
1790
1922
  if (claudeUpdated)
1791
1923
  updated.push(".claude/CLAUDE.md (@imports updated)");
1924
+ for (const p of ensureCodexLayout(process.cwd())) {
1925
+ updated.push(`${p} (created for Codex/cross-tool)`);
1926
+ }
1792
1927
  }
1793
1928
  if (updated.length === 0 && preserved.length === 0) {
1794
1929
  console.log(" Already up to date — no changes.\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@overscore/cli",
3
- "version": "0.13.16",
3
+ "version": "0.13.17",
4
4
  "description": "CLI for deploying Overscore dashboards and publishing analyses",
5
5
  "bin": {
6
6
  "overscore": "dist/index.js"