@jefuriiij/synthra 0.1.25 → 0.2.1

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
@@ -1,426 +1,481 @@
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.25] — 2026-06-06
11
-
12
- ### Fixed
13
-
14
- - **PreToolUse (Moat) bash hook now parses the gate response with `jq`, not a
15
- greedy `sed` capture (issue #13).** `src/hooks/scripts/pre-tool-use.sh`
16
- extracted the block `reason` via `sed -n 's/.*"reason"…\(.*\)".*/\1/p'`. The
17
- greedy `\(.*\)"` capture over-ran into the trailing JSON fields, and because a
18
- block `reason` legitimately contains double quotes (it quotes the searched
19
- query, e.g. `"login"`), the captured text broke the deny JSON when embedded
20
- raw in the output heredoc so on a real block Claude Code received malformed
21
- `hookSpecificOutput` and the deny was silently dropped. The hook now reads
22
- `.decision` / `.reason` with `jq -r '… // empty'` and re-emits the deny object
23
- with `jq -nc --arg` (correct escaping), behind a `command -v jq` guard that
24
- silently no-ops when `jq` is absent — mirroring the Stop/Prime hooks fixed in
25
- #1. Gate/Moat decision logic is unchanged. This completes the `jq` migration
26
- across all three bash hooks (the last v0.2 item). Verified end-to-end under
27
- bash on Linux: SessionStart primer injection, Grep/Glob Moat blocks with
28
- well-formed escaped deny JSON, and Stop-hook token totals reaching the
29
- dashboard.
30
-
31
- ---
32
-
33
- ## [0.1.24] 2026-06-06
34
-
35
- ### Added
36
-
37
- - **`syn doctor [path]` — setup and environment health check (issue #9).** New
38
- read-only CLI subcommand that runs a one-shot checklist and exits. Checks: Node
39
- version, `jq` availability (bash Stop/Prime hooks silently no-op without it),
40
- `claude` CLI on PATH, graph freshness (symbol count, schema version, scan age),
41
- `.mcp.json` project-scope registration (required for Synthra tools to appear in
42
- the Claude Code IDE), CLAUDE.md policy-block version, and hook installation
43
- status. Warnings surface with the exact `syn .` command needed to resolve them.
44
- The command mutates nothing safe to run at any time.
45
-
46
- - **Graph-tool usage metric on the dashboard (issue #2).** The MCP server now
47
- appends a record to `.synthra-graph/tool_log.jsonl` on every Synthra tool call
48
- (`graph_continue`, `graph_read`, `graph_register_edit`, etc.). `delta.ts`
49
- aggregates per-tool call counts into `ProjectStats.tool_calls` (per-project) and
50
- `global.tool_calls` (cross-project totals). The dashboard shows a new "Graph
51
- tools used" card in the right column with per-tool counts. This is a positive
52
- signal complementing the Moat's blocked-Grep count: it captures Synthra pivots
53
- that happen before a Grep fires, which the block counter misses entirely.
54
-
55
- - **Session-aware routing `graph_continue` seeds retrieval with the session's
56
- touched files (issue #14).** Files the human recently saved (last 15 min) and
57
- files the AI registered via `graph_register_edit` now get a ranking boost in
58
- `graph_continue` results, so the returned context tracks what you're actually
59
- working on. Mirrors the `/pack` route, which already seeded retrieval this way.
60
-
61
- ---
62
-
63
- ## [0.1.23] — 2026-06-06
64
-
65
- ### Added
66
-
67
- - **Dashboard token-log dedupe can now be disabled via `SYN_DASHBOARD_DEDUPE=0`.**
68
- By default, `delta.ts` deduplicates `token_log.jsonl` entries that share the
69
- same project, usage totals, and second-rounded timestamp collapsing the
70
- duplicate writes that a co-installed AI tool's Stop hook may produce. Set
71
- `SYN_DASHBOARD_DEDUPE=0` (also accepts `off` or `false`) to see every raw
72
- entry. Useful when debugging multi-tool coexistence or auditing raw log data.
73
-
74
- - **Graph schema-migration check on load.** A new `SCHEMA_VERSION` constant is
75
- exported from `src/graph/types.ts` and stamped into `info_graph.json` by
76
- `buildGraph`. On server start, `http.ts` compares the stored graph's
77
- `schema_version` to the current constant; if they differ it triggers an
78
- automatic one-time rescan instead of serving an incompatible graph. No
79
- behavior change today all graphs are v1 and schema_version matches but
80
- this is the forward-safety mechanism for future schema bumps so existing
81
- graphs are never silently misread.
82
-
83
- ### Fixed
84
-
85
- - **JS/TS parser now captures member-assigned functions** (`exports.handler = fn`,
86
- `module.exports.route = () => {}`, `this.x = () => {}`). Previously these
87
- CommonJS/member-export patterns were invisible to the query, so modules that
88
- exclusively use this style extracted zero symbols and degraded to whole-file
89
- reads via `graph_read`. A member-assignment capture has been added to both
90
- `JS_QUERY` and `TS_QUERY` in `src/scanner/parsers/typescript.ts`. Note: a
91
- pure-wiring `server.js` whose only structure is anonymous inline-callback
92
- arguments (e.g. `io.use(...)` / `socket.on(event, fn)`) is genuinely
93
- symbol-less that is correct, and the gate's symbol-hit guard already
94
- prevents blocking such files.
95
-
96
- ### Changed
97
-
98
- - **Policy block v4 v5.** Adds a "large file pull the symbol, don't
99
- chunk" nudge to address recurring dogfood friction: on large files Claude
100
- was reading successive line-range chunks instead of fetching the specific
101
- symbol via `graph_read("file::symbol")`. The v5 block now explicitly
102
- instructs: when a file is large, use `graph_read("file/path.ts::SymbolName")`
103
- to pull the symbol directly rather than reading successive line-range chunks.
104
- `POLICY_VERSION` bumped `4 5`; existing v4 blocks auto-upgrade on the
105
- next `syn .` run.
106
-
107
- ---
108
-
109
- ## [0.1.22] — 2026-06-06
110
-
111
- ### Fixed
112
-
113
- - **`graph_read` now resolves shortened file paths (path-suffix fallback).** Previously
114
- `graph_read` performed an exact `path === target` match only. Passing a shortened path
115
- like `appsettings.json` returned "file not found" even when
116
- `connectwarev2/.../appsettings.json` was indexed. A new `resolveFileTarget` helper (now
117
- exported) tries an exact match first; on a miss it looks for a unique path-suffix match
118
- and serves that file; if multiple files share the suffix it reports them as ambiguous with
119
- candidate paths rather than guessing. Symbol lookups use the resolved path. No API or
120
- protocol change. Roadmap item #11.
121
-
122
- - **Gate content-keyword relaxation now intersects file contents, not just file paths.**
123
- The Moat's recent-activity relaxation previously matched query tokens against the paths of
124
- recently-touched files only. A query like `Grep "login"` would not relax on a recent save
125
- of `auth.ts` unless the word "login" appeared in the path. Now the relaxation also checks
126
- the recently-touched file's graph-node keywords (its indexed content), so a recent save
127
- relaxes a Grep whenever the file *contains* the queried term — not just when the path
128
- matches it. Completes roadmap item #3.
129
-
130
- ### Changed
131
-
132
- - **Dashboard Projects card shows a first-run hint in the empty state.** When no projects
133
- have run `syn .` yet, the Projects card now displays "No projects yet — run `syn .` in a
134
- project to start" instead of a blank card. The Recent-turns card already carried this
135
- hint; Projects now matches it. Roadmap item #10.
136
-
137
- - **`bin` path normalization (chore).** Ran `npm pkg fix` to normalize `bin` entries from
138
- `./bin/syn` to `bin/syn`. Silences the cosmetic publish warnings
139
- (`"bin[syn]" script name was cleaned`). `syn` and `synthra` still resolve to the same
140
- entry point. Roadmap item #4.
141
-
142
- ---
143
-
144
- ## [0.1.21] 2026-06-06
145
-
146
- ### Added
147
-
148
- - **HubL (HubSpot CMS) symbol extraction for `.html` and `.hubl` files.**
149
- Previously `.html` files were content-indexed only — keyword search and
150
- whole-file reads, no symbol-level granularity. On HubSpot projects this
151
- meant the graph contributed nothing: zero `graph_continue`/`graph_read`
152
- calls resolved to symbol slices all session. Now `.html` and `.hubl` files
153
- run through a new **regex-based** parser (`parsers/hubl.ts`; there is no
154
- tree-sitter grammar for HubL):
155
- - `{% macro name(args) %}` extracted as a `function` symbol
156
- - `{% block name %}` extracted as a `component` symbol
157
- - `{% include / extends / import / from "path" %}` → import edges (relative
158
- paths resolve to local templates; `.html`/`.hubl` added to the resolver's
159
- extension list)
160
-
161
- Plain HTML with no HubL tags is unaffected — the parser yields zero symbols
162
- and zero imports, identical to before. No API, protocol, or policy-block
163
- change. Roadmap item #12.
164
-
165
- ---
166
-
167
- ## [0.1.20] — 2026-06-06
168
-
169
- ### Fixed
170
-
171
- - **Gate (Moat) no longer blocks Grep/Glob queries the graph cannot answer with a symbol.**
172
- Previously, the PreToolUse gate blocked whenever retrieval confidence was `medium` or `high`,
173
- but confidence is driven by keyword and path hits too not only by symbol matches. This meant
174
- literal/attribute/CSS-selector patterns (`data-tour=`, `class=`, `: 100%`, `.filter-bar`,
175
- `<div>`) and path-only Globs were blocked and redirected to `graph_read`, which has no symbol
176
- slice to return for those queries, so Claude fell back to Grep or a whole-file Read anyway —
177
- a wasted round-trip. Found across multiple dogfood sessions including well-indexed Svelte
178
- repos. Two new guards close the gap:
179
- - **Query-shape pre-filter** Grep patterns that target markup, CSS, attributes, or string
180
- literals are allowed through up front, before the retrieval step runs.
181
- - **Symbol-hit requirement** the gate now only blocks when retrieval matched a symbol whose
182
- name the query mentions exactly. `RetrievalResult` gained a `symbolMatched` flag; the scorer
183
- exposes `exactSym`.
184
-
185
- Net effect: genuine symbol lookups still block (verified: `fetchWith429Retry`,
186
- `MAX_ROWS_PER_TABLE`, `verifyPin`, `SOCKET_AUTH_SECRET`, `seedCredentials`); queries that
187
- could never have been answered by the graph now allow through without the wasted redirect.
188
- No API, protocol, or policy-block changepurely server-side gate behavior.
189
-
190
- - **Gate and rank test coverage added** (`tests/gate.test.ts`, `tests/rank.test.ts`).
191
- Chips at the v0.2 backlog item to fill vitest tests beyond `it.todo` placeholders.
192
-
193
- ---
194
-
195
- ## [0.1.19] 2026-06-01
196
-
197
- ### Changed
198
-
199
- - **Policy block v4: targeted Read-before-Edit for graph-discovered files.**
200
- Claude Code's `Edit` tool requires a file to have been opened with its own
201
- `Read` tool; a `graph_read` slice does not satisfy that gate. Previously,
202
- editing a file known only through `graph_read` would fail with *"File has
203
- not been read yet"* and force a whole-file `Read` eroding token savings on
204
- edit-heavy sessions. The v4 policy now instructs: take the line range already
205
- reported in the `graph_read` header (e.g. `…::handler (L120-168)`), do a
206
- targeted `Read` with matching `offset`/`limit`, then `Edit`. This satisfies
207
- the gate while keeping the read small. Existing v3 blocks auto-upgrade on the
208
- next `syn .` run.
209
-
210
- ---
211
-
212
- ## [0.1.18] 2026-06-01
213
-
214
- ### Fixed
215
-
216
- - **Stop hook on Linux/macOS no longer posts zero tokens to the dashboard.** The bash
217
- `stop.sh` hook extracted `transcript_path` from the Claude Code Stop payload using a
218
- greedy `sed` capture (`\(.*\)"`). Because the real payload has additional fields after
219
- `transcript_path`, the capture grabbed those trailing fields and produced a
220
- non-existent path string. The `-f` file check therefore always failed, totals were
221
- never POSTed to `/log`, and the dashboard stayed stuck at 0 on every turn (GitHub
222
- issue #1). Fixed by parsing with `jq -r '.transcript_path // empty'` and moving the
223
- `command -v jq` guard above the parse so the hook exits cleanly when `jq` is absent.
224
- - **SessionStart/PreCompact primer hook (`prime.sh`) hardened the same way.** The
225
- `/prime` response is `{"primer":"…","port":…}`, so the old greedy capture accidentally
226
- injected trailing `","port":…` junk into the primer string. Because primer text can
227
- contain inner quotes, a negated-class fix (`[^"]*`) would have truncated it at the
228
- first quote `jq -r '.primer // empty'` is the correct parse. Switched `printf '%b'`
229
- to `printf '%s'` since `jq -r` already decodes JSON escapes.
230
- - Both fixes are **bash-only**. The Windows PowerShell hooks (`stop.ps1`, `prime.ps1`)
231
- use `ConvertFrom-Json` and were already correct.
232
-
233
- ---
234
-
235
- ## [0.1.17] 2026-05-29
236
-
237
- ### Added
238
-
239
- - **`syn .` scaffolds an agent-onboarding CLAUDE.md on brand-new projects.**
240
- When a project has no CLAUDE.md, Synthra now writes a lean skeleton —
241
- `Build & test`, `Conventions`, `Key decisions`, `Gotchas` (with TODO
242
- prompts) *above* its managed policy block, instead of a bare policy
243
- block. This is the durable "why/how" layer the graph can't infer; the
244
- graph still owns "what/where." Fill it in, or run `/init` to auto-draft.
245
- The skeleton is written **once** and lives outside the
246
- `<!-- synthra-policy -->` markers, so re-running `syn .` (which
247
- refreshes the policy block) never clobbers what you've written.
248
- Projects that already have a CLAUDE.md are untouched — no skeleton is
249
- injected.
250
-
251
- ---
252
-
253
- ## [0.1.16] — 2026-05-29
254
-
255
- ### Changed
256
-
257
- - **Moat card shows 50 recent gate decisions** (was 12). The inline list
258
- already scrolls within the card, and the `/data` payload already carries
259
- up to 500 gates, so this just renders more of them. The headline block
260
- count was always all-time/uncapped unchanged.
261
-
262
- ---
263
-
264
- ## [0.1.15] — 2026-05-29
265
-
266
- ### Changed
267
-
268
- - **Recent turns are paginated.** The dashboard now carries up to 500 turns
269
- (was 25) and shows them 25 per page with Prev/Next controls — so you can
270
- browse history instead of only seeing the last 25. Configurable via
271
- `SYN_DASHBOARD_RECENT_N` (default 500).
272
- - **Model-usage donut is now all-time, not last-25.** It was tallying models
273
- from the capped recent-turns window, so a run of >25 same-model turns showed
274
- that model at 100% and hid the rest. It now sums the uncapped per-project
275
- model counts, so it always reflects your true all-time split.
276
- - **Dashboard poll slowed 2s 10s.** Lighter on resources and steadier to
277
- read; pagination stays live (the current page re-renders each poll).
278
-
279
- ---
280
-
281
- ## [0.1.14] 2026-05-29
282
-
283
- ### Changed
284
-
285
- - **Dashboard visual refresh.** No API surface change all visual / UX.
286
- - Removed the hero strip and the standalone Legend card. Date + active
287
- project now live as compact chips inside the top nav (active-project
288
- path uses RTL truncation so the folder name stays visible).
289
- - New **Projects bar chart** in the left column — colored bars ranked
290
- by turn count. Stable per-name OKLCH palette (8 colors, hash-keyed)
291
- so a project keeps the same color across sessions. Click any row to
292
- open its full breakdown.
293
- - **Donut legend** gains a turn-count column alongside the percentage.
294
- - **Savings card** elevated: radial green backdrop, money figure 40px,
295
- soft glow makes the "what Synthra saved you" number the visual
296
- anchor of the dashboard.
297
- - **Recent turns column headers** are now hover-explainable.
298
- - Active-project chip tightens + month name hides under 1100px width.
299
-
300
- ---
301
-
302
- ## [0.1.13] 2026-05-29
303
-
304
- ### Fixed
305
-
306
- - **Dashboard footer version is now dynamic.** Was hardcoded to `v0.1.8`
307
- in the HTML and never updated. The dashboard server now injects the
308
- running binary's version (from `package.json`) into the footer on every
309
- `GET /` via a `__SYN_VERSION__` placeholder. Re-run `syn .` after an
310
- update and the dashboard reflects the new version automatically.
311
-
312
- ---
313
-
314
- ## [0.1.12] 2026-05-29
315
-
316
- ### Fixed
317
-
318
- - **`Language.query is deprecated` spam at scan time.** Every parsed file
319
- printed the warning 57 prints on a Flutter codebase, one per parsed
320
- file. Switched all four parsers (TypeScript, JavaScript, Python, Dart,
321
- plus the generic helper) from the deprecated `language.query(QUERY)`
322
- to `new Query(language, QUERY)`. No behavior change, just clean
323
- terminal output.
324
-
325
- ---
326
-
327
- ## [0.1.11] 2026-05-29
328
-
329
- ### Fixed
330
-
331
- - **Dart parser actually runs now.** Was silently broken since v0.1 due to an
332
- ABI mismatch (shipped wasm was ABI v15, pinned `web-tree-sitter` only
333
- supported v13–v14). Every `.dart` file got zero symbols, zero imports —
334
- the exception was swallowed by the parser's try/catch. Bumped
335
- `web-tree-sitter` to `^0.25.10` to fix.
336
- - **Real Dart symbol extraction.** Classes, mixins, extensions, enums,
337
- typedefs, top-level functions, methods, getters, setters, constructors.
338
- - **Dart import normalization.** `package:foo/bar.dart` and `dart:async` are
339
- stripped (cross-project); bare `'sibling.dart'` is rewritten to
340
- `./sibling.dart` so the project resolver can complete them.
341
-
342
- ### Changed
343
-
344
- - **Update check runs on every `syn .`** (no more 24h cache). If you're on
345
- latest, stays silent. If outdated, prompts `[y/N]` as before.
346
- - **Auto-update now shows a changelog.** After `npm install -g …@latest`
347
- succeeds, Synthra prints the new version's section from this file before
348
- telling you to re-run. Catches `npm install` outside of `syn .` too
349
- next startup compares your current version to `~/.synthra/last-seen-version.json`
350
- and prints if it's newer.
351
-
352
- ---
353
-
354
- ## [0.1.10] — 2026-05-29
355
-
356
- ### Changed
357
-
358
- - **CLAUDE.md policy v2 → v3.** Session-end now goes through
359
- `context_remember({kind: "task"|"decision"|"next"})` instead of writing
360
- `.synthra/CONTEXT.md` directly. The Stop hook always re-rendered CONTEXT.md
361
- from `context-store.json` under v2 Claude's direct writes were getting
362
- wiped on session end. Existing v2 blocks auto-upgrade.
363
-
364
- ### Added
365
-
366
- - **Scanner ignores more build caches.** `.dart_tool/`, `.flutter-plugins`,
367
- `.flutter-plugins-dependencies`, `.gradle/`, `target/`, `Pods/`,
368
- `DerivedData/`, `__pycache__/`, `.venv/`, `venv/`, `.tox/`,
369
- `.pytest_cache/`, `.mypy_cache/`, `.ruff_cache/`, `obj/`, `.vs/`.
370
-
371
- ---
372
-
373
- ## [0.1.9] 2026-05-29
374
-
375
- ### Fixed
376
-
377
- - **Crash on prototype-colliding symbol names.** `buildSymbolIndex` built
378
- the lookup on a plain `{}`, so a symbol named `toString` (which every
379
- Dart class overrides), `constructor`, `valueOf`, etc. resolved to the
380
- inherited `Object.prototype` member and crashed on `.push`. Now uses
381
- `Object.create(null)` on both fresh-build and load-from-disk paths.
382
-
383
- ---
384
-
385
- ## [0.1.8] — 2026-05-29
386
-
387
- ### Added
388
-
389
- - **Interactive auto-update.** `syn .` checks npm at startup; if a newer
390
- version is available, prompts `[y/N]`. On `y`, runs
391
- `npm install -g @jefuriiij/synthra@latest` with stdio inherited and
392
- exits with re-run instructions. Non-TTY runs (CI, piped stdin) fall
393
- back to a silent one-line hint. `SYN_NO_UPDATE_CHECK=1` opts out.
394
-
395
- ---
396
-
397
- ## [0.1.7] — 2026-05-29
398
-
399
- ### Fixed
400
-
401
- - **JS parser missed CommonJS imports + JS class names.** Unified TS/JS
402
- query only matched ES `import_statement`, and used `(type_identifier)`
403
- for class names which is TS-grammar-only. Result: every `.js`/`.cjs`/
404
- `.mjs` file silently produced zero imports, and any class in a JS file
405
- was skipped. Split into `TS_QUERY` and `JS_QUERY`; JS query adds a
406
- `require()` capture and uses `(identifier)` for class names.
407
-
408
- ---
409
-
410
- ## [0.1.6] — 2026-05-29
411
-
412
- ### Fixed
413
-
414
- - **MCP registration now uses `--scope project`** so the Claude Code IDE
415
- extension actually sees Synthra. The previous `--scope local` wrote to
416
- a per-project section of `~/.claude.json` that only the `claude` CLI
417
- reads invisible to the IDE.
418
-
419
- ---
420
-
421
- ## [0.1.5] and earlier
422
-
423
- See [GitHub commits](https://github.com/jefuriiij/synthra/commits/main) for
424
- detail. v0.1.5 introduced the v2 policy template with namespace + skip rules;
425
- v0.1.4 fixed a DEP0190 deprecation on Windows; v0.1.3 was the dashboard
426
- redesign (Cool Marine palette, FAQ modal, savings audit row).
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.2.1] — 2026-06-06
11
+
12
+ ### Changed
13
+
14
+ - **Keyword retrieval is now IDF-weighted (BM25's term-rarity component).** A
15
+ query token that's rare across the repo counts for more than a common one, so
16
+ on a multi-term query the files matching the *specific* terms rank above those
17
+ matching generic ones instead of every keyword match counting the same. The
18
+ weighting is normalized to the query's mean IDF, so a typical match scores the
19
+ same as before: overall ranking magnitude and the confidence / Moat gating
20
+ that depends on itis unchanged. Purely an in-repo ranking refinement, no API
21
+ or data-model change. (TF-saturation / length-norm parts of full BM25 don't
22
+ apply to the deduped top-N keyword representation.)
23
+
24
+ ---
25
+
26
+ ## [0.2.0] 2026-06-06
27
+
28
+ ### Added
29
+
30
+ - **Cross-session "second brain" — a resume digest at session start.** Synthra
31
+ now captures a snapshot at session end (open next-steps/decisions, files
32
+ touched, and commits since your last session) and, on the next session, leads
33
+ the SessionStart primer with a budget-bounded **"Since you were last here"**
34
+ digest. A fresh session arrives already oriented instead of re-paying tokens
35
+ to rediscover recent work. The snapshot lives in `.synthra-graph/`
36
+ (machine-local) and falls back to the normal primer when there's nothing to
37
+ show.
38
+ - **Usage learning — retrieval that gets smarter the more you use it.** Files
39
+ you actually open (`graph_read`) or edit (`graph_register_edit`) accrue a
40
+ time-decayed weight (7-day half-life), and retrieval gives genuinely "hot"
41
+ files a small, capped re-rank boost. It's anchored to files that already match
42
+ your query and capped below the existing seed boost, so it sharpens ranking
43
+ without ever overriding relevance. Purely local, per-developer; degrades to
44
+ the exact prior ranking when there's no usage history. Tunable via
45
+ `SYN_LEARN_HALFLIFE_DAYS` and `SYN_LEARN_BOOST_CAP`.
46
+ - **CLAUDE.md policy v6** teaches the assistant to trust the resume digest and
47
+ pull concrete next steps via `context_recall({kind:"next"})` instead of
48
+ re-exploring the codebase.
49
+
50
+ ### Fixed
51
+
52
+ - **`pre-compact.sh` now parses the primer with `jq`, not a greedy `sed`
53
+ capture** completing the `jq` migration across all four bash hooks (matches
54
+ the Stop/Prime/PreToolUse fixes). The multi-line resume digest contains quotes
55
+ and newlines the old `sed` capture would have mangled.
56
+
57
+ ### Internal
58
+
59
+ - **CI (GitHub Actions), Biome (lint + format), and coverage** added. CI runs on
60
+ an ubuntu + windows matrix, so cross-platform hook regressions are caught
61
+ automatically. `.gitattributes` enforces LF line endings on every platform.
62
+
63
+ ---
64
+
65
+ ## [0.1.25] — 2026-06-06
66
+
67
+ ### Fixed
68
+
69
+ - **PreToolUse (Moat) bash hook now parses the gate response with `jq`, not a
70
+ greedy `sed` capture (issue #13).** `src/hooks/scripts/pre-tool-use.sh`
71
+ extracted the block `reason` via `sed -n 's/.*"reason"…\(.*\)".*/\1/p'`. The
72
+ greedy `\(.*\)"` capture over-ran into the trailing JSON fields, and because a
73
+ block `reason` legitimately contains double quotes (it quotes the searched
74
+ query, e.g. `"login"`), the captured text broke the deny JSON when embedded
75
+ raw in the output heredoc so on a real block Claude Code received malformed
76
+ `hookSpecificOutput` and the deny was silently dropped. The hook now reads
77
+ `.decision` / `.reason` with `jq -r '… // empty'` and re-emits the deny object
78
+ with `jq -nc --arg` (correct escaping), behind a `command -v jq` guard that
79
+ silently no-ops when `jq` is absent mirroring the Stop/Prime hooks fixed in
80
+ #1. Gate/Moat decision logic is unchanged. This completes the `jq` migration
81
+ across all three bash hooks (the last v0.2 item). Verified end-to-end under
82
+ bash on Linux: SessionStart primer injection, Grep/Glob Moat blocks with
83
+ well-formed escaped deny JSON, and Stop-hook token totals reaching the
84
+ dashboard.
85
+
86
+ ---
87
+
88
+ ## [0.1.24] 2026-06-06
89
+
90
+ ### Added
91
+
92
+ - **`syn doctor [path]` setup and environment health check (issue #9).** New
93
+ read-only CLI subcommand that runs a one-shot checklist and exits. Checks: Node
94
+ version, `jq` availability (bash Stop/Prime hooks silently no-op without it),
95
+ `claude` CLI on PATH, graph freshness (symbol count, schema version, scan age),
96
+ `.mcp.json` project-scope registration (required for Synthra tools to appear in
97
+ the Claude Code IDE), CLAUDE.md policy-block version, and hook installation
98
+ status. Warnings surface with the exact `syn .` command needed to resolve them.
99
+ The command mutates nothing safe to run at any time.
100
+
101
+ - **Graph-tool usage metric on the dashboard (issue #2).** The MCP server now
102
+ appends a record to `.synthra-graph/tool_log.jsonl` on every Synthra tool call
103
+ (`graph_continue`, `graph_read`, `graph_register_edit`, etc.). `delta.ts`
104
+ aggregates per-tool call counts into `ProjectStats.tool_calls` (per-project) and
105
+ `global.tool_calls` (cross-project totals). The dashboard shows a new "Graph
106
+ tools used" card in the right column with per-tool counts. This is a positive
107
+ signal complementing the Moat's blocked-Grep count: it captures Synthra pivots
108
+ that happen before a Grep fires, which the block counter misses entirely.
109
+
110
+ - **Session-aware routing — `graph_continue` seeds retrieval with the session's
111
+ touched files (issue #14).** Files the human recently saved (last 15 min) and
112
+ files the AI registered via `graph_register_edit` now get a ranking boost in
113
+ `graph_continue` results, so the returned context tracks what you're actually
114
+ working on. Mirrors the `/pack` route, which already seeded retrieval this way.
115
+
116
+ ---
117
+
118
+ ## [0.1.23] 2026-06-06
119
+
120
+ ### Added
121
+
122
+ - **Dashboard token-log dedupe can now be disabled via `SYN_DASHBOARD_DEDUPE=0`.**
123
+ By default, `delta.ts` deduplicates `token_log.jsonl` entries that share the
124
+ same project, usage totals, and second-rounded timestamp collapsing the
125
+ duplicate writes that a co-installed AI tool's Stop hook may produce. Set
126
+ `SYN_DASHBOARD_DEDUPE=0` (also accepts `off` or `false`) to see every raw
127
+ entry. Useful when debugging multi-tool coexistence or auditing raw log data.
128
+
129
+ - **Graph schema-migration check on load.** A new `SCHEMA_VERSION` constant is
130
+ exported from `src/graph/types.ts` and stamped into `info_graph.json` by
131
+ `buildGraph`. On server start, `http.ts` compares the stored graph's
132
+ `schema_version` to the current constant; if they differ it triggers an
133
+ automatic one-time rescan instead of serving an incompatible graph. No
134
+ behavior change today all graphs are v1 and schema_version matches but
135
+ this is the forward-safety mechanism for future schema bumps so existing
136
+ graphs are never silently misread.
137
+
138
+ ### Fixed
139
+
140
+ - **JS/TS parser now captures member-assigned functions** (`exports.handler = fn`,
141
+ `module.exports.route = () => {}`, `this.x = () => {}`). Previously these
142
+ CommonJS/member-export patterns were invisible to the query, so modules that
143
+ exclusively use this style extracted zero symbols and degraded to whole-file
144
+ reads via `graph_read`. A member-assignment capture has been added to both
145
+ `JS_QUERY` and `TS_QUERY` in `src/scanner/parsers/typescript.ts`. Note: a
146
+ pure-wiring `server.js` whose only structure is anonymous inline-callback
147
+ arguments (e.g. `io.use(...)` / `socket.on(event, fn)`) is genuinely
148
+ symbol-less that is correct, and the gate's symbol-hit guard already
149
+ prevents blocking such files.
150
+
151
+ ### Changed
152
+
153
+ - **Policy block v4 → v5.** Adds a "large file pull the symbol, don't
154
+ chunk" nudge to address recurring dogfood friction: on large files Claude
155
+ was reading successive line-range chunks instead of fetching the specific
156
+ symbol via `graph_read("file::symbol")`. The v5 block now explicitly
157
+ instructs: when a file is large, use `graph_read("file/path.ts::SymbolName")`
158
+ to pull the symbol directly rather than reading successive line-range chunks.
159
+ `POLICY_VERSION` bumped `4 → 5`; existing v4 blocks auto-upgrade on the
160
+ next `syn .` run.
161
+
162
+ ---
163
+
164
+ ## [0.1.22] — 2026-06-06
165
+
166
+ ### Fixed
167
+
168
+ - **`graph_read` now resolves shortened file paths (path-suffix fallback).** Previously
169
+ `graph_read` performed an exact `path === target` match only. Passing a shortened path
170
+ like `appsettings.json` returned "file not found" even when
171
+ `connectwarev2/.../appsettings.json` was indexed. A new `resolveFileTarget` helper (now
172
+ exported) tries an exact match first; on a miss it looks for a unique path-suffix match
173
+ and serves that file; if multiple files share the suffix it reports them as ambiguous with
174
+ candidate paths rather than guessing. Symbol lookups use the resolved path. No API or
175
+ protocol change. Roadmap item #11.
176
+
177
+ - **Gate content-keyword relaxation now intersects file contents, not just file paths.**
178
+ The Moat's recent-activity relaxation previously matched query tokens against the paths of
179
+ recently-touched files only. A query like `Grep "login"` would not relax on a recent save
180
+ of `auth.ts` unless the word "login" appeared in the path. Now the relaxation also checks
181
+ the recently-touched file's graph-node keywords (its indexed content), so a recent save
182
+ relaxes a Grep whenever the file *contains* the queried term not just when the path
183
+ matches it. Completes roadmap item #3.
184
+
185
+ ### Changed
186
+
187
+ - **Dashboard Projects card shows a first-run hint in the empty state.** When no projects
188
+ have run `syn .` yet, the Projects card now displays "No projects yet run `syn .` in a
189
+ project to start" instead of a blank card. The Recent-turns card already carried this
190
+ hint; Projects now matches it. Roadmap item #10.
191
+
192
+ - **`bin` path normalization (chore).** Ran `npm pkg fix` to normalize `bin` entries from
193
+ `./bin/syn` to `bin/syn`. Silences the cosmetic publish warnings
194
+ (`"bin[syn]" script name was cleaned`). `syn` and `synthra` still resolve to the same
195
+ entry point. Roadmap item #4.
196
+
197
+ ---
198
+
199
+ ## [0.1.21] 2026-06-06
200
+
201
+ ### Added
202
+
203
+ - **HubL (HubSpot CMS) symbol extraction for `.html` and `.hubl` files.**
204
+ Previously `.html` files were content-indexed only keyword search and
205
+ whole-file reads, no symbol-level granularity. On HubSpot projects this
206
+ meant the graph contributed nothing: zero `graph_continue`/`graph_read`
207
+ calls resolved to symbol slices all session. Now `.html` and `.hubl` files
208
+ run through a new **regex-based** parser (`parsers/hubl.ts`; there is no
209
+ tree-sitter grammar for HubL):
210
+ - `{% macro name(args) %}` → extracted as a `function` symbol
211
+ - `{% block name %}` → extracted as a `component` symbol
212
+ - `{% include / extends / import / from "path" %}` → import edges (relative
213
+ paths resolve to local templates; `.html`/`.hubl` added to the resolver's
214
+ extension list)
215
+
216
+ Plain HTML with no HubL tags is unaffected the parser yields zero symbols
217
+ and zero imports, identical to before. No API, protocol, or policy-block
218
+ change. Roadmap item #12.
219
+
220
+ ---
221
+
222
+ ## [0.1.20] 2026-06-06
223
+
224
+ ### Fixed
225
+
226
+ - **Gate (Moat) no longer blocks Grep/Glob queries the graph cannot answer with a symbol.**
227
+ Previously, the PreToolUse gate blocked whenever retrieval confidence was `medium` or `high`,
228
+ but confidence is driven by keyword and path hits too not only by symbol matches. This meant
229
+ literal/attribute/CSS-selector patterns (`data-tour=`, `class=`, `: 100%`, `.filter-bar`,
230
+ `<div>`) and path-only Globs were blocked and redirected to `graph_read`, which has no symbol
231
+ slice to return for those queries, so Claude fell back to Grep or a whole-file Read anyway
232
+ a wasted round-trip. Found across multiple dogfood sessions including well-indexed Svelte
233
+ repos. Two new guards close the gap:
234
+ - **Query-shape pre-filter** — Grep patterns that target markup, CSS, attributes, or string
235
+ literals are allowed through up front, before the retrieval step runs.
236
+ - **Symbol-hit requirement** — the gate now only blocks when retrieval matched a symbol whose
237
+ name the query mentions exactly. `RetrievalResult` gained a `symbolMatched` flag; the scorer
238
+ exposes `exactSym`.
239
+
240
+ Net effect: genuine symbol lookups still block (verified: `fetchWith429Retry`,
241
+ `MAX_ROWS_PER_TABLE`, `verifyPin`, `SOCKET_AUTH_SECRET`, `seedCredentials`); queries that
242
+ could never have been answered by the graph now allow through without the wasted redirect.
243
+ No API, protocol, or policy-block change purely server-side gate behavior.
244
+
245
+ - **Gate and rank test coverage added** (`tests/gate.test.ts`, `tests/rank.test.ts`).
246
+ Chips at the v0.2 backlog item to fill vitest tests beyond `it.todo` placeholders.
247
+
248
+ ---
249
+
250
+ ## [0.1.19] — 2026-06-01
251
+
252
+ ### Changed
253
+
254
+ - **Policy block v4: targeted Read-before-Edit for graph-discovered files.**
255
+ Claude Code's `Edit` tool requires a file to have been opened with its own
256
+ `Read` tool; a `graph_read` slice does not satisfy that gate. Previously,
257
+ editing a file known only through `graph_read` would fail with *"File has
258
+ not been read yet"* and force a whole-file `Read` eroding token savings on
259
+ edit-heavy sessions. The v4 policy now instructs: take the line range already
260
+ reported in the `graph_read` header (e.g. `…::handler (L120-168)`), do a
261
+ targeted `Read` with matching `offset`/`limit`, then `Edit`. This satisfies
262
+ the gate while keeping the read small. Existing v3 blocks auto-upgrade on the
263
+ next `syn .` run.
264
+
265
+ ---
266
+
267
+ ## [0.1.18] — 2026-06-01
268
+
269
+ ### Fixed
270
+
271
+ - **Stop hook on Linux/macOS no longer posts zero tokens to the dashboard.** The bash
272
+ `stop.sh` hook extracted `transcript_path` from the Claude Code Stop payload using a
273
+ greedy `sed` capture (`\(.*\)"`). Because the real payload has additional fields after
274
+ `transcript_path`, the capture grabbed those trailing fields and produced a
275
+ non-existent path string. The `-f` file check therefore always failed, totals were
276
+ never POSTed to `/log`, and the dashboard stayed stuck at 0 on every turn (GitHub
277
+ issue #1). Fixed by parsing with `jq -r '.transcript_path // empty'` and moving the
278
+ `command -v jq` guard above the parse so the hook exits cleanly when `jq` is absent.
279
+ - **SessionStart/PreCompact primer hook (`prime.sh`) hardened the same way.** The
280
+ `/prime` response is `{"primer":"…","port":…}`, so the old greedy capture accidentally
281
+ injected trailing `","port":…` junk into the primer string. Because primer text can
282
+ contain inner quotes, a negated-class fix (`[^"]*`) would have truncated it at the
283
+ first quote — `jq -r '.primer // empty'` is the correct parse. Switched `printf '%b'`
284
+ to `printf '%s'` since `jq -r` already decodes JSON escapes.
285
+ - Both fixes are **bash-only**. The Windows PowerShell hooks (`stop.ps1`, `prime.ps1`)
286
+ use `ConvertFrom-Json` and were already correct.
287
+
288
+ ---
289
+
290
+ ## [0.1.17] 2026-05-29
291
+
292
+ ### Added
293
+
294
+ - **`syn .` scaffolds an agent-onboarding CLAUDE.md on brand-new projects.**
295
+ When a project has no CLAUDE.md, Synthra now writes a lean skeleton —
296
+ `Build & test`, `Conventions`, `Key decisions`, `Gotchas` (with TODO
297
+ prompts) *above* its managed policy block, instead of a bare policy
298
+ block. This is the durable "why/how" layer the graph can't infer; the
299
+ graph still owns "what/where." Fill it in, or run `/init` to auto-draft.
300
+ The skeleton is written **once** and lives outside the
301
+ `<!-- synthra-policy -->` markers, so re-running `syn .` (which
302
+ refreshes the policy block) never clobbers what you've written.
303
+ Projects that already have a CLAUDE.md are untouched — no skeleton is
304
+ injected.
305
+
306
+ ---
307
+
308
+ ## [0.1.16] 2026-05-29
309
+
310
+ ### Changed
311
+
312
+ - **Moat card shows 50 recent gate decisions** (was 12). The inline list
313
+ already scrolls within the card, and the `/data` payload already carries
314
+ up to 500 gates, so this just renders more of them. The headline block
315
+ count was always all-time/uncapped — unchanged.
316
+
317
+ ---
318
+
319
+ ## [0.1.15]2026-05-29
320
+
321
+ ### Changed
322
+
323
+ - **Recent turns are paginated.** The dashboard now carries up to 500 turns
324
+ (was 25) and shows them 25 per page with Prev/Next controls — so you can
325
+ browse history instead of only seeing the last 25. Configurable via
326
+ `SYN_DASHBOARD_RECENT_N` (default 500).
327
+ - **Model-usage donut is now all-time, not last-25.** It was tallying models
328
+ from the capped recent-turns window, so a run of >25 same-model turns showed
329
+ that model at 100% and hid the rest. It now sums the uncapped per-project
330
+ model counts, so it always reflects your true all-time split.
331
+ - **Dashboard poll slowed 2s → 10s.** Lighter on resources and steadier to
332
+ read; pagination stays live (the current page re-renders each poll).
333
+
334
+ ---
335
+
336
+ ## [0.1.14] 2026-05-29
337
+
338
+ ### Changed
339
+
340
+ - **Dashboard visual refresh.** No API surface change — all visual / UX.
341
+ - Removed the hero strip and the standalone Legend card. Date + active
342
+ project now live as compact chips inside the top nav (active-project
343
+ path uses RTL truncation so the folder name stays visible).
344
+ - New **Projects bar chart** in the left column colored bars ranked
345
+ by turn count. Stable per-name OKLCH palette (8 colors, hash-keyed)
346
+ so a project keeps the same color across sessions. Click any row to
347
+ open its full breakdown.
348
+ - **Donut legend** gains a turn-count column alongside the percentage.
349
+ - **Savings card** elevated: radial green backdrop, money figure 40px,
350
+ soft glow makes the "what Synthra saved you" number the visual
351
+ anchor of the dashboard.
352
+ - **Recent turns column headers** are now hover-explainable.
353
+ - Active-project chip tightens + month name hides under 1100px width.
354
+
355
+ ---
356
+
357
+ ## [0.1.13] — 2026-05-29
358
+
359
+ ### Fixed
360
+
361
+ - **Dashboard footer version is now dynamic.** Was hardcoded to `v0.1.8`
362
+ in the HTML and never updated. The dashboard server now injects the
363
+ running binary's version (from `package.json`) into the footer on every
364
+ `GET /` via a `__SYN_VERSION__` placeholder. Re-run `syn .` after an
365
+ update and the dashboard reflects the new version automatically.
366
+
367
+ ---
368
+
369
+ ## [0.1.12] 2026-05-29
370
+
371
+ ### Fixed
372
+
373
+ - **`Language.query is deprecated` spam at scan time.** Every parsed file
374
+ printed the warning — 57 prints on a Flutter codebase, one per parsed
375
+ file. Switched all four parsers (TypeScript, JavaScript, Python, Dart,
376
+ plus the generic helper) from the deprecated `language.query(QUERY)`
377
+ to `new Query(language, QUERY)`. No behavior change, just clean
378
+ terminal output.
379
+
380
+ ---
381
+
382
+ ## [0.1.11] — 2026-05-29
383
+
384
+ ### Fixed
385
+
386
+ - **Dart parser actually runs now.** Was silently broken since v0.1 due to an
387
+ ABI mismatch (shipped wasm was ABI v15, pinned `web-tree-sitter` only
388
+ supported v13–v14). Every `.dart` file got zero symbols, zero imports —
389
+ the exception was swallowed by the parser's try/catch. Bumped
390
+ `web-tree-sitter` to `^0.25.10` to fix.
391
+ - **Real Dart symbol extraction.** Classes, mixins, extensions, enums,
392
+ typedefs, top-level functions, methods, getters, setters, constructors.
393
+ - **Dart import normalization.** `package:foo/bar.dart` and `dart:async` are
394
+ stripped (cross-project); bare `'sibling.dart'` is rewritten to
395
+ `./sibling.dart` so the project resolver can complete them.
396
+
397
+ ### Changed
398
+
399
+ - **Update check runs on every `syn .`** (no more 24h cache). If you're on
400
+ latest, stays silent. If outdated, prompts `[y/N]` as before.
401
+ - **Auto-update now shows a changelog.** After `npm install -g …@latest`
402
+ succeeds, Synthra prints the new version's section from this file before
403
+ telling you to re-run. Catches `npm install` outside of `syn .` too —
404
+ next startup compares your current version to `~/.synthra/last-seen-version.json`
405
+ and prints if it's newer.
406
+
407
+ ---
408
+
409
+ ## [0.1.10] — 2026-05-29
410
+
411
+ ### Changed
412
+
413
+ - **CLAUDE.md policy v2 → v3.** Session-end now goes through
414
+ `context_remember({kind: "task"|"decision"|"next"})` instead of writing
415
+ `.synthra/CONTEXT.md` directly. The Stop hook always re-rendered CONTEXT.md
416
+ from `context-store.json` under v2 Claude's direct writes were getting
417
+ wiped on session end. Existing v2 blocks auto-upgrade.
418
+
419
+ ### Added
420
+
421
+ - **Scanner ignores more build caches.** `.dart_tool/`, `.flutter-plugins`,
422
+ `.flutter-plugins-dependencies`, `.gradle/`, `target/`, `Pods/`,
423
+ `DerivedData/`, `__pycache__/`, `.venv/`, `venv/`, `.tox/`,
424
+ `.pytest_cache/`, `.mypy_cache/`, `.ruff_cache/`, `obj/`, `.vs/`.
425
+
426
+ ---
427
+
428
+ ## [0.1.9] — 2026-05-29
429
+
430
+ ### Fixed
431
+
432
+ - **Crash on prototype-colliding symbol names.** `buildSymbolIndex` built
433
+ the lookup on a plain `{}`, so a symbol named `toString` (which every
434
+ Dart class overrides), `constructor`, `valueOf`, etc. resolved to the
435
+ inherited `Object.prototype` member and crashed on `.push`. Now uses
436
+ `Object.create(null)` on both fresh-build and load-from-disk paths.
437
+
438
+ ---
439
+
440
+ ## [0.1.8] — 2026-05-29
441
+
442
+ ### Added
443
+
444
+ - **Interactive auto-update.** `syn .` checks npm at startup; if a newer
445
+ version is available, prompts `[y/N]`. On `y`, runs
446
+ `npm install -g @jefuriiij/synthra@latest` with stdio inherited and
447
+ exits with re-run instructions. Non-TTY runs (CI, piped stdin) fall
448
+ back to a silent one-line hint. `SYN_NO_UPDATE_CHECK=1` opts out.
449
+
450
+ ---
451
+
452
+ ## [0.1.7] — 2026-05-29
453
+
454
+ ### Fixed
455
+
456
+ - **JS parser missed CommonJS imports + JS class names.** Unified TS/JS
457
+ query only matched ES `import_statement`, and used `(type_identifier)`
458
+ for class names — which is TS-grammar-only. Result: every `.js`/`.cjs`/
459
+ `.mjs` file silently produced zero imports, and any class in a JS file
460
+ was skipped. Split into `TS_QUERY` and `JS_QUERY`; JS query adds a
461
+ `require()` capture and uses `(identifier)` for class names.
462
+
463
+ ---
464
+
465
+ ## [0.1.6] — 2026-05-29
466
+
467
+ ### Fixed
468
+
469
+ - **MCP registration now uses `--scope project`** so the Claude Code IDE
470
+ extension actually sees Synthra. The previous `--scope local` wrote to
471
+ a per-project section of `~/.claude.json` that only the `claude` CLI
472
+ reads — invisible to the IDE.
473
+
474
+ ---
475
+
476
+ ## [0.1.5] and earlier
477
+
478
+ See [GitHub commits](https://github.com/jefuriiij/synthra/commits/main) for
479
+ detail. v0.1.5 introduced the v2 policy template with namespace + skip rules;
480
+ v0.1.4 fixed a DEP0190 deprecation on Windows; v0.1.3 was the dashboard
481
+ redesign (Cool Marine palette, FAQ modal, savings audit row).