@hanna84/mcp-writing 2.5.0 → 2.6.0
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 +20 -0
- package/README.md +4 -3
- package/importer.js +10 -0
- package/index.js +43 -7
- package/package.json +1 -1
- package/prose-styleguide.js +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,11 +4,31 @@ 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.6.0](https://github.com/hannasdev/mcp-writing.git
|
|
8
|
+
/compare/v2.5.1...v2.6.0)
|
|
9
|
+
|
|
10
|
+
- feat(styleguide): improve AI navigation with next_step hints and identifier validation [`#89`](https://github.com/hannasdev/mcp-writing.git
|
|
11
|
+
/pull/89)
|
|
12
|
+
|
|
13
|
+
#### [v2.5.1](https://github.com/hannasdev/mcp-writing.git
|
|
14
|
+
/compare/v2.5.0...v2.5.1)
|
|
15
|
+
|
|
16
|
+
> 26 April 2026
|
|
17
|
+
|
|
18
|
+
- docs(prd): update README phase status and add refactoring PRD [`#88`](https://github.com/hannasdev/mcp-writing.git
|
|
19
|
+
/pull/88)
|
|
20
|
+
- Release 2.5.1 [`4c78109`](https://github.com/hannasdev/mcp-writing.git
|
|
21
|
+
/commit/4c781092152e3ae52715bfe05dbfda143f9c37c7)
|
|
22
|
+
|
|
7
23
|
#### [v2.5.0](https://github.com/hannasdev/mcp-writing.git
|
|
8
24
|
/compare/v2.4.0...v2.5.0)
|
|
9
25
|
|
|
26
|
+
> 26 April 2026
|
|
27
|
+
|
|
10
28
|
- feat(styleguide): add prose styleguide bootstrap suggestions [`#87`](https://github.com/hannasdev/mcp-writing.git
|
|
11
29
|
/pull/87)
|
|
30
|
+
- Release 2.5.0 [`3e93d31`](https://github.com/hannasdev/mcp-writing.git
|
|
31
|
+
/commit/3e93d319212a9e8182bc4a272a8c97d806d605f8)
|
|
12
32
|
|
|
13
33
|
#### [v2.4.0](https://github.com/hannasdev/mcp-writing.git
|
|
14
34
|
/compare/v2.3.0...v2.4.0)
|
package/README.md
CHANGED
|
@@ -10,9 +10,10 @@ Designed to work with [OpenClaw](https://github.com/openclaw/openclaw) but compa
|
|
|
10
10
|
|
|
11
11
|
Instead of feeding an entire manuscript to an AI and hoping it fits in the context window, `mcp-writing` builds a structured index from your scene files. The AI queries that index first — finding relevant characters, beats, and loglines — then loads only the specific prose it needs.
|
|
12
12
|
|
|
13
|
-
**
|
|
14
|
-
**Phase
|
|
15
|
-
**Phase
|
|
13
|
+
**Current status:**
|
|
14
|
+
- **Phase 1-3 completed:** Metadata-first analysis, sidecar-backed metadata maintenance, and AI-assisted prose editing with confirmation + git history.
|
|
15
|
+
- **Phase 4 delivered in part:** Review bundles and Scrivener Direct extraction are complete; embedding search and reference-doc querying are intentionally deferred.
|
|
16
|
+
- **Phase 5 in progress:** OpenClaw runtime integration is active.
|
|
16
17
|
|
|
17
18
|
## Who it is for
|
|
18
19
|
|
package/importer.js
CHANGED
|
@@ -32,6 +32,16 @@ export function validateProjectId(projectId) {
|
|
|
32
32
|
return { ok: true };
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
export function validateUniverseId(universeId) {
|
|
36
|
+
if (typeof universeId !== "string" || universeId.trim().length === 0) {
|
|
37
|
+
return { ok: false, reason: "universe_id must be a non-empty string." };
|
|
38
|
+
}
|
|
39
|
+
if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(universeId)) {
|
|
40
|
+
return { ok: false, reason: "universe_id may contain only lowercase letters, numbers, and hyphens, and must not start or end with a hyphen." };
|
|
41
|
+
}
|
|
42
|
+
return { ok: true };
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
// Parse "NNN Title [binder_id].txt" -> { seq, rawTitle, binderId, ext } or null
|
|
36
46
|
function parseFilename(filename) {
|
|
37
47
|
const m = filename.match(/^(\d+)\s+(.+?)\s*\[(\d+)\]\.(txt|md)$/);
|
package/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import { openDb } from "./db.js";
|
|
|
15
15
|
import { syncAll, isSyncDirWritable, getSyncOwnershipDiagnostics, getFileWriteDiagnostics, writeMeta, readMeta, indexSceneFile, normalizeSceneMetaForPath, sidecarPath } from "./sync.js";
|
|
16
16
|
import { isGitAvailable, isGitRepository, initGitRepository, createSnapshot, listSnapshots, getSceneProseAtCommit, getHeadCommitHash } from "./git.js";
|
|
17
17
|
import { renderCharacterArcTemplate, renderCharacterSheetTemplate, renderPlaceSheetTemplate, slugifyEntityName } from "./world-entity-templates.js";
|
|
18
|
-
import { importScrivenerSync, validateProjectId } from "./importer.js";
|
|
18
|
+
import { importScrivenerSync, validateProjectId, validateUniverseId } from "./importer.js";
|
|
19
19
|
import { ASYNC_PROGRESS_PREFIX } from "./async-progress.js";
|
|
20
20
|
import {
|
|
21
21
|
STYLEGUIDE_CONFIG_BASENAME,
|
|
@@ -845,6 +845,10 @@ async function gracefulShutdown(signal) {
|
|
|
845
845
|
process.exit(0);
|
|
846
846
|
}
|
|
847
847
|
|
|
848
|
+
function maxScenesNextStep(matchedCount) {
|
|
849
|
+
return `Re-run with max_scenes set to at least ${matchedCount}.`;
|
|
850
|
+
}
|
|
851
|
+
|
|
848
852
|
// ---------------------------------------------------------------------------
|
|
849
853
|
// MCP server factory
|
|
850
854
|
// ---------------------------------------------------------------------------
|
|
@@ -1226,6 +1230,7 @@ function createMcpServer() {
|
|
|
1226
1230
|
matched_scenes: targetScenes.length,
|
|
1227
1231
|
max_scenes,
|
|
1228
1232
|
project_id,
|
|
1233
|
+
next_step: maxScenesNextStep(targetScenes.length),
|
|
1229
1234
|
}
|
|
1230
1235
|
);
|
|
1231
1236
|
}
|
|
@@ -1485,6 +1490,7 @@ function createMcpServer() {
|
|
|
1485
1490
|
config: draft.config,
|
|
1486
1491
|
inferred_defaults: draft.inferred_defaults,
|
|
1487
1492
|
warnings: draft.warnings,
|
|
1493
|
+
next_step: "Config created. Call update_prose_styleguide_config to apply field updates.",
|
|
1488
1494
|
});
|
|
1489
1495
|
}
|
|
1490
1496
|
);
|
|
@@ -1520,7 +1526,7 @@ function createMcpServer() {
|
|
|
1520
1526
|
ok: true,
|
|
1521
1527
|
styleguide: resolved,
|
|
1522
1528
|
next_step: resolved.setup_required
|
|
1523
|
-
? "No prose-styleguide.config.yaml was found.
|
|
1529
|
+
? "No prose-styleguide.config.yaml was found. Call setup_prose_styleguide_config (with language e.g. 'en') to create one at sync root or project root."
|
|
1524
1530
|
: "Config resolved successfully.",
|
|
1525
1531
|
});
|
|
1526
1532
|
}
|
|
@@ -1632,7 +1638,12 @@ function createMcpServer() {
|
|
|
1632
1638
|
return errorResponse(
|
|
1633
1639
|
"VALIDATION_ERROR",
|
|
1634
1640
|
`Matched ${targetScenes.length} scenes, which exceeds max_scenes=${max_scenes}.`,
|
|
1635
|
-
{
|
|
1641
|
+
{
|
|
1642
|
+
matched_scenes: targetScenes.length,
|
|
1643
|
+
max_scenes,
|
|
1644
|
+
project_id,
|
|
1645
|
+
next_step: maxScenesNextStep(targetScenes.length),
|
|
1646
|
+
}
|
|
1636
1647
|
);
|
|
1637
1648
|
}
|
|
1638
1649
|
|
|
@@ -1669,7 +1680,7 @@ function createMcpServer() {
|
|
|
1669
1680
|
checked_scenes: sceneSignals.length,
|
|
1670
1681
|
unreadable_scenes: unreadableScenes,
|
|
1671
1682
|
suggested_config: suggestedConfig,
|
|
1672
|
-
next_step:
|
|
1683
|
+
next_step: `To apply: (1) If no project-scoped config exists yet, call setup_prose_styleguide_config first with scope=project_root, project_id=${project_id}, and language (e.g. 'en'). (2) Then call update_prose_styleguide_config with the fields from suggested_config you want to apply.`,
|
|
1673
1684
|
scene_signals: include_scene_signals ? sceneSignals : undefined,
|
|
1674
1685
|
});
|
|
1675
1686
|
}
|
|
@@ -1886,7 +1897,12 @@ function createMcpServer() {
|
|
|
1886
1897
|
return errorResponse(
|
|
1887
1898
|
"VALIDATION_ERROR",
|
|
1888
1899
|
`Matched ${targetScenes.length} scenes, which exceeds max_scenes=${max_scenes}.`,
|
|
1889
|
-
{
|
|
1900
|
+
{
|
|
1901
|
+
matched_scenes: targetScenes.length,
|
|
1902
|
+
max_scenes,
|
|
1903
|
+
project_id,
|
|
1904
|
+
next_step: maxScenesNextStep(targetScenes.length),
|
|
1905
|
+
}
|
|
1890
1906
|
);
|
|
1891
1907
|
}
|
|
1892
1908
|
|
|
@@ -2511,9 +2527,19 @@ function createMcpServer() {
|
|
|
2511
2527
|
if (!SYNC_DIR_WRITABLE) {
|
|
2512
2528
|
return errorResponse("READ_ONLY", "Cannot create character sheet: sync dir is read-only.");
|
|
2513
2529
|
}
|
|
2514
|
-
|
|
2530
|
+
const hasProjectId = project_id !== undefined;
|
|
2531
|
+
const hasUniverseId = universe_id !== undefined;
|
|
2532
|
+
if ((hasProjectId && hasUniverseId) || (!hasProjectId && !hasUniverseId)) {
|
|
2515
2533
|
return errorResponse("VALIDATION_ERROR", "Provide exactly one of project_id or universe_id.");
|
|
2516
2534
|
}
|
|
2535
|
+
if (hasProjectId) {
|
|
2536
|
+
const check = validateProjectId(project_id);
|
|
2537
|
+
if (!check.ok) return errorResponse("INVALID_PROJECT_ID", check.reason, { project_id });
|
|
2538
|
+
}
|
|
2539
|
+
if (hasUniverseId) {
|
|
2540
|
+
const check = validateUniverseId(universe_id);
|
|
2541
|
+
if (!check.ok) return errorResponse("INVALID_UNIVERSE_ID", check.reason, { universe_id });
|
|
2542
|
+
}
|
|
2517
2543
|
|
|
2518
2544
|
try {
|
|
2519
2545
|
const result = createCanonicalWorldEntity({
|
|
@@ -2575,9 +2601,19 @@ function createMcpServer() {
|
|
|
2575
2601
|
if (!SYNC_DIR_WRITABLE) {
|
|
2576
2602
|
return errorResponse("READ_ONLY", "Cannot create place sheet: sync dir is read-only.");
|
|
2577
2603
|
}
|
|
2578
|
-
|
|
2604
|
+
const hasProjectId = project_id !== undefined;
|
|
2605
|
+
const hasUniverseId = universe_id !== undefined;
|
|
2606
|
+
if ((hasProjectId && hasUniverseId) || (!hasProjectId && !hasUniverseId)) {
|
|
2579
2607
|
return errorResponse("VALIDATION_ERROR", "Provide exactly one of project_id or universe_id.");
|
|
2580
2608
|
}
|
|
2609
|
+
if (hasProjectId) {
|
|
2610
|
+
const check = validateProjectId(project_id);
|
|
2611
|
+
if (!check.ok) return errorResponse("INVALID_PROJECT_ID", check.reason, { project_id });
|
|
2612
|
+
}
|
|
2613
|
+
if (hasUniverseId) {
|
|
2614
|
+
const check = validateUniverseId(universe_id);
|
|
2615
|
+
if (!check.ok) return errorResponse("INVALID_UNIVERSE_ID", check.reason, { universe_id });
|
|
2616
|
+
}
|
|
2581
2617
|
|
|
2582
2618
|
try {
|
|
2583
2619
|
const result = createCanonicalWorldEntity({
|
package/package.json
CHANGED
package/prose-styleguide.js
CHANGED
|
@@ -391,7 +391,12 @@ function prepareStyleguideConfigUpdate({ syncDir, scope, projectId, updates = {}
|
|
|
391
391
|
error: {
|
|
392
392
|
code: "STYLEGUIDE_CONFIG_NOT_FOUND",
|
|
393
393
|
message: "Cannot update styleguide config because no config exists at the requested scope.",
|
|
394
|
-
details: {
|
|
394
|
+
details: {
|
|
395
|
+
file_path: filePath,
|
|
396
|
+
scope,
|
|
397
|
+
project_id: projectId ?? null,
|
|
398
|
+
next_step: `Call setup_prose_styleguide_config first (scope=${scope}${projectId ? `, project_id=${projectId}` : ""}, language=<e.g. 'en'>), then retry update_prose_styleguide_config.`,
|
|
399
|
+
},
|
|
395
400
|
},
|
|
396
401
|
};
|
|
397
402
|
}
|