@danielmarbach/mnemonic-mcp 0.27.1 → 0.27.2

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
@@ -6,6 +6,19 @@ The format is loosely based on Keep a Changelog and uses semver-style version he
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.27.2] - 2026-05-01
10
+
11
+ ### Fixed
12
+
13
+ - `remember` and `update(content)` now return structured error responses with actionable retry guidance when markdown lint fails, instead of propagating raw errors that LLMs sometimes ignore.
14
+ - `remember` and `update` `content` parameter descriptions include proactive lint guidance (auto-fixable vs. unfixable issues, common MD040 fenced-code-language gotcha).
15
+
16
+ ### Changed
17
+
18
+ - `semanticPatch` schema example and guidance now use `insertAfter` instead of the rejected `appendChild`/`replaceChildren` on `heading` selectors.
19
+ - `semanticPatch` workflow hint corrected: recommends `insertAfter` for adding content under headings instead of `appendChild`, which the code rejects.
20
+ - `semanticPatch` parameter now auto-parses string-wrapped JSON arrays, recovering gracefully when LLMs serialize the array as a string instead of a proper JSON array.
21
+
9
22
  ## [0.27.1] - 2026-04-28
10
23
 
11
24
  ### Changed
package/build/index.js CHANGED
@@ -1461,7 +1461,9 @@ server.registerTool("remember", {
1461
1461
  },
1462
1462
  inputSchema: z.object({
1463
1463
  title: z.string().describe("Specific, retrieval-friendly title. Prefer the concrete topic or decision, not a vague label."),
1464
- content: z.string().describe("Markdown note body. Put the key fact, decision, or outcome in the opening lines, then supporting detail. Embeddings weight early content more heavily."),
1464
+ content: z.string().describe("Markdown note body. Put the key fact, decision, or outcome in the opening lines, then supporting detail. Embeddings weight early content more heavily. " +
1465
+ "Content must pass markdown lint. Auto-fixable issues are fixed automatically. Common unfixable issues: fenced code blocks need a language tag (e.g. use ```text not bare ```), and broken links are rejected. " +
1466
+ "If lint fails, fix the specific issues listed in the error and retry the same call."),
1465
1467
  tags: z.array(z.string()).optional().default([]).describe("Optional tags for later filtering. Use a small number of stable, meaningful tags."),
1466
1468
  lifecycle: z
1467
1469
  .enum(NOTE_LIFECYCLES)
@@ -1504,7 +1506,21 @@ server.registerTool("remember", {
1504
1506
  }, async ({ title, content, tags, lifecycle, role, summary, alwaysLoad, cwd, scope, allowProtectedBranch = false }) => {
1505
1507
  await ensureBranchSynced(cwd);
1506
1508
  const project = await resolveProject(cwd);
1507
- const cleanedContent = await cleanMarkdown(content);
1509
+ let cleanedContent;
1510
+ try {
1511
+ cleanedContent = await cleanMarkdown(content);
1512
+ }
1513
+ catch (err) {
1514
+ if (err instanceof MarkdownLintError) {
1515
+ const message = `Markdown lint issues prevented this note from being stored. Fix the specific lint errors listed below in your content and retry the remember call — the note was NOT stored.\n\n${err.message}`;
1516
+ return {
1517
+ content: [{ type: "text", text: message }],
1518
+ structuredContent: { action: "lint_error", tool: "remember", issues: err.issues },
1519
+ isError: true,
1520
+ };
1521
+ }
1522
+ throw err;
1523
+ }
1508
1524
  const policy = project ? await configStore.getProjectPolicy(project.id) : undefined;
1509
1525
  const policyScope = policy?.defaultScope;
1510
1526
  const projectVaultExists = cwd ? Boolean(await vaultManager.getProjectVaultIfExists(cwd)) : true;
@@ -2263,7 +2279,17 @@ server.registerTool("update", {
2263
2279
  inputSchema: z.object({
2264
2280
  id: z.string().describe("Exact memory id. Use an id returned by `recall`, `list`, `recent_memories`, or `where_is`."),
2265
2281
  semanticPatch: z
2266
- .array(z.object({
2282
+ .preprocess((val) => {
2283
+ if (typeof val === "string") {
2284
+ try {
2285
+ return JSON.parse(val);
2286
+ }
2287
+ catch {
2288
+ return val;
2289
+ }
2290
+ }
2291
+ return val;
2292
+ }, z.array(z.object({
2267
2293
  selector: z.object({
2268
2294
  heading: z.string().optional(),
2269
2295
  headingStartsWith: z.string().optional(),
@@ -2282,20 +2308,20 @@ server.registerTool("update", {
2282
2308
  z.object({ op: z.literal("insertBefore"), value: z.string() }),
2283
2309
  z.object({ op: z.literal("remove") }),
2284
2310
  ]),
2285
- }))
2311
+ })))
2286
2312
  .optional()
2287
2313
  .describe("Targeted edits to note sections. Array of {selector, operation} objects. Mutually exclusive with content. " +
2288
2314
  "If this fails, fix the issue in your patch values and retry — do NOT fall back to full content rewrite.\n\n" +
2289
2315
  "selector: exactly one of { heading: \"exact heading text\" } | { headingStartsWith: \"prefix\" } | { lastChild: true } | { nthChild: 0-based-index }\n" +
2290
2316
  "operation: { op: \"appendChild\", value: \"content\" } | { op: \"prependChild\", value: \"content\" } | { op: \"replace\", value: \"new content\" } | { op: \"replaceChildren\", value: \"new children\" } | { op: \"insertAfter\", value: \"content\" } | { op: \"insertBefore\", value: \"content\" } | { op: \"remove\" }\n\n" +
2291
- "Example — append a paragraph under ## Findings, replace ## Recommendation body, remove ## Old Section:\n" +
2317
+ "Example — add a paragraph under ## Findings, replace body under ## Recommendation, remove ## Old Section:\n" +
2292
2318
  "[\n" +
2293
- " { \"selector\": { \"heading\": \"Findings\" }, \"operation\": { \"op\": \"appendChild\", \"value\": \"A new paragraph.\" } },\n" +
2294
- " { \"selector\": { \"heading\": \"Recommendation\" }, \"operation\": { \"op\": \"replaceChildren\", \"value\": \"Updated recommendation.\" } },\n" +
2319
+ " { \"selector\": { \"heading\": \"Findings\" }, \"operation\": { \"op\": \"insertAfter\", \"value\": \"A new paragraph.\" } },\n" +
2320
+ " { \"selector\": { \"heading\": \"Recommendation\" }, \"operation\": { \"op\": \"replace\", \"value\": \"## Recommendation\\n\\nUpdated recommendation.\" } },\n" +
2295
2321
  " { \"selector\": { \"heading\": \"Old Section\" }, \"operation\": { \"op\": \"remove\" } }\n" +
2296
2322
  "]\n\n" +
2297
- "`replaceChildren` on a `heading` selector targets the heading itself, not the body below it. Use `appendChild` or `replace` instead."),
2298
- content: z.string().optional().describe("Full note body replacement. Use only for complete rewrites or when the note is small. Mutually exclusive with semanticPatch."),
2323
+ "IMPORTANT: `appendChild`, `prependChild`, and `replaceChildren` do NOT work with `heading` selectors (headings only contain inline text, not block content). To add or replace content under a heading, use `insertAfter`. To replace a heading entirely, use `replace`."),
2324
+ content: z.string().optional().describe("Full note body replacement. Use only for complete rewrites or when the note is small. Mutually exclusive with semanticPatch. Content must pass markdown lint. Auto-fixable issues are fixed automatically. Common unfixable issues: fenced code blocks need a language tag (e.g. use ```text not bare ```), and broken links are rejected. If lint fails, fix the specific issues and retry — do NOT fall back to semanticPatch for this."),
2299
2325
  title: z.string().optional().describe("Specific, retrieval-friendly title. Prefer the concrete topic or decision, not a vague label."),
2300
2326
  tags: z.array(z.string()).optional().describe("Optional tags for later filtering. Use a small number of stable, meaningful tags."),
2301
2327
  lifecycle: z
@@ -2373,7 +2399,23 @@ server.registerTool("update", {
2373
2399
  return { content: [{ type: "text", text: `Semantic patch failed: ${message}` }], isError: true };
2374
2400
  }
2375
2401
  }
2376
- const cleanedContent = content === undefined ? undefined : await cleanMarkdown(content);
2402
+ let cleanedContent;
2403
+ if (content !== undefined) {
2404
+ try {
2405
+ cleanedContent = await cleanMarkdown(content);
2406
+ }
2407
+ catch (err) {
2408
+ if (err instanceof MarkdownLintError) {
2409
+ const message = `Markdown lint issues prevented the update. Fix the specific lint errors in your content and retry — do NOT fall back to semanticPatch for this.\n\n${err.message}`;
2410
+ return {
2411
+ content: [{ type: "text", text: message }],
2412
+ structuredContent: { action: "lint_error", tool: "update", issues: err.issues },
2413
+ isError: true,
2414
+ };
2415
+ }
2416
+ throw err;
2417
+ }
2418
+ }
2377
2419
  const resolvedTitle = title ?? note.title;
2378
2420
  const resolvedContent = patchedContent ?? cleanedContent ?? note.content;
2379
2421
  const resolvedTags = tags ?? note.tags;
@@ -5365,8 +5407,8 @@ server.registerPrompt("mnemonic-workflow-hint", {
5365
5407
  "- `operation` has an `op` key plus `value` (except `remove` which has no value).\n" +
5366
5408
  "- The parameter must be a JSON array, NOT a string.\n" +
5367
5409
  "- Use `get` first to read exact heading text, then use those headings (without `##` prefix) as selector values.\n" +
5368
- "- Common mistake: writing `{ \"op\": \"appendChild\", \"value\": \"...\" }` at the top level instead of nesting inside `operation`. Correct shape: `{ \"selector\": { \"heading\": \"Findings\" }, \"operation\": { \"op\": \"appendChild\", \"value\": \"text\" } }`\n" +
5369
- "- `replaceChildren` on a `heading` selector targets the heading itself, not the body below it. Use `appendChild` or `replace` instead.",
5410
+ "- Common mistake: writing `{ \"op\": \"appendChild\", \"value\": \"...\" }` at the top level instead of nesting inside `operation`. Correct shape: `{ \"selector\": { \"heading\": \"Findings\" }, \"operation\": { \"op\": \"insertAfter\", \"value\": \"text\" } }`\n" +
5411
+ "- `appendChild`, `prependChild`, and `replaceChildren` do NOT work with `heading` selectors. To add content under a heading, use `insertAfter`. To replace a heading, use `replace`.",
5370
5412
  },
5371
5413
  },
5372
5414
  ],