@jefuriiij/synthra 0.1.15 → 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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,35 @@ For older versions, see [GitHub Releases](https://github.com/jefuriiij/synthra/r
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.1.17] — 2026-05-29
11
+
12
+ ### Added
13
+
14
+ - **`syn .` scaffolds an agent-onboarding CLAUDE.md on brand-new projects.**
15
+ When a project has no CLAUDE.md, Synthra now writes a lean skeleton —
16
+ `Build & test`, `Conventions`, `Key decisions`, `Gotchas` (with TODO
17
+ prompts) — *above* its managed policy block, instead of a bare policy
18
+ block. This is the durable "why/how" layer the graph can't infer; the
19
+ graph still owns "what/where." Fill it in, or run `/init` to auto-draft.
20
+ The skeleton is written **once** and lives outside the
21
+ `<!-- synthra-policy -->` markers, so re-running `syn .` (which
22
+ refreshes the policy block) never clobbers what you've written.
23
+ Projects that already have a CLAUDE.md are untouched — no skeleton is
24
+ injected.
25
+
26
+ ---
27
+
28
+ ## [0.1.16] — 2026-05-29
29
+
30
+ ### Changed
31
+
32
+ - **Moat card shows 50 recent gate decisions** (was 12). The inline list
33
+ already scrolls within the card, and the `/data` payload already carries
34
+ up to 500 gates, so this just renders more of them. The headline block
35
+ count was always all-time/uncapped — unchanged.
36
+
37
+ ---
38
+
10
39
  ## [0.1.15] — 2026-05-29
11
40
 
12
41
  ### Changed
