Skip to content

Refactor src/ into layered tools/operations/infra architecture#110

Merged
vasylenko merged 11 commits intomainfrom
refactor/layered-src
Apr 23, 2026
Merged

Refactor src/ into layered tools/operations/infra architecture#110
vasylenko merged 11 commits intomainfrom
refactor/layered-src

Conversation

@vasylenko
Copy link
Copy Markdown
Owner

@vasylenko vasylenko commented Apr 23, 2026

Summary

  • Restructures src/ into a layered architecture: tools/ → operations/ → infra/ with shared root modules (config.ts, types.ts, logging.ts, main.ts)
  • No behavior changes, no new features — pure move + split across 11 commits (one per plan step)
  • Dissolves two real import cycles (utils ↔ notes, utils ↔ bear-urls) by splitting logger into a pure logging.ts and pushing handleNoteTextUpdate into the tools layer
  • Shrinks main.ts from 969 → 55 lines — it now reads as "create server → register tools → connect transport"
  • Every concern has a single home; src/ is understandable top-down
  • Fixes a latent bundle bug in Taskfile.yml: the old cp -r dist/*.js glob would have silently omitted the new dist/tools/, dist/operations/, dist/infra/ subdirectories from the .mcpb, breaking Claude Desktop installs on the first post-refactor release. Now uses cp -R dist/. tmp/bundle/ which preserves the tree.

Target structure

src/
├── config.ts, logging.ts, main.ts, types.ts       ← root (pure, importable by all)
├── tools/
│   ├── note-tools.ts       (9 note-domain tools + registerNoteTools)
│   ├── responses.ts        (createToolResponse, createErrorResponse)
│   └── tag-tools.ts        (3 tag-global tools + registerTagTools)
├── operations/
│   ├── bear-encoding.ts    + bear-encoding.test.ts
│   ├── note-conventions.ts + note-conventions.test.ts
│   ├── notes.ts            + notes.test.ts
│   └── tags.ts
└── infra/
    ├── bear-urls.ts        + bear-urls.test.ts
    └── database.ts

Layer rule: tools → operations → infra → root; root imports nothing inside src/.

Why

The flat src/ had grown a grab-bag utils.ts with two real import cycles because logger lived alongside helpers that called getNoteContent and executeBearXCallbackApi. As more tools and operations accreted, main.ts became a 969-line registration monolith that mixed server wiring, tool definitions, and response formatting. The layered structure gives each concern a single home, eliminates the cycles, and makes the codebase navigable for future contributors (human and AI) without needing to trace through a grab-bag module to understand dependencies.

Splits the logger and logAndThrow out of the grab-bag utils.ts into a
pure, zero-internal-import module at src/logging.ts. This is the first
cycle-breaker in the layered refactor: utils <-> notes and utils <->
bear-urls cycles existed partly because logger lived alongside code
that imported from those modules. A pure logging.ts can be reached
from any layer (tools, operations, infra) without creating cycles.

No behavior change. All importers (main, notes, tags, bear-urls,
database, and utils itself) now pull logger/logAndThrow from
./logging.js instead of ./utils.js.
…ses.ts

Moves createToolResponse and createErrorResponse verbatim out of utils.ts
into src/tools/responses.ts — the first file in the new tools/ layer.
Signatures and behavior unchanged (same Pick<CallToolResult, ...> return
types, same annotations audience). Updates main.ts and utils.ts (which
still holds handleNoteTextUpdate temporarily) to import from the new
location.
…ear-encoding.ts

Moves the 4 Bear-format encoding helpers (decodeTagName, cleanBase64,
convertCoreDataTimestamp, convertDateToCoreDataTimestamp) out of utils.ts
into a dedicated operations/bear-encoding.ts. These share a single
concern — converting between Bear's on-disk/on-URL formats and JS types —
and are consumed by operations (notes, tags) plus the bear-add-file tool
via cleanBase64.

Co-locates the convertCoreDataTimestamp test into the new module's
bear-encoding.test.ts. Updates the notes.ts DECODED_TAG_TITLE sync
comment to reference the helper's new home.

All 37 unit tests still pass (1 moved from utils.test.ts to
bear-encoding.test.ts; utils.test.ts now has 18 of its previous 19
describe assertions remaining).
First file relocated into the infra layer. No logic change — just:
- the file now sits at src/infra/database.ts
- its own imports go up one level (../config.js, ../logging.js)
- the two operations that read from it (notes.ts, tags.ts) now import
  from ./infra/database.js while they still sit at src/ root
  (their own relocation into operations/ happens in steps 6 and 7).
bear-urls.ts joins database.ts in the infra/ layer. Both are the only
modules that speak to the outside world (Bear's SQLite DB and the
x-callback-url URL scheme via 'open'). Their own imports go up one
level (../config.js, ../logging.js); the co-located test imports
remain sibling-relative (./bear-urls.js). Callers (main.ts, utils.ts)
now import from ./infra/bear-urls.js.

Note: the utils <-> bear-urls cycle is still present at this step
because utils.ts still holds handleNoteTextUpdate, which calls
buildBearUrl and executeBearXCallbackApi. That cycle dissolves in
step 9 when handleNoteTextUpdate moves into tools/note-tools.ts and
utils.ts is deleted.
notes.ts joins bear-encoding.ts in the operations/ layer. As part of
the move, three helpers that lived in the grab-bag utils.ts get folded
in where they belong:
- parseDateString — already used only by searchNotes' date filter
- stripLeadingHeader and noteHasHeader — pure markdown content helpers
  used by handleNoteTextUpdate

Their 3 describe blocks move verbatim from utils.test.ts into a new
co-located operations/notes.test.ts, preserving all 17 assertions.
utils.test.ts is now empty of useful content and is deleted.

utils.ts shrinks to ~85 lines — it now contains only handleNoteTextUpdate
(which will move into tools/note-tools.ts in step 9, at which point
utils.ts disappears entirely). Its import of getNoteContent and the
two markdown helpers now resolves to ./operations/notes.js.

Tests: 37 total, same as before. Test files: utils.test.ts is gone
(19 tests → 18 moved to operations/notes.test.ts + 1 moved to
operations/bear-encoding.test.ts in step 3).
tags.ts joins notes.ts and bear-encoding.ts in the operations/ layer.
Its imports now resolve to the correct parent paths (../types,
../logging, ../infra/database) and its sibling bear-encoding.js.
main.ts imports from ./operations/tags.js.
…ions/

Simplest move in the refactor: note-conventions.ts has no cross-file
imports and its test is co-located. Only main.ts's import path needs
updating to ./operations/note-conventions.js.
… delete utils.ts

The architectural crown of the refactor. Creates src/tools/note-tools.ts
exporting registerNoteTools(server), which registers all 9 note-domain
tools verbatim from main.ts:
- bear-open-note, bear-create-note, bear-search-notes
- bear-add-text, bear-replace-text, bear-add-file
- bear-find-untagged-notes, bear-add-tag, bear-archive-note

All schemas, descriptions, annotations, handler bodies, and registration
order preserved. handleNoteTextUpdate moves into this file as a
module-private helper (used only by bear-add-text and bear-replace-text).

main.ts shrinks from ~970 lines to ~220 — it now creates the McpServer,
calls registerNoteTools(server), then still holds the 3 tag tools
inline (move in step 10). Unused imports in main.ts removed.

src/utils.ts is now empty of content and deleted. Both legacy cycles
(utils <-> notes, utils <-> bear-urls) are gone. Verified:

  npx madge --circular --extensions ts src/  →  ✔ No circular dependency found!

37 tests still pass.
Creates src/tools/tag-tools.ts exporting registerTagTools(server),
which registers all 3 tag-global tools verbatim from main.ts:
- bear-list-tags (with its formatTagTree helper)
- bear-rename-tag
- bear-delete-tag

All schemas, descriptions, annotations, and handler bodies preserved.

main.ts is now a thin entry point: create the McpServer, call
registerNoteTools(server) + registerTagTools(server), attach process
error handlers, connect the stdio transport. Dropped from 969 lines
(pre-refactor) to 55 lines. No server.registerTool(...) calls remain
in main.ts.

No circular dependencies (verified with madge). 37 unit tests pass.
…ectories

The old pack step 'cp -r dist/*.js ...' globbed only top-level .js
files, which silently broke the .mcpb bundle once src/ was layered —
dist/tools/*, dist/operations/*, and dist/infra/* would never reach
tmp/bundle/, so main.js's layered imports failed at runtime.

Replaces the single glob with two copies:
  cp -R dist/. tmp/bundle/           # preserves full subdir tree
  cp -r assets/* manifest.json package.json tmp/bundle/   # same as before

The trailing '/.' on dist matters: without it we'd get tmp/bundle/dist/
and manifest.json's entry_point='main.js' would fail to resolve.

Assets continue to be copied via the existing glob pattern so dotfiles
like .DS_Store stay out of the bundle.

Verified: manual simulation of the pack copy produces the expected
tree under tmp/bundle/ with main.js at root + tools/, operations/,
infra/ subdirectories and logging.js/config.js/types.js at root.
Copilot AI review requested due to automatic review settings April 23, 2026 13:23
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
bear-notes-mcp Ready Ready Preview, Comment Apr 23, 2026 1:23pm

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors the codebase from a flat src/ layout into a layered architecture (tools/ → operations/ → infra/ plus root modules) to remove import cycles and make tool registration and dependencies clearer, while keeping tool behavior consistent.

Changes:

  • Split the former utils.ts into focused modules (logging.ts, tools/responses.ts, operations/bear-encoding.ts) and moved note mutation handling into the tools layer.
  • Extracted tool registrations into src/tools/* and simplified src/main.ts to server wiring + tool registration.
  • Updated packaging in Taskfile.yml to bundle the full dist/ tree (including new subdirectories).

Reviewed changes

Copilot reviewed 14 out of 17 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/utils.ts Removed monolithic utilities module; functionality redistributed across layers.
src/logging.ts New centralized logger + logAndThrow, used across operations/infra/tools.
src/main.ts Reduced to server creation + registering note/tag tool modules + transport connect.
src/tools/responses.ts New shared MCP response helpers (createToolResponse, createErrorResponse).
src/tools/note-tools.ts New module registering note-domain MCP tools and shared note-text update handler.
src/tools/tag-tools.ts New module registering tag-domain MCP tools and tag tree formatting.
src/operations/notes.ts Updated imports + now houses date parsing and header utilities used by tools.
src/operations/notes.test.ts Updated imports to match refactor; moved timestamp test elsewhere.
src/operations/tags.ts Updated imports; uses new logging/infra and bear-encoding helpers.
src/operations/bear-encoding.ts New module for tag decoding, base64 cleanup, and Core Data timestamp conversions.
src/operations/bear-encoding.test.ts New unit test coverage for Core Data timestamp conversion.
src/operations/note-conventions.ts New module to embed tags as inline Bear tag syntax on note creation.
src/operations/note-conventions.test.ts New unit tests for note-conventions behavior.
src/infra/bear-urls.ts Updated imports to root logging/config after move.
src/infra/bear-urls.test.ts New unit tests for URL encoding behavior.
src/infra/database.ts Updated imports to root logging/config after move.
Taskfile.yml Fix bundling to copy full dist/ directory structure into the .mcpb bundle.

Comment thread src/tools/note-tools.ts
Comment thread src/tools/note-tools.ts
Comment thread src/tools/tag-tools.ts
Comment thread src/tools/tag-tools.ts
@vasylenko vasylenko merged commit f1bb100 into main Apr 23, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants