Skip to content

Yaml frontmatter fix#111

Open
stefanstr wants to merge 10 commits intovasylenko:mainfrom
stefanstr:yaml-frontmatter-fix
Open

Yaml frontmatter fix#111
stefanstr wants to merge 10 commits intovasylenko:mainfrom
stefanstr:yaml-frontmatter-fix

Conversation

@stefanstr
Copy link
Copy Markdown

The original MCP breaks notes using YAML front matter. I added logic ensuring that YAML remains at the beginning of the note for the notes using it.

Stefan Stryjecki and others added 10 commits April 25, 2026 09:10
…ntax helpers

parseFrontmatter detects YAML frontmatter only when --- is line 1 and a
closing --- exists, preventing horizontal rules in note bodies from being
misidentified. formatTagsAsInlineSyntax extracts the tag-formatting logic
from applyNoteConventions so it can be reused by tool handlers.

Also adds 'replace_all' to BearUrlParams.mode for the upcoming bear-add-tag
frontmatter fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tTagsAsInlineSyntax

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When text starts with ---\n...\n---, assemble the final note as
frontmatter → title (H1) → tags → body and pass it as a single text
payload so Bear does not insert the title or tags outside the block.
Notes without frontmatter follow the existing code path unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a note contains YAML frontmatter, rebuild the note body with tags
inserted after the closing --- so a blind prepend cannot clobber the
block. Notes without frontmatter keep the original prepend behavior.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…dd-tag

Tests require Bear to be running. They create throwaway notes with unique
prefixes, verify frontmatter is preserved correctly, then trash the notes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
API validation confirmed mode=replace_all is documented; no code changes
needed. Commit 26fd648 split into two atomic commits (bear-create-note
and bear-add-tag). FINDINGS.md documents the API verification result and
the discrepancy in TASK.md's expected commit count (7 vs 8).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 28, 2026

@stefanstr is attempting to deploy a commit to the vasylenko's projects Team on Vercel.

A member of the Team first needs to authorize it.

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot
18.0% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@vasylenko
Copy link
Copy Markdown
Owner

Hi @stefanstr ! Thank you for contributing! I will do my best to review this promptly.

Meanwhile, could you please explain in detail how this pain surfaces to you? How can I reproduce it from my end?

The original MCP breaks notes using YAML front matter

Also, please clean up the leftovers from the coding agent (unless you wanted that to remain on purpose, e.g., for me to review? LMK if so), such as progress file or summary (concise version of that might go to the PR description, btw).

Thanks!

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

This PR addresses a correctness issue where Bear x-callback-url operations could disrupt notes that start with YAML frontmatter, by detecting frontmatter and ensuring subsequent title/tag insertion preserves the frontmatter block at the top of the note.

Changes:

  • Added frontmatter parsing + tag formatting/insertion helpers in note conventions, with unit tests.
  • Updated bear-create-note to assemble a single text payload when frontmatter is present to prevent Bear from inserting content above the YAML block.
  • Updated bear-add-tag to detect frontmatter and avoid clobbering it (using mode=replace_all when needed), plus added system tests and documentation/changelog updates.

Reviewed changes

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

Show a summary per file
File Description
src/tools/note-tools.ts Adds frontmatter-aware handling in bear-create-note and bear-add-tag to preserve YAML structure.
src/operations/note-conventions.ts Introduces parseFrontmatter, formatTagsAsInlineSyntax, and insertInlineTags helpers.
src/operations/note-conventions.test.ts Adds unit tests covering new helpers and insertion behaviors.
src/infra/bear-urls.ts Extends Bear URL mode typing to include replace_all.
tests/system/frontmatter.test.ts Adds end-to-end/system coverage for frontmatter interactions in create-note and add-tag.
README.md Documents frontmatter behavior and conventions.
CHANGELOG.md Adds unreleased entries describing the fixes.
SUMMARY.md Branch/implementation summary added (reviewed for maintainability impact).
PROGRESS.md Branch progress log added (reviewed for maintainability impact).

Comment thread PROGRESS.md
Comment on lines +1 to +24
# Progress: yaml-frontmatter-fix