package/dist/cli/index.js CHANGED
@@ -18,7 +18,7 @@ var init_package = __esm({
18
18
  "package.json"() {
19
19
  package_default = {
20
20
  name: "@jefuriiij/synthra",
21
- version: "0.1.15",
21
+ version: "0.1.17",
22
22
  publishConfig: {
23
23
  access: "public"
24
24
  },
@@ -1041,7 +1041,7 @@ var public_default = `<!doctype html>
1041
1041
  return;
1042
1042
  }
1043
1043
  const frag = document.createDocumentFragment();
1044
- for (const g of gates.slice(0, 12)) {
1044
+ for (const g of gates.slice(0, 50)) {
1045
1045
  const row = document.createElement('div');
1046
1046
  row.className = 'gate-row';
1047
1047
  const cls = g.decision === 'block' ? 'block' : 'allow';
@@ -3198,9 +3198,11 @@ async function readSymbolIndex(path) {
3198
3198
 
3199
3199
  // src/cli/bootstrap.ts
3200
3200
  import { mkdir as mkdir5, readFile as readFile10, stat as stat2, writeFile as writeFile5 } from "fs/promises";
3201
+ import { basename as basename4 } from "path";
3201
3202
 
3202
3203
  // src/hooks/claude-md.ts
3203
3204
  import { readFile as readFile9, writeFile as writeFile4 } from "fs/promises";
3205
+ import { basename as basename3, dirname as dirname6 } from "path";
3204
3206
  var POLICY_VERSION = 3;
3205
3207
  var POLICY_BEGIN = `<!-- synthra-policy v${POLICY_VERSION} BEGIN -->`;
3206
3208
  var POLICY_END = `<!-- synthra-policy v${POLICY_VERSION} END -->`;
@@ -3301,7 +3303,39 @@ function policyBlock() {
3301
3303
  POLICY_END
3302
3304
  ].join("\n");
3303
3305
  }
3304
- async function patchClaudeMd(path) {
3306
+ function onboardingSkeleton(projectName) {
3307
+ return [
3308
+ `# ${projectName}`,
3309
+ "",
3310
+ "> Onboarding notes for AI coding agents. Synthra's graph already knows the",
3311
+ "> code's *structure* (files, symbols, imports) \u2014 this file is for what the",
3312
+ "> graph can't infer: how to run the project, its conventions, and the",
3313
+ "> decisions behind them. Keep it lean and current; delete prompts you don't need.",
3314
+ "",
3315
+ "## Build & test",
3316
+ "",
3317
+ "- TODO: install deps / build",
3318
+ "- TODO: run tests / lint / typecheck",
3319
+ "- TODO: run the app locally",
3320
+ "",
3321
+ "## Conventions",
3322
+ "",
3323
+ "- TODO: code style, naming, file layout the agent should follow",
3324
+ "",
3325
+ "## Key decisions",
3326
+ "",
3327
+ '- TODO: non-obvious choices and *why* ("we use X not Y because \u2026")',
3328
+ "",
3329
+ "## Gotchas",
3330
+ "",
3331
+ `- TODO: traps, footguns, "don't touch X without Y"`,
3332
+ "",
3333
+ "_Tip: run `/init` in Claude Code to auto-draft the sections above, then trim",
3334
+ "to the durable bits. Synthra manages its own block below \u2014 leave it._",
3335
+ ""
3336
+ ].join("\n");
3337
+ }
3338
+ async function patchClaudeMd(path, projectName) {
3305
3339
  let existing;
3306
3340
  try {
3307
3341
  existing = await readFile9(path, "utf8");
@@ -3310,7 +3344,8 @@ async function patchClaudeMd(path) {
3310
3344
  }
3311
3345
  const block = policyBlock();
3312
3346
  if (existing === null) {
3313
- await writeFile4(path, block + "\n", "utf8");
3347
+ const name = projectName || basename3(dirname6(path)) || "this project";
3348
+ await writeFile4(path, onboardingSkeleton(name) + "\n" + block + "\n", "utf8");
3314
3349
  return { created: true, updated: false, skipped: false };
3315
3350
  }
3316
3351
  const stripped = existing.replace(ANY_BLOCK_RE, "");
@@ -3367,7 +3402,7 @@ async function bootstrap(paths) {
3367
3402
  const contextCreated = await ensureDir(paths.contextDir);
3368
3403
  const gitignoreUpdated = await patchGitignore(paths.gitignore);
3369
3404
  const claudeMdExistedBefore = await exists(paths.claudeMd);
3370
- const patch = await patchClaudeMd(paths.claudeMd);
3405
+ const patch = await patchClaudeMd(paths.claudeMd, basename4(paths.projectRoot));
3371
3406
  return {
3372
3407
  graphCreated,
3373
3408
  contextCreated,
@@ -3420,8 +3455,12 @@ async function scanProject(projectRootRaw, opts = {}) {
3420
3455
  if (boot.graphCreated) log.info(" created .synthra-graph/");
3421
3456
  if (boot.contextCreated) log.info(" created .synthra/");
3422
3457
  if (boot.gitignoreUpdated) log.info(" updated .gitignore");
3423
- if (boot.claudeMdCreated) log.info(" created CLAUDE.md");
3424
- else if (boot.claudeMdUpdated) log.info(" updated CLAUDE.md");
3458
+ if (boot.claudeMdCreated) {
3459
+ log.info(" created CLAUDE.md \u2014 onboarding skeleton for the agent");
3460
+ log.info(" \u21B3 fill in Build / Conventions / Decisions (or run /init in Claude to auto-draft)");
3461
+ } else if (boot.claudeMdUpdated) {
3462
+ log.info(" updated CLAUDE.md");
3463
+ }
3425
3464
  }
3426
3465
  const walked = [];
3427
3466
  for await (const file of walk(projectRoot)) walked.push(file);
@@ -3721,7 +3760,7 @@ function resolveBranchPaths(contextDir, branch, isDefault) {
3721
3760
 
3722
3761
  // src/memory/context-md.ts
3723
3762
  import { mkdir as mkdir6, readFile as readFile12, writeFile as writeFile6 } from "fs/promises";
3724
- import { dirname as dirname6 } from "path";
3763
+ import { dirname as dirname7 } from "path";
3725
3764
  var MAX_BULLETS = 3;
3726
3765
  function deriveContextMd(entries, branch) {
3727
3766
  const tasks = entries.filter((e) => e.type === "task").reverse();
@@ -3764,13 +3803,13 @@ function formatContextMd(ctx) {
3764
3803
  return lines.join("\n");
3765
3804
  }
3766
3805
  async function writeContextMd(path, ctx) {
3767
- await mkdir6(dirname6(path), { recursive: true });
3806
+ await mkdir6(dirname7(path), { recursive: true });
3768
3807
  await writeFile6(path, formatContextMd(ctx), "utf8");
3769
3808
  }
3770
3809
 
3771
3810
  // src/memory/context-store.ts
3772
3811
  import { mkdir as mkdir7, readFile as readFile13, writeFile as writeFile7 } from "fs/promises";
3773
- import { dirname as dirname7 } from "path";
3812
+ import { dirname as dirname8 } from "path";
3774
3813
  var SCHEMA_VERSION2 = 1;
3775
3814
  async function readEntries(path) {
3776
3815
  try {
@@ -3782,7 +3821,7 @@ async function readEntries(path) {
3782
3821
  }
3783
3822
  }
3784
3823
  async function writeEntries(path, entries) {
3785
- await mkdir7(dirname7(path), { recursive: true });
3824
+ await mkdir7(dirname8(path), { recursive: true });
3786
3825
  const store = { schema_version: SCHEMA_VERSION2, entries };
3787
3826
  await writeFile7(path, JSON.stringify(store, null, 2) + "\n", "utf8");
3788
3827
  }
@@ -4477,7 +4516,7 @@ async function handleContextUpdate(req, ctx) {
4477
4516
 
4478
4517
  // src/server/routes/gate.ts
4479
4518
  import { appendFile as appendFile2, mkdir as mkdir8 } from "fs/promises";
4480
- import { dirname as dirname8 } from "path";
4519
+ import { dirname as dirname9 } from "path";
4481
4520
  var BLOCKABLE_TOOLS = /* @__PURE__ */ new Set(["Grep", "Glob"]);
4482
4521
  var RECENT_ACTIVITY_WINDOW_MS = 5 * 60 * 1e3;
4483
4522
  function extractQuery(toolName, input) {
@@ -4507,7 +4546,7 @@ function recentlyTouchedMatchesQuery(recentPaths, queryTokens) {
4507
4546
  }
4508
4547
  async function logDecision(ctx, toolName, query, decision, reason) {
4509
4548
  try {
4510
- await mkdir8(dirname8(ctx.paths.gateLog), { recursive: true });
4549
+ await mkdir8(dirname9(ctx.paths.gateLog), { recursive: true });
4511
4550
  const entry = {
4512
4551
  ts: (/* @__PURE__ */ new Date()).toISOString(),
4513
4552
  tool: toolName,
@@ -4564,14 +4603,14 @@ async function handleGate(req, ctx) {
4564
4603
 
4565
4604
  // src/server/routes/log.ts
4566
4605
  import { appendFile as appendFile3, mkdir as mkdir9 } from "fs/promises";
4567
- import { dirname as dirname9 } from "path";
4606
+ import { dirname as dirname10 } from "path";
4568
4607
  async function handleLog(entry, ctx) {
4569
4608
  if (!entry || typeof entry.input_tokens !== "number" || typeof entry.output_tokens !== "number") {
4570
4609
  throw new Error("log: input_tokens and output_tokens (number) are required");
4571
4610
  }
4572
4611
  const written_at = (/* @__PURE__ */ new Date()).toISOString();
4573
4612
  const record = { ...entry, written_at };
4574
- await mkdir9(dirname9(ctx.paths.tokenLog), { recursive: true });
4613
+ await mkdir9(dirname10(ctx.paths.tokenLog), { recursive: true });
4575
4614
  await appendFile3(ctx.paths.tokenLog, JSON.stringify(record) + "\n", "utf8");
4576
4615
  return { ok: true, written_at };
4577
4616
  }