@jefuriiij/synthra 0.1.16 → 0.1.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.
@@ -1553,9 +1553,11 @@ function resolvePaths(projectRoot) {
1553
1553
 
1554
1554
  // src/cli/bootstrap.ts
1555
1555
  import { mkdir as mkdir3, readFile as readFile7, stat as stat2, writeFile as writeFile3 } from "fs/promises";
1556
+ import { basename as basename2 } from "path";
1556
1557
 
1557
1558
  // src/hooks/claude-md.ts
1558
1559
  import { readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
1560
+ import { basename, dirname as dirname4 } from "path";
1559
1561
  var POLICY_VERSION = 3;
1560
1562
  var POLICY_BEGIN = `<!-- synthra-policy v${POLICY_VERSION} BEGIN -->`;
1561
1563
  var POLICY_END = `<!-- synthra-policy v${POLICY_VERSION} END -->`;
@@ -1656,7 +1658,39 @@ function policyBlock() {
1656
1658
  POLICY_END
1657
1659
  ].join("\n");
1658
1660
  }
1659
- async function patchClaudeMd(path) {
1661
+ function onboardingSkeleton(projectName) {
1662
+ return [
1663
+ `# ${projectName}`,
1664
+ "",
1665
+ "> Onboarding notes for AI coding agents. Synthra's graph already knows the",
1666
+ "> code's *structure* (files, symbols, imports) \u2014 this file is for what the",
1667
+ "> graph can't infer: how to run the project, its conventions, and the",
1668
+ "> decisions behind them. Keep it lean and current; delete prompts you don't need.",
1669
+ "",
1670
+ "## Build & test",
1671
+ "",
1672
+ "- TODO: install deps / build",
1673
+ "- TODO: run tests / lint / typecheck",
1674
+ "- TODO: run the app locally",
1675
+ "",
1676
+ "## Conventions",
1677
+ "",
1678
+ "- TODO: code style, naming, file layout the agent should follow",
1679
+ "",
1680
+ "## Key decisions",
1681
+ "",
1682
+ '- TODO: non-obvious choices and *why* ("we use X not Y because \u2026")',
1683
+ "",
1684
+ "## Gotchas",
1685
+ "",
1686
+ `- TODO: traps, footguns, "don't touch X without Y"`,
1687
+ "",
1688
+ "_Tip: run `/init` in Claude Code to auto-draft the sections above, then trim",
1689
+ "to the durable bits. Synthra manages its own block below \u2014 leave it._",
1690
+ ""
1691
+ ].join("\n");
1692
+ }
1693
+ async function patchClaudeMd(path, projectName) {
1660
1694
  let existing;
1661
1695
  try {
1662
1696
  existing = await readFile6(path, "utf8");
@@ -1665,7 +1699,8 @@ async function patchClaudeMd(path) {
1665
1699
  }
1666
1700
  const block = policyBlock();
1667
1701
  if (existing === null) {
1668
- await writeFile2(path, block + "\n", "utf8");
1702
+ const name = projectName || basename(dirname4(path)) || "this project";
1703
+ await writeFile2(path, onboardingSkeleton(name) + "\n" + block + "\n", "utf8");
1669
1704
  return { created: true, updated: false, skipped: false };
1670
1705
  }
1671
1706
  const stripped = existing.replace(ANY_BLOCK_RE, "");
@@ -1722,7 +1757,7 @@ async function bootstrap(paths) {
1722
1757
  const contextCreated = await ensureDir(paths.contextDir);
1723
1758
  const gitignoreUpdated = await patchGitignore(paths.gitignore);
1724
1759
  const claudeMdExistedBefore = await exists(paths.claudeMd);
1725
- const patch = await patchClaudeMd(paths.claudeMd);
1760
+ const patch = await patchClaudeMd(paths.claudeMd, basename2(paths.projectRoot));
1726
1761
  return {
1727
1762
  graphCreated,
1728
1763
  contextCreated,
@@ -1775,8 +1810,12 @@ async function scanProject(projectRootRaw, opts = {}) {
1775
1810
  if (boot.graphCreated) log.info(" created .synthra-graph/");
1776
1811
  if (boot.contextCreated) log.info(" created .synthra/");
1777
1812
  if (boot.gitignoreUpdated) log.info(" updated .gitignore");
1778
- if (boot.claudeMdCreated) log.info(" created CLAUDE.md");
1779
- else if (boot.claudeMdUpdated) log.info(" updated CLAUDE.md");
1813
+ if (boot.claudeMdCreated) {
1814
+ log.info(" created CLAUDE.md \u2014 onboarding skeleton for the agent");
1815
+ log.info(" \u21B3 fill in Build / Conventions / Decisions (or run /init in Claude to auto-draft)");
1816
+ } else if (boot.claudeMdUpdated) {
1817
+ log.info(" updated CLAUDE.md");
1818
+ }
1780
1819
  }
1781
1820
  const walked = [];
1782
1821
  for await (const file of walk(projectRoot)) walked.push(file);
@@ -2073,7 +2112,7 @@ function resolveBranchPaths(contextDir, branch, isDefault) {
2073
2112
 
2074
2113
  // src/memory/context-md.ts
2075
2114
  import { mkdir as mkdir4, readFile as readFile9, writeFile as writeFile4 } from "fs/promises";
2076
- import { dirname as dirname4 } from "path";
2115
+ import { dirname as dirname5 } from "path";
2077
2116
  var MAX_BULLETS = 3;
2078
2117
  function deriveContextMd(entries, branch) {
2079
2118
  const tasks = entries.filter((e) => e.type === "task").reverse();
@@ -2116,13 +2155,13 @@ function formatContextMd(ctx) {
2116
2155
  return lines.join("\n");
2117
2156
  }
2118
2157
  async function writeContextMd(path, ctx) {
2119
- await mkdir4(dirname4(path), { recursive: true });
2158
+ await mkdir4(dirname5(path), { recursive: true });
2120
2159
  await writeFile4(path, formatContextMd(ctx), "utf8");
2121
2160
  }
2122
2161
 
2123
2162
  // src/memory/context-store.ts
2124
2163
  import { mkdir as mkdir5, readFile as readFile10, writeFile as writeFile5 } from "fs/promises";
2125
- import { dirname as dirname5 } from "path";
2164
+ import { dirname as dirname6 } from "path";
2126
2165
  var SCHEMA_VERSION = 1;
2127
2166
  async function readEntries(path) {
2128
2167
  try {
@@ -2134,7 +2173,7 @@ async function readEntries(path) {
2134
2173
  }
2135
2174
  }
2136
2175
  async function writeEntries(path, entries) {
2137
- await mkdir5(dirname5(path), { recursive: true });
2176
+ await mkdir5(dirname6(path), { recursive: true });
2138
2177
  const store = { schema_version: SCHEMA_VERSION, entries };
2139
2178
  await writeFile5(path, JSON.stringify(store, null, 2) + "\n", "utf8");
2140
2179
  }
@@ -2848,7 +2887,7 @@ async function handleContextUpdate(req, ctx) {
2848
2887
 
2849
2888
  // src/server/routes/gate.ts
2850
2889
  import { appendFile as appendFile2, mkdir as mkdir6 } from "fs/promises";
2851
- import { dirname as dirname6 } from "path";
2890
+ import { dirname as dirname7 } from "path";
2852
2891
  var BLOCKABLE_TOOLS = /* @__PURE__ */ new Set(["Grep", "Glob"]);
2853
2892
  var RECENT_ACTIVITY_WINDOW_MS = 5 * 60 * 1e3;
2854
2893
  function extractQuery(toolName, input) {
@@ -2878,7 +2917,7 @@ function recentlyTouchedMatchesQuery(recentPaths, queryTokens) {
2878
2917
  }
2879
2918
  async function logDecision(ctx, toolName, query, decision, reason) {
2880
2919
  try {
2881
- await mkdir6(dirname6(ctx.paths.gateLog), { recursive: true });
2920
+ await mkdir6(dirname7(ctx.paths.gateLog), { recursive: true });
2882
2921
  const entry = {
2883
2922
  ts: (/* @__PURE__ */ new Date()).toISOString(),
2884
2923
  tool: toolName,
@@ -2935,14 +2974,14 @@ async function handleGate(req, ctx) {
2935
2974
 
2936
2975
  // src/server/routes/log.ts
2937
2976
  import { appendFile as appendFile3, mkdir as mkdir7 } from "fs/promises";
2938
- import { dirname as dirname7 } from "path";
2977
+ import { dirname as dirname8 } from "path";
2939
2978
  async function handleLog(entry, ctx) {
2940
2979
  if (!entry || typeof entry.input_tokens !== "number" || typeof entry.output_tokens !== "number") {
2941
2980
  throw new Error("log: input_tokens and output_tokens (number) are required");
2942
2981
  }
2943
2982
  const written_at = (/* @__PURE__ */ new Date()).toISOString();
2944
2983
  const record = { ...entry, written_at };
2945
- await mkdir7(dirname7(ctx.paths.tokenLog), { recursive: true });
2984
+ await mkdir7(dirname8(ctx.paths.tokenLog), { recursive: true });
2946
2985
  await appendFile3(ctx.paths.tokenLog, JSON.stringify(record) + "\n", "utf8");
2947
2986
  return { ok: true, written_at };
2948
2987
  }