## Status: Complete

## What was done

### Task 1: API Validation (FINDINGS.md)
- Fetched official Bear x-callback-url docs
- Confirmed `mode=replace_all` is a documented, supported value for `/add-text`
- Confirmed `/replace-note` does not exist (BRIEF.md was mistaken about this alternative)
- No code changes required

### Task 2: Commit split
- Commit 26fd648 (mixed bear-create-note + bear-add-tag changes) split into two atomic commits
- Note: TASK.md expected 3 commits from 26fd648, but the bear-urls.ts change was already in d86067b; the correct split yielded 2 commits from that mixed change
- Final branch structure: 8 commits on top of main, including this follow-up documentation refresh

### Task 3: Documentation refresh
- SUMMARY.md updated with new commit SHAs, API validation result, resolved blockers
- PROGRESS.md updated (this file)

## Remaining work (for PR author)
- Run system tests with Bear open: `npm run test:system`
- Manual verification: create a frontmatter note via MCP, add a tag, confirm structure in Bear
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

PROGRESS.md is tracking branch-specific execution status and references internal task artifacts (e.g., FINDINGS.md) that aren’t present in the repo. This kind of progress log tends to go stale quickly once merged; consider removing it from the repository root or relocating it to a more appropriate place (e.g., PR description or contributor docs) if you want to preserve the narrative.

Suggested change
# Progress: yaml-frontmatter-fix
## Status: Complete
## What was done
### Task 1: API Validation (FINDINGS.md)
- Fetched official Bear x-callback-url docs
- Confirmed `mode=replace_all` is a documented, supported value for `/add-text`
- Confirmed `/replace-note` does not exist (BRIEF.md was mistaken about this alternative)
- No code changes required
### Task 2: Commit split
- Commit 26fd648 (mixed bear-create-note + bear-add-tag changes) split into two atomic commits
- Note: TASK.md expected 3 commits from 26fd648, but the bear-urls.ts change was already in d86067b; the correct split yielded 2 commits from that mixed change
- Final branch structure: 8 commits on top of main, including this follow-up documentation refresh
### Task 3: Documentation refresh
- SUMMARY.md updated with new commit SHAs, API validation result, resolved blockers
- PROGRESS.md updated (this file)
## Remaining work (for PR author)
- Run system tests with Bear open: `npm run test:system`
- Manual verification: create a frontmatter note via MCP, add a tag, confirm structure in Bear
# Progress
This repository does not keep branch-specific progress logs in versioned documentation.
Short-lived implementation notes, task checklists, commit-splitting details, and manual verification steps should be recorded in the pull request description or other contributor workflow materials instead of this file.
Keep repository-root documentation focused on stable, long-term project information.

Copilot uses AI. Check for mistakes.
Comment thread src/tools/note-tools.ts
Comment on lines +273 to +278
const parsed = text ? parseFrontmatter(text) : null;

let url: string;
let pollTitle: string | undefined;

