@hanna84/mcp-writing 2.12.3 → 2.12.4

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
@@ -4,11 +4,21 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ #### [v2.12.4](https://github.com/hannasdev/mcp-writing.git
8
+ /compare/v2.12.3...v2.12.4)
9
+
10
+ - fix: detect no-op commit_edit proposals [`#117`](https://github.com/hannasdev/mcp-writing.git
11
+ /pull/117)
12
+
7
13
  #### [v2.12.3](https://github.com/hannasdev/mcp-writing.git
8
14
  /compare/v2.12.2...v2.12.3)
9
15
 
16
+ > 27 April 2026
17
+
10
18
  - docs: refine landing page copy and visuals [`#116`](https://github.com/hannasdev/mcp-writing.git
11
19
  /pull/116)
20
+ - Release 2.12.3 [`97ed1f6`](https://github.com/hannasdev/mcp-writing.git
21
+ /commit/97ed1f69fa2419dc0875792e048886d96e054cba)
12
22
 
13
23
  #### [v2.12.2](https://github.com/hannasdev/mcp-writing.git
14
24
  /compare/v2.12.1...v2.12.2)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanna84/mcp-writing",
3
- "version": "2.12.3",
3
+ "version": "2.12.4",
4
4
  "description": "MCP service for AI-assisted reasoning and editing on long-form fiction projects",
5
5
  "homepage": "https://hannasdev.github.io/mcp-writing/",
6
6
  "type": "module",
package/tools/editing.js CHANGED
@@ -5,6 +5,14 @@ import yaml from "js-yaml";
5
5
  import { createSnapshot, listSnapshots } from "../git.js";
6
6
  import { getFileWriteDiagnostics, readMeta, indexSceneFile } from "../sync.js";
7
7
 
8
+ function renderSceneContent(metadata, revisedProse) {
9
+ const hasFrontmatter = metadata && Object.keys(metadata).length > 0;
10
+ const normalizedProse = revisedProse.replace(/\r?\n$/, "");
11
+ return hasFrontmatter
12
+ ? `---\n${yaml.dump(metadata)}---\n\n${normalizedProse}\n`
13
+ : `${normalizedProse}\n`;
14
+ }
15
+
8
16
  export function registerEditingTools(s, {
9
17
  db,
10
18
  SYNC_DIR,
@@ -36,6 +44,7 @@ export function registerEditingTools(s, {
36
44
  try {
37
45
  const raw = fs.readFileSync(scene.file_path, "utf8");
38
46
  const { data: metadata, content: currentProse } = matter(raw);
47
+ const renderedContent = renderSceneContent(metadata, revised_prose);
39
48
 
40
49
  const currentLines = currentProse.trim().split("\n");
41
50
  const revisedLines = revised_prose.trim().split("\n");
@@ -60,17 +69,28 @@ export function registerEditingTools(s, {
60
69
  scene_file_path: scene.file_path,
61
70
  instruction,
62
71
  revised_prose,
72
+ rendered_content: renderedContent,
63
73
  original_prose: currentProse,
64
74
  metadata,
65
75
  created_at: new Date().toISOString(),
66
76
  });
67
77
 
78
+ const noop = renderedContent === raw;
79
+ const diffPreview = noop
80
+ ? "(no changes)"
81
+ : diffLines.length > 0
82
+ ? diffLines.join("\n")
83
+ : "(file changes are not visible in the prose preview; they may be due to frontmatter or whitespace/newline formatting)";
84
+
68
85
  return jsonResponse({
69
86
  proposal_id: proposalId,
70
87
  scene_id,
71
88
  instruction,
72
- diff_preview: diffLines.join("\n"),
73
- note: "Review the diff above. Call commit_edit with this proposal_id to apply the change.",
89
+ diff_preview: diffPreview,
90
+ noop,
91
+ note: noop
92
+ ? "This proposal matches the current scene file. Calling commit_edit will be a no-op."
93
+ : "Review the diff above. Call commit_edit with this proposal_id to apply the change.",
74
94
  });
75
95
  } catch (err) {
76
96
  if (err.code === "ENOENT") {
@@ -152,10 +172,24 @@ export function registerEditingTools(s, {
152
172
  );
153
173
  }
154
174
 
155
- const hasFrontmatter = proposal.metadata && Object.keys(proposal.metadata).length > 0;
156
- const content = hasFrontmatter
157
- ? `---\n${yaml.dump(proposal.metadata)}---\n\n${proposal.revised_prose}\n`
158
- : `${proposal.revised_prose}\n`;
175
+ const content = proposal.rendered_content ?? renderSceneContent(proposal.metadata, proposal.revised_prose);
176
+ const currentRaw = fs.readFileSync(proposal.scene_file_path, "utf8");
177
+
178
+ if (currentRaw === content) {
179
+ const { meta: canonicalMeta } = readMeta(proposal.scene_file_path, SYNC_DIR, { writable: false });
180
+ const { content: currentProse } = matter(currentRaw);
181
+ indexSceneFile(db, SYNC_DIR, proposal.scene_file_path, canonicalMeta, currentProse);
182
+ pendingProposals.delete(proposal_id);
183
+
184
+ return jsonResponse({
185
+ ok: true,
186
+ scene_id,
187
+ proposal_id,
188
+ snapshot_commit: null,
189
+ noop: true,
190
+ message: `Proposal for scene '${scene_id}' matches the current file. Nothing was written.`,
191
+ });
192
+ }
159
193
 
160
194
  const snapshot = createSnapshot(SYNC_DIR, proposal.scene_file_path, scene_id, proposal.instruction);
161
195
 
@@ -172,7 +206,8 @@ export function registerEditingTools(s, {
172
206
  scene_id,
173
207
  proposal_id,
174
208
  snapshot_commit: snapshot.commit_hash,
175
- message: `Committed edit for scene '${scene_id}'${snapshot.commit_hash ? ` (snapshot: ${snapshot.commit_hash.substring(0, 7)})` : " (no changes to snapshot)"}`,
209
+ noop: false,
210
+ message: `Applied edit to scene '${scene_id}'${snapshot.commit_hash ? ` (snapshot: ${snapshot.commit_hash.substring(0, 7)})` : " (no pre-edit snapshot needed)"}`,
176
211
  });
177
212
  } catch (err) {
178
213
  if (err.code === "ENOENT") {