@jefuriiij/synthra 0.1.10 → 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,109 @@
1
+ # Synthra changelog
2
+
3
+ Notable changes per version. This file ships inside the npm tarball — `syn .`
4
+ reads it after an auto-update to show you what changed.
5
+
6
+ For older versions, see [GitHub Releases](https://github.com/jefuriiij/synthra/releases).
7
+
8
+ ---
9
+
10
+ ## [0.1.11] — 2026-05-29
11
+
12
+ ### Fixed
13
+
14
+ - **Dart parser actually runs now.** Was silently broken since v0.1 due to an
15
+ ABI mismatch (shipped wasm was ABI v15, pinned `web-tree-sitter` only
16
+ supported v13–v14). Every `.dart` file got zero symbols, zero imports —
17
+ the exception was swallowed by the parser's try/catch. Bumped
18
+ `web-tree-sitter` to `^0.25.10` to fix.
19
+ - **Real Dart symbol extraction.** Classes, mixins, extensions, enums,
20
+ typedefs, top-level functions, methods, getters, setters, constructors.
21
+ - **Dart import normalization.** `package:foo/bar.dart` and `dart:async` are
22
+ stripped (cross-project); bare `'sibling.dart'` is rewritten to
23
+ `./sibling.dart` so the project resolver can complete them.
24
+
25
+ ### Changed
26
+
27
+ - **Update check runs on every `syn .`** (no more 24h cache). If you're on
28
+ latest, stays silent. If outdated, prompts `[y/N]` as before.
29
+ - **Auto-update now shows a changelog.** After `npm install -g …@latest`
30
+ succeeds, Synthra prints the new version's section from this file before
31
+ telling you to re-run. Catches `npm install` outside of `syn .` too —
32
+ next startup compares your current version to `~/.synthra/last-seen-version.json`
33
+ and prints if it's newer.
34
+
35
+ ---
36
+
37
+ ## [0.1.10] — 2026-05-29
38
+
39
+ ### Changed
40
+
41
+ - **CLAUDE.md policy v2 → v3.** Session-end now goes through
42
+ `context_remember({kind: "task"|"decision"|"next"})` instead of writing
43
+ `.synthra/CONTEXT.md` directly. The Stop hook always re-rendered CONTEXT.md
44
+ from `context-store.json` — under v2 Claude's direct writes were getting
45
+ wiped on session end. Existing v2 blocks auto-upgrade.
46
+
47
+ ### Added
48
+
49
+ - **Scanner ignores more build caches.** `.dart_tool/`, `.flutter-plugins`,
50
+ `.flutter-plugins-dependencies`, `.gradle/`, `target/`, `Pods/`,
51
+ `DerivedData/`, `__pycache__/`, `.venv/`, `venv/`, `.tox/`,
52
+ `.pytest_cache/`, `.mypy_cache/`, `.ruff_cache/`, `obj/`, `.vs/`.
53
+
54
+ ---
55
+
56
+ ## [0.1.9] — 2026-05-29
57
+
58
+ ### Fixed
59
+
60
+ - **Crash on prototype-colliding symbol names.** `buildSymbolIndex` built
61
+ the lookup on a plain `{}`, so a symbol named `toString` (which every
62
+ Dart class overrides), `constructor`, `valueOf`, etc. resolved to the
63
+ inherited `Object.prototype` member and crashed on `.push`. Now uses
64
+ `Object.create(null)` on both fresh-build and load-from-disk paths.
65
+
66
+ ---
67
+
68
+ ## [0.1.8] — 2026-05-29
69
+
70
+ ### Added
71
+
72
+ - **Interactive auto-update.** `syn .` checks npm at startup; if a newer
73
+ version is available, prompts `[y/N]`. On `y`, runs
74
+ `npm install -g @jefuriiij/synthra@latest` with stdio inherited and
75
+ exits with re-run instructions. Non-TTY runs (CI, piped stdin) fall
76
+ back to a silent one-line hint. `SYN_NO_UPDATE_CHECK=1` opts out.
77
+
78
+ ---
79
+
80
+ ## [0.1.7] — 2026-05-29
81
+
82
+ ### Fixed
83
+
84
+ - **JS parser missed CommonJS imports + JS class names.** Unified TS/JS
85
+ query only matched ES `import_statement`, and used `(type_identifier)`
86
+ for class names — which is TS-grammar-only. Result: every `.js`/`.cjs`/
87
+ `.mjs` file silently produced zero imports, and any class in a JS file
88
+ was skipped. Split into `TS_QUERY` and `JS_QUERY`; JS query adds a
89
+ `require()` capture and uses `(identifier)` for class names.
90
+
91
+ ---
92
+
93
+ ## [0.1.6] — 2026-05-29
94
+
95
+ ### Fixed
96
+
97
+ - **MCP registration now uses `--scope project`** so the Claude Code IDE
98
+ extension actually sees Synthra. The previous `--scope local` wrote to
99
+ a per-project section of `~/.claude.json` that only the `claude` CLI
100
+ reads — invisible to the IDE.
101
+
102
+ ---
103
+
104
+ ## [0.1.5] and earlier
105
+
106
+ See [GitHub commits](https://github.com/jefuriiij/synthra/commits/main) for
107
+ detail. v0.1.5 introduced the v2 policy template with namespace + skip rules;
108
+ v0.1.4 fixed a DEP0190 deprecation on Windows; v0.1.3 was the dashboard
109
+ redesign (Cool Marine palette, FAQ modal, savings audit row).
package/README.md CHANGED
@@ -177,13 +177,15 @@ Claude Code honors the block and pivots to the MCP tool. The structured pack is
177
177
 
178
178
  ## Self-update
179
179
 
180
- Daily fire-and-forget version check against the npm registry. When a newer version is available:
180
+ Every `syn .` checks the npm registry for a newer version (no cache — always fresh; 2s hard timeout; silent fallthrough on network failure). When you're on latest, the check stays silent and `syn .` proceeds. When you're outdated:
181
181
 
182
182
  - **Interactive shell** (TTY) → you see `[syn] Synthra X.Y.Z is available (you have A.B.C). Update now? [y/N]:` *before* the scan starts. Type `y` to install; press Enter (or anything else) to skip and continue with the current version.
183
- - **Non-interactive** (CI, piped stdin) → silent one-line log line, no prompt. Pure fire-and-forget.
183
+ - **Non-interactive** (CI, piped stdin) → silent one-line hint, no prompt.
184
184
  - **Disabled entirely** → set `SYN_NO_UPDATE_CHECK=1`.
185
185
 
186
- On `y`, Synthra spawns `npm install -g @jefuriiij/synthra@latest` with stdio inherited (you see npm's progress), then exits with re-run instructions — the running Node process is still the old version and can't hot-swap its own code mid-run. Cached at `~/.synthra/version-check.json` for 24h so you're never nagged on rapid `syn .` runs.
186
+ On `y`, Synthra spawns `npm install -g @jefuriiij/synthra@latest` with stdio inherited (you see npm's progress), then **prints the new version's `CHANGELOG.md` section** (so you know what you just got), then exits with re-run instructions — the running Node process is still the old version and can't hot-swap its own code mid-run.
187
+
188
+ If you upgrade via `npm install -g @jefuriiij/synthra@latest` directly (outside Synthra's prompt), the next `syn .` notices and prints the changelog anyway. It tracks the last-seen version at `~/.synthra/last-seen-version.json`.
187
189
 
188
190
  ---
189
191
 
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.10",
21
+ version: "0.1.11",
22
22
  publishConfig: {
23
23
  access: "public"
24
24
  },
@@ -39,6 +39,7 @@ var init_package = __esm({
39
39
  "dist",
40
40
  "bin",
41
41
  "README.md",
42
+ "CHANGELOG.md",
42
43
  "LICENSE",
43
44
  "ROADMAP.md"
44
45
  ],
@@ -71,7 +72,7 @@ var init_package = __esm({
71
72
  ignore: "^7.0.0",
72
73
  sade: "^1.8.1",
73
74
  "tree-sitter-wasms": "^0.1.12",
74
- "web-tree-sitter": "~0.22.6"
75
+ "web-tree-sitter": "^0.25.10"
75
76
  },
76
77
  devDependencies: {
77
78
  "@types/cross-spawn": "^6.0.6",
@@ -2103,7 +2104,7 @@ function extractKeywords(content, _ext) {
2103
2104
  }
2104
2105
 
2105
2106
  // src/scanner/extract.ts
2106
- var RESOLVE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".svelte", ".vue"];
2107
+ var RESOLVE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".svelte", ".vue", ".dart"];
2107
2108
  var INDEX_FILES = ["index.ts", "index.tsx", "index.js", "index.jsx", "__init__.py"];
2108
2109
  function fileId(relPath) {
2109
2110
  return `file:${relPath}`;
@@ -2243,7 +2244,7 @@ function buildSymbolIndex(graph) {
2243
2244
  // src/scanner/parser.ts
2244
2245
  import { readFile as readFile6 } from "fs/promises";
2245
2246
  import { createRequire } from "module";
2246
- import Parser from "web-tree-sitter";
2247
+ import { Language, Parser } from "web-tree-sitter";
2247
2248
 
2248
2249
  // src/scanner/parsers/_generic.ts
2249
2250
  function firstLine(text, max = 200) {
@@ -2392,26 +2393,93 @@ async function parseCSharp(f, source) {
2392
2393
 
2393
2394
  // src/scanner/parsers/dart.ts
2394
2395
  var QUERY4 = `
2395
- (class_definition (identifier) @class.name) @class
2396
- (mixin_declaration (identifier) @class.name) @mixin
2397
- (extension_declaration (identifier) @class.name) @ext
2398
- (function_signature (identifier) @function.name) @function
2396
+ (class_definition name: (identifier) @class.name) @class
2397
+ (mixin_declaration (identifier) @mixin.name) @mixin
2398
+ (extension_declaration name: (identifier) @ext.name) @ext
2399
+ (enum_declaration name: (identifier) @enum.name) @enum
2400
+ (type_alias (type_identifier) @typedef.name) @typedef
2401
+
2402
+ (program (function_signature name: (identifier) @function.name) @function)
2403
+
2404
+ (method_signature (function_signature name: (identifier) @method.name)) @method
2405
+ (method_signature (getter_signature name: (identifier) @getter.name)) @getter
2406
+ (method_signature (setter_signature name: (identifier) @setter.name)) @setter
2407
+ (constructor_signature name: (identifier) @ctor.name) @ctor
2408
+
2409
+ (import_or_export (library_import (import_specification (configurable_uri (uri (string_literal) @import)))))
2399
2410
  `;
2411
+ var DECLS = [
2412
+ { declCap: "class", nameCap: "class.name", kind: "class" },
2413
+ { declCap: "mixin", nameCap: "mixin.name", kind: "class" },
2414
+ { declCap: "ext", nameCap: "ext.name", kind: "class" },
2415
+ { declCap: "enum", nameCap: "enum.name", kind: "enum" },
2416
+ { declCap: "typedef", nameCap: "typedef.name", kind: "type" },
2417
+ { declCap: "function", nameCap: "function.name", kind: "function" },
2418
+ { declCap: "method", nameCap: "method.name", kind: "method" },
2419
+ { declCap: "getter", nameCap: "getter.name", kind: "method" },
2420
+ { declCap: "setter", nameCap: "setter.name", kind: "method" },
2421
+ { declCap: "ctor", nameCap: "ctor.name", kind: "method" }
2422
+ ];
2423
+ function firstLine2(text, max = 200) {
2424
+ const line = text.split(/\r?\n/, 1)[0] ?? "";
2425
+ return line.length > max ? line.slice(0, max) + "\u2026" : line;
2426
+ }
2427
+ function normalizeDartImport(raw) {
2428
+ const stripped = raw.replace(/^['"]|['"]$/g, "");
2429
+ if (!stripped) return null;
2430
+ if (stripped.startsWith("package:")) return null;
2431
+ if (stripped.startsWith("dart:")) return null;
2432
+ if (stripped.startsWith(".") || stripped.startsWith("/")) return stripped;
2433
+ return `./${stripped}`;
2434
+ }
2400
2435
  async function parseDart(f, source) {
2401
- return runGenericParser(
2402
- {
2403
- grammar: "dart",
2404
- query: QUERY4,
2405
- decls: [
2406
- { declCapture: "class", nameCapture: "class.name", kind: "class" },
2407
- { declCapture: "mixin", nameCapture: "class.name", kind: "class" },
2408
- { declCapture: "ext", nameCapture: "class.name", kind: "class" },
2409
- { declCapture: "function", nameCapture: "function.name", kind: "function" }
2410
- ]
2411
- },
2412
- f,
2413
- source
2414
- );
2436
+ let symbols = [];
2437
+ let imports = [];
2438
+ try {
2439
+ const { parser, language } = await createParser("dart");
2440
+ const tree = parser.parse(source);
2441
+ if (!tree) return { file: f, source, symbols, imports, calls: [] };
2442
+ const query = language.query(QUERY4);
2443
+ const matches = query.matches(tree.rootNode);
2444
+ for (const match of matches) {
2445
+ const byName = /* @__PURE__ */ new Map();
2446
+ for (const cap of match.captures) byName.set(cap.name, cap.node);
2447
+ let matched = null;
2448
+ for (const d of DECLS) {
2449
+ if (byName.has(d.declCap) && byName.has(d.nameCap)) {
2450
+ matched = d;
2451
+ break;
2452
+ }
2453
+ }
2454
+ if (matched) {
2455
+ const declNode = byName.get(matched.declCap);
2456
+ const nameNode = byName.get(matched.nameCap);
2457
+ symbols.push({
2458
+ name: nameNode.text,
2459
+ kind: matched.kind,
2460
+ startLine: declNode.startPosition.row + 1,
2461
+ endLine: declNode.endPosition.row + 1,
2462
+ signature: firstLine2(declNode.text)
2463
+ });
2464
+ continue;
2465
+ }
2466
+ const importNode = byName.get("import");
2467
+ if (importNode) {
2468
+ const norm = normalizeDartImport(importNode.text);
2469
+ if (norm) imports.push(norm);
2470
+ }
2471
+ }
2472
+ const seen = /* @__PURE__ */ new Set();
2473
+ symbols = symbols.filter((s) => {
2474
+ const k = `${s.name}:${s.startLine}`;
2475
+ if (seen.has(k)) return false;
2476
+ seen.add(k);
2477
+ return true;
2478
+ });
2479
+ imports = Array.from(new Set(imports));
2480
+ } catch {
2481
+ }
2482
+ return { file: f, source, symbols, imports, calls: [] };
2415
2483
  }
2416
2484
 
2417
2485
  // src/scanner/parsers/go.ts
@@ -2522,7 +2590,7 @@ var QUERY9 = `
2522
2590
  (import_from_statement module_name: (dotted_name) @import.from)
2523
2591
  (import_from_statement module_name: (relative_import) @import.from)
2524
2592
  `;
2525
- function firstLine2(text, max = 200) {
2593
+ function firstLine3(text, max = 200) {
2526
2594
  const line = text.split(/\r?\n/, 1)[0] ?? "";
2527
2595
  return line.length > max ? line.slice(0, max) + "\u2026" : line;
2528
2596
  }
@@ -2548,7 +2616,7 @@ async function parsePython(f, source) {
2548
2616
  kind: isMethod ? "method" : "function",
2549
2617
  startLine: funcDecl.startPosition.row + 1,
2550
2618
  endLine: funcDecl.endPosition.row + 1,
2551
- signature: firstLine2(funcDecl.text)
2619
+ signature: firstLine3(funcDecl.text)
2552
2620
  });
2553
2621
  continue;
2554
2622
  }
@@ -2560,7 +2628,7 @@ async function parsePython(f, source) {
2560
2628
  kind: "class",
2561
2629
  startLine: classDecl.startPosition.row + 1,
2562
2630
  endLine: classDecl.endPosition.row + 1,
2563
- signature: firstLine2(classDecl.text)
2631
+ signature: firstLine3(classDecl.text)
2564
2632
  });
2565
2633
  continue;
2566
2634
  }
@@ -2660,7 +2728,7 @@ function queryFor(grammar) {
2660
2728
  function unquote(s) {
2661
2729
  return s.replace(/^["'`]|["'`]$/g, "");
2662
2730
  }
2663
- function firstLine3(text, max = 200) {
2731
+ function firstLine4(text, max = 200) {
2664
2732
  const line = text.split(/\r?\n/, 1)[0] ?? "";
2665
2733
  return line.length > max ? line.slice(0, max) + "\u2026" : line;
2666
2734
  }
@@ -2692,7 +2760,7 @@ async function parseTypeScript(f, source) {
2692
2760
  kind: shape.kind,
2693
2761
  startLine: shape.decl.startPosition.row + 1,
2694
2762
  endLine: shape.decl.endPosition.row + 1,
2695
- signature: firstLine3(shape.decl.text)
2763
+ signature: firstLine4(shape.decl.text)
2696
2764
  });
2697
2765
  continue;
2698
2766
  }
@@ -2837,7 +2905,7 @@ async function loadGrammar(name) {
2837
2905
  const cached = languageCache.get(name);
2838
2906
  if (cached) return cached;
2839
2907
  const wasmPath = require2.resolve(GRAMMAR_FILES[name]);
2840
- const lang = await Parser.Language.load(wasmPath);
2908
+ const lang = await Language.load(wasmPath);
2841
2909
  languageCache.set(name, lang);
2842
2910
  return lang;
2843
2911
  }
@@ -4712,9 +4780,8 @@ import { join as join10 } from "path";
4712
4780
  import { createInterface } from "readline/promises";
4713
4781
  import spawn from "cross-spawn";
4714
4782
  var PKG_NAME = "@jefuriiij/synthra";
4715
- var CACHE_DIR = join10(homedir3(), ".synthra");
4716
- var CACHE_PATH = join10(CACHE_DIR, "version-check.json");
4717
- var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
4783
+ var SYNTHRA_DIR = join10(homedir3(), ".synthra");
4784
+ var LAST_SEEN_PATH = join10(SYNTHRA_DIR, "last-seen-version.json");
4718
4785
  var REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PKG_NAME)}/latest`;
4719
4786
  var FETCH_TIMEOUT_MS = 2e3;
4720
4787
  var currentVersionCache = null;
@@ -4729,23 +4796,6 @@ async function getCurrentVersion() {
4729
4796
  return "0.0.0";
4730
4797
  }
4731
4798
  }
4732
- async function readCache() {
4733
- try {
4734
- const raw = await readFile14(CACHE_PATH, "utf8");
4735
- const parsed = JSON.parse(raw);
4736
- if (!parsed.checked_at || !parsed.latest_version) return null;
4737
- return parsed;
4738
- } catch {
4739
- return null;
4740
- }
4741
- }
4742
- async function writeCache(cache) {
4743
- try {
4744
- await mkdir10(CACHE_DIR, { recursive: true });
4745
- await writeFile9(CACHE_PATH, JSON.stringify(cache, null, 2), "utf8");
4746
- } catch {
4747
- }
4748
- }
4749
4799
  function isNewer(candidate, baseline) {
4750
4800
  const a = candidate.split(/[.-]/).map((p) => Number(p));
4751
4801
  const b = baseline.split(/[.-]/).map((p) => Number(p));
@@ -4776,23 +4826,88 @@ async function checkForUpdate() {
4776
4826
  if (process.env.SYN_NO_UPDATE_CHECK === "1") {
4777
4827
  return { current, latest: null, hasUpdate: false };
4778
4828
  }
4779
- const cache = await readCache();
4780
- const now = Date.now();
4781
- const cacheAge = cache ? now - Date.parse(cache.checked_at) : Infinity;
4782
- let latest = null;
4783
- if (cache && cacheAge < CACHE_TTL_MS) {
4784
- latest = cache.latest_version;
4785
- } else {
4786
- latest = await fetchLatestFromRegistry();
4787
- if (latest) {
4788
- await writeCache({ checked_at: (/* @__PURE__ */ new Date()).toISOString(), latest_version: latest });
4789
- } else if (cache) {
4790
- latest = cache.latest_version;
4791
- }
4792
- }
4829
+ const latest = await fetchLatestFromRegistry();
4793
4830
  const hasUpdate = latest ? isNewer(latest, current) : false;
4794
4831
  return { current, latest, hasUpdate };
4795
4832
  }
4833
+ async function readLastSeen() {
4834
+ try {
4835
+ const raw = await readFile14(LAST_SEEN_PATH, "utf8");
4836
+ const parsed = JSON.parse(raw);
4837
+ return parsed.version ?? null;
4838
+ } catch {
4839
+ return null;
4840
+ }
4841
+ }
4842
+ async function writeLastSeen(version) {
4843
+ try {
4844
+ await mkdir10(SYNTHRA_DIR, { recursive: true });
4845
+ const data = { version, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
4846
+ await writeFile9(LAST_SEEN_PATH, JSON.stringify(data, null, 2), "utf8");
4847
+ } catch {
4848
+ }
4849
+ }
4850
+ function npmGlobalRoot() {
4851
+ return new Promise((resolve5) => {
4852
+ const chunks = [];
4853
+ const proc = spawn("npm", ["root", "-g"], { stdio: ["ignore", "pipe", "ignore"] });
4854
+ proc.stdout?.on("data", (c) => chunks.push(c));
4855
+ proc.on("error", () => resolve5(null));
4856
+ proc.on("exit", (code) => {
4857
+ if (code !== 0) return resolve5(null);
4858
+ const out = Buffer.concat(chunks).toString("utf8").trim();
4859
+ resolve5(out || null);
4860
+ });
4861
+ });
4862
+ }
4863
+ function extractChangelogSection(text, version) {
4864
+ const escapedVersion = version.replace(/\./g, "\\.");
4865
+ const headingRe = new RegExp(`^##\\s+\\[?v?${escapedVersion}\\]?.*$`, "m");
4866
+ const m = headingRe.exec(text);
4867
+ if (!m) return null;
4868
+ const startBody = m.index + m[0].length;
4869
+ const rest = text.slice(startBody);
4870
+ const nextHeadingIdx = rest.search(/^##\s+/m);
4871
+ const body = nextHeadingIdx < 0 ? rest : rest.slice(0, nextHeadingIdx);
4872
+ return body.replace(/^---\s*$/gm, "").trim() || null;
4873
+ }
4874
+ async function readInstalledChangelog() {
4875
+ const root = await npmGlobalRoot();
4876
+ if (!root) return null;
4877
+ try {
4878
+ return await readFile14(join10(root, "@jefuriiij", "synthra", "CHANGELOG.md"), "utf8");
4879
+ } catch {
4880
+ return null;
4881
+ }
4882
+ }
4883
+ async function printChangelogForVersion(version) {
4884
+ const md = await readInstalledChangelog();
4885
+ if (!md) return;
4886
+ const section = extractChangelogSection(md, version);
4887
+ if (!section) return;
4888
+ log.info("");
4889
+ log.info(`What's new in ${version}:`);
4890
+ log.info("");
4891
+ for (const line of section.split(/\r?\n/)) {
4892
+ log.info(` ${line}`);
4893
+ }
4894
+ log.info("");
4895
+ }
4896
+ async function runStartupChangelogCheck() {
4897
+ try {
4898
+ const current = await getCurrentVersion();
4899
+ const lastSeen = await readLastSeen();
4900
+ if (!lastSeen) {
4901
+ await writeLastSeen(current);
4902
+ return;
4903
+ }
4904
+ if (isNewer(current, lastSeen)) {
4905
+ await printChangelogForVersion(current);
4906
+ await writeLastSeen(current);
4907
+ }
4908
+ } catch {
4909
+ }
4910
+ }
4796
4911
  async function promptYesNo(question) {
4797
4912
  if (!process.stdin.isTTY) return false;
4798
4913
  const rl = createInterface({ input: process.stdin, output: process.stdout });
@@ -4834,7 +4949,10 @@ async function promptForUpdateOrLog() {
4834
4949
  log.warn("npm install failed \u2014 continuing with current version.");
4835
4950
  return;
4836
4951
  }
4837
- log.info(`\u2713 Updated to ${r.latest}. Please re-run: syn .`);
4952
+ log.info(`\u2713 Updated to ${r.latest}.`);
4953
+ await printChangelogForVersion(r.latest);
4954
+ await writeLastSeen(r.latest);
4955
+ log.info(`Please re-run: syn .`);
4838
4956
  process.exit(0);
4839
4957
  } catch {
4840
4958
  }
@@ -4955,6 +5073,7 @@ async function defaultFlow(rawPath, opts) {
4955
5073
  const projectRoot = resolve4(rawPath);
4956
5074
  const paths = resolvePaths(projectRoot);
4957
5075
  const cfg = loadConfig();
5076
+ await runStartupChangelogCheck();
4958
5077
  await promptForUpdateOrLog();
4959
5078
  await recordProject(projectRoot);
4960
5079
  const scan = await scanCommand(rawPath);