if (parsed?.frontmatter !== null && parsed !== null) {
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

The frontmatter detection branch uses a confusing/nullish pattern (const parsed = text ? parseFrontmatter(text) : null and if (parsed?.frontmatter !== null && parsed !== null)). Since parseFrontmatter() always returns an object, consider keeping parsed non-null (e.g., always call it when text is provided) and simplify the condition to a straightforward parsed.frontmatter !== null check to reduce cognitive load and avoid subtle truthiness pitfalls later.

Suggested change
const parsed = text ? parseFrontmatter(text) : null;
let url: string;
let pollTitle: string | undefined;
if (parsed?.frontmatter !== null && parsed !== null) {
const parsed = parseFrontmatter(text ?? '');
let url: string;
let pollTitle: string | undefined;
if (parsed.frontmatter !== null) {

Copilot uses AI. Check for mistakes.
Comment thread src/tools/note-tools.ts
Comment on lines +279 to +284
// Frontmatter path: assemble the full note content so Bear doesn't
// insert a title H1 or tags outside the frontmatter block.
const tagLine = tags ? formatTagsAsInlineSyntax(tags) : '';
const bodySegments: string[] = [];
if (title) bodySegments.push(`# ${title}`);
if (parsed.body) bodySegments.push(parsed.body);
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

In the frontmatter path, the tool constructs its own # <title> line and sends only text to Bear (no title URL param). That makes the current inputSchema description for text (“Do not include a title heading — Bear adds it automatically from the title parameter.”) inaccurate for frontmatter notes. Update the tool’s schema/description so callers know that when frontmatter is present the server will manage the H1/title itself (and what to do if the user’s text already contains a heading).

Copilot uses AI. Check for mistakes.
Comment thread SUMMARY.md
Comment on lines +1 to +79
# Branch Summary: yaml-frontmatter-fix

## What Changed

8 commits on top of `main` (26fd648 was split into two atomic commits; see FINDINGS.md for why the expected 3-way split became 2 commits from that mixed change):

| SHA | Description |
|-----|-------------|
| d86067b | feat(note-conventions): add parseFrontmatter and formatTagsAsInlineSyntax helpers; add replace_all to BearUrlParams.mode |
| 6b66bc0 | test(note-conventions): add unit tests for parseFrontmatter and formatTagsAsInlineSyntax |
| 88b6951 | feat(bear-create-note): auto-detect and preserve YAML frontmatter |
| a318830 | feat(bear-add-tag): preserve frontmatter when adding tags |
| 8878bf1 | test(system): add frontmatter integration tests for create-note and add-tag |
| 8bfc620 | docs: add Frontmatter handling section to README and update CHANGELOG |
| 0a21763 | chore: add SUMMARY.md / PROGRESS.md |
| a76da24 | chore: update progress and summary after API validation and commit split |

## Files Modified

- **`src/operations/note-conventions.ts`** — added `parseFrontmatter` and `formatTagsAsInlineSyntax`; refactored `applyNoteConventions` to use the extracted helper
- **`src/infra/bear-urls.ts`** — added `'replace_all'` to `BearUrlParams.mode` union type
- **`src/tools/note-tools.ts`** — Fix 1 (bear-create-note) and Fix 2 (bear-add-tag)
- **`src/operations/note-conventions.test.ts`** — 10 new unit tests for the two new helpers
- **`tests/system/frontmatter.test.ts`** — new integration test suite (4 tests, requires Bear)
- **`README.md`** — new "Frontmatter Handling" section under Configuration
- **`CHANGELOG.md`** — [Unreleased] entries for both fixes

## Fix 1: bear-create-note

When `text` starts with `---\n…\n---`, the handler now:
1. Parses the frontmatter block using `parseFrontmatter`
2. Assembles the note as `frontmatter → # title → #tag line → body` (joined with `\n`)
3. Passes the assembled string as `text` only — no separate `title` or `tags` URL params — so Bear uses the H1 as the title and does not insert anything outside the frontmatter block

Backward compat: if no frontmatter is detected, the original code path runs unchanged.

## Fix 2: bear-add-tag

When the existing note text (read from SQLite) starts with `---\n`, the handler:
1. Parses the frontmatter block
2. Builds a new full-note text: `frontmatter\n<tag line>\n<body>`
3. Writes it back using `add-text?mode=replace_all` (replaces entire note content)

When no frontmatter: original `mode=prepend` + `tags` URL param, unchanged.

## API Validation (Task 1)

`mode=replace_all` is **officially documented** in the Bear x-callback-url docs. The original concern in TASK.md was unfounded — no redesign is needed. See FINDINGS.md for the full verification.

The `/replace-note` endpoint mentioned in BRIEF.md as an alternative does not exist in the Bear API.

## Test Results

```
Unit tests: 47 passed / 0 failed (includes 10 new parseFrontmatter/formatTagsAsInlineSyntax tests)
Build: tsc clean, 0 errors
Integration: tests/system/frontmatter.test.ts — NOT run (requires Bear app running)
```

## Blockers / Open Questions

1. **ZTEXT storage format**: the implementation assumes that for notes created with frontmatter (via Fix 1), Bear stores ZTEXT starting with `---` (i.e., Bear does not prepend a `# Title` line automatically). This needs system test validation.

2. **Note title after replace_all**: the Bear docs don't specify whether `replace_all` updates ZTITLE from the first H1. If it doesn't, the SQLite title remains unchanged (which is fine — the title hasn't changed). Either way, the operation is safe.

## Suggested PR Description

### Summary
- `bear-create-note`: auto-detects YAML frontmatter (`---`…`---` at start of `text`) and assembles the note content so frontmatter, title (H1), and tags appear in the right order without Bear interfering
- `bear-add-tag`: detects frontmatter in the existing note text and inserts new tags immediately after the closing `---` instead of clobbering it with a blind prepend
- `mode=replace_all` is an officially documented Bear API parameter — verified against the Bear x-callback-url documentation
- Backward compatible: notes without frontmatter follow the exact same code paths as before

### Test plan
- [x] All 47 unit tests pass (`npm test`)
- [x] TypeScript build clean (`npm run build`)
- [ ] Run `npm run test:system` with Bear open to validate integration tests in `tests/system/frontmatter.test.ts`
- [ ] Manually verify a note created with frontmatter shows the correct structure in Bear
- [ ] Manually verify `bear-add-tag` on a frontmatter note places tags after the `---` block
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

SUMMARY.md (and PROGRESS.md) contain branch-specific metadata (branch name, commit SHAs, status) that will become stale immediately after merge and add long-term maintenance noise to the repo root. Consider moving this content into the PR description/discussion (or docs/dev if it must be retained as project documentation) and removing these files from the shipped tree.

Suggested change
# Branch Summary: yaml-frontmatter-fix
## What Changed
8 commits on top of `main` (26fd648 was split into two atomic commits; see FINDINGS.md for why the expected 3-way split became 2 commits from that mixed change):
| SHA | Description |
|-----|-------------|
| d86067b | feat(note-conventions): add parseFrontmatter and formatTagsAsInlineSyntax helpers; add replace_all to BearUrlParams.mode |
| 6b66bc0 | test(note-conventions): add unit tests for parseFrontmatter and formatTagsAsInlineSyntax |
| 88b6951 | feat(bear-create-note): auto-detect and preserve YAML frontmatter |
| a318830 | feat(bear-add-tag): preserve frontmatter when adding tags |
| 8878bf1 | test(system): add frontmatter integration tests for create-note and add-tag |
| 8bfc620 | docs: add Frontmatter handling section to README and update CHANGELOG |
| 0a21763 | chore: add SUMMARY.md / PROGRESS.md |
| a76da24 | chore: update progress and summary after API validation and commit split |
## Files Modified
- **`src/operations/note-conventions.ts`** — added `parseFrontmatter` and `formatTagsAsInlineSyntax`; refactored `applyNoteConventions` to use the extracted helper
- **`src/infra/bear-urls.ts`** — added `'replace_all'` to `BearUrlParams.mode` union type
- **`src/tools/note-tools.ts`** — Fix 1 (bear-create-note) and Fix 2 (bear-add-tag)
- **`src/operations/note-conventions.test.ts`** — 10 new unit tests for the two new helpers
- **`tests/system/frontmatter.test.ts`** — new integration test suite (4 tests, requires Bear)
- **`README.md`** — new "Frontmatter Handling" section under Configuration
- **`CHANGELOG.md`**[Unreleased] entries for both fixes
## Fix 1: bear-create-note
When `text` starts with `---\n…\n---`, the handler now:
1. Parses the frontmatter block using `parseFrontmatter`
2. Assembles the note as `frontmatter → # title → #tag line → body` (joined with `\n`)
3. Passes the assembled string as `text` only — no separate `title` or `tags` URL params — so Bear uses the H1 as the title and does not insert anything outside the frontmatter block
Backward compat: if no frontmatter is detected, the original code path runs unchanged.
## Fix 2: bear-add-tag
When the existing note text (read from SQLite) starts with `---\n`, the handler:
1. Parses the frontmatter block
2. Builds a new full-note text: `frontmatter\n<tag line>\n<body>`
3. Writes it back using `add-text?mode=replace_all` (replaces entire note content)
When no frontmatter: original `mode=prepend` + `tags` URL param, unchanged.
## API Validation (Task 1)
`mode=replace_all` is **officially documented** in the Bear x-callback-url docs. The original concern in TASK.md was unfounded — no redesign is needed. See FINDINGS.md for the full verification.
The `/replace-note` endpoint mentioned in BRIEF.md as an alternative does not exist in the Bear API.
## Test Results
```
Unit tests: 47 passed / 0 failed (includes 10 new parseFrontmatter/formatTagsAsInlineSyntax tests)
Build: tsc clean, 0 errors
Integration: tests/system/frontmatter.test.ts — NOT run (requires Bear app running)
```
## Blockers / Open Questions
1. **ZTEXT storage format**: the implementation assumes that for notes created with frontmatter (via Fix 1), Bear stores ZTEXT starting with `---` (i.e., Bear does not prepend a `# Title` line automatically). This needs system test validation.
2. **Note title after replace_all**: the Bear docs don't specify whether `replace_all` updates ZTITLE from the first H1. If it doesn't, the SQLite title remains unchanged (which is fine — the title hasn't changed). Either way, the operation is safe.
## Suggested PR Description
### Summary
- `bear-create-note`: auto-detects YAML frontmatter (`---``---` at start of `text`) and assembles the note content so frontmatter, title (H1), and tags appear in the right order without Bear interfering
- `bear-add-tag`: detects frontmatter in the existing note text and inserts new tags immediately after the closing `---` instead of clobbering it with a blind prepend
- `mode=replace_all` is an officially documented Bear API parameter — verified against the Bear x-callback-url documentation
- Backward compatible: notes without frontmatter follow the exact same code paths as before
### Test plan
- [x] All 47 unit tests pass (`npm test`)
- [x] TypeScript build clean (`npm run build`)
- [ ] Run `npm run test:system` with Bear open to validate integration tests in `tests/system/frontmatter.test.ts`
- [ ] Manually verify a note created with frontmatter shows the correct structure in Bear
- [ ] Manually verify `bear-add-tag` on a frontmatter note places tags after the `---` block
# Summary
This repository does not retain branch-specific summaries, commit SHAs, PR status, or other short-lived review metadata in the shipped tree.
If implementation notes for a change need to be preserved:
- keep PR-specific context in the PR description or discussion
- move stable, long-lived technical documentation into `docs/dev` or another appropriate documentation file
See the relevant source files, tests, `README.md`, and `CHANGELOG.md` for durable project information.

Copilot uses AI. Check for mistakes.
@stefanstr
Copy link
Copy Markdown
Author

Hi @stefanstr ! Thank you for contributing! I will do my best to review this promptly.

Meanwhile, could you please explain in detail how this pain surfaces to you? How can I reproduce it from my end?

The original MCP breaks notes using YAML front matter

Also, please clean up the leftovers from the coding agent (unless you wanted that to remain on purpose, e.g., for me to review? LMK if so), such as progress file or summary (concise version of that might go to the PR description, btw).

Thanks!

Hey. First off, apologies, I didn't mean to push it to main just yet, hence the leftovers. Misclicked. Feel free to reject this one or pull from it whatever is useful.

The painpoint is that Bear's X-Callback-URL actions were written before they added YAML support and they break notes with frontmatter. YAML blocks need to be at the start of the note whereas currently, new tags, titles, etc. get injected before them. My additions check whether a note contains YAML, and if it does, it makes sure it stays note-initial. Frontmatter is useful for keeping info on web excerpts and such.

@vasylenko
Copy link
Copy Markdown
Owner

The painpoint is that Bear's X-Callback-URL actions were written before they added YAML support and they break notes with frontmatter. YAML blocks need to be at the start of the note whereas currently, new tags, titles, etc. get injected before them. My additions check whether a note contains YAML, and if it does, it makes sure it stays note-initial. Frontmatter is useful for keeping info on web excerpts and such.

Interesting... Does it behave the same with and without the note structure convention enforced? There is an ENV variable that enforces the MCP server to put tags after the H1 note title.

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.

3 participants