@hanna84/mcp-writing 1.8.0 → 1.8.1
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 +10 -0
- package/package.json +1 -1
- package/sync.js +53 -0
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
|
+
#### [v1.8.1](https://github.com/hannasdev/mcp-writing.git
|
|
8
|
+
/compare/v1.8.0...v1.8.1)
|
|
9
|
+
|
|
10
|
+
- Fix inferProjectAndUniverse misidentifying two-segment universe paths under projects/ [`#51`](https://github.com/hannasdev/mcp-writing.git
|
|
11
|
+
/pull/51)
|
|
12
|
+
|
|
7
13
|
#### [v1.8.0](https://github.com/hannasdev/mcp-writing.git
|
|
8
14
|
/compare/v1.7.0...v1.8.0)
|
|
9
15
|
|
|
16
|
+
> 19 April 2026
|
|
17
|
+
|
|
10
18
|
- feat(search): index metadata keywords in scenes FTS and preserve legacy index data [`#50`](https://github.com/hannasdev/mcp-writing.git
|
|
11
19
|
/pull/50)
|
|
20
|
+
- Release 1.8.0 [`5e3c4fd`](https://github.com/hannasdev/mcp-writing.git
|
|
21
|
+
/commit/5e3c4fd6fa4caade6540d3228bfa52ee5f42c923)
|
|
12
22
|
|
|
13
23
|
#### [v1.7.0](https://github.com/hannasdev/mcp-writing.git
|
|
14
24
|
/compare/v1.6.3...v1.7.0)
|
package/package.json
CHANGED
package/sync.js
CHANGED
|
@@ -96,17 +96,66 @@ export function normalizeSceneMetaForPath(syncDir, filePath, meta = {}) {
|
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
// Structural directory names that are never project slugs under projects/<id>/.
|
|
100
|
+
const PROJECT_STRUCTURAL_DIRS = new Set(["world", "scenes", "misc", "fragments", "feedback", "draft"]);
|
|
101
|
+
|
|
102
|
+
// Cache universe project root existence checks during sync scans.
|
|
103
|
+
const UNIVERSE_PROJECT_ROOT_CACHE = new Map();
|
|
104
|
+
|
|
105
|
+
// Returns true for known structural path segments directly under a project root
|
|
106
|
+
// (named dirs like "world", "scenes", and part-N / chapter-N path segments).
|
|
107
|
+
function isProjectStructuralDir(name) {
|
|
108
|
+
const normalized = String(name ?? "").toLowerCase();
|
|
109
|
+
return PROJECT_STRUCTURAL_DIRS.has(normalized)
|
|
110
|
+
|| /^part-\d+$/.test(normalized)
|
|
111
|
+
|| /^chapter-\d+$/.test(normalized);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function isBookSlug(name) {
|
|
115
|
+
return /^book-[a-z0-9][a-z0-9-]*$/i.test(String(name ?? ""));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function hasUniverseProjectRoot(syncDir, universeId, projectSlug) {
|
|
119
|
+
const key = `${syncDir}::${universeId}/${projectSlug}`;
|
|
120
|
+
if (UNIVERSE_PROJECT_ROOT_CACHE.has(key)) {
|
|
121
|
+
return UNIVERSE_PROJECT_ROOT_CACHE.get(key);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const exists = fs.existsSync(path.join(syncDir, "universes", universeId, projectSlug));
|
|
125
|
+
UNIVERSE_PROJECT_ROOT_CACHE.set(key, exists);
|
|
126
|
+
return exists;
|
|
127
|
+
}
|
|
128
|
+
|
|
99
129
|
export function inferProjectAndUniverse(syncDir, filePath) {
|
|
100
130
|
const rel = path.relative(syncDir, filePath);
|
|
101
131
|
const parts = rel.split(path.sep);
|
|
102
132
|
|
|
103
133
|
if (parts[0] === "universes" && parts.length >= 3) {
|
|
134
|
+
// Case-sensitive "world" intentionally matches isWorldFile() which also uses lowercase.
|
|
104
135
|
if (parts[2] === "world") {
|
|
105
136
|
return { universe_id: parts[1], project_id: null };
|
|
106
137
|
}
|
|
107
138
|
return { universe_id: parts[1], project_id: `${parts[1]}/${parts[2]}` };
|
|
108
139
|
}
|
|
109
140
|
if (parts[0] === "projects" && parts.length >= 2) {
|
|
141
|
+
// Detect accidental two-segment layout: projects/<universe>/<book>/...
|
|
142
|
+
// This occurs when a universe-scoped project_id (e.g. "universe-1/book-1-the-lamb")
|
|
143
|
+
// is written under projects/ instead of universes/.
|
|
144
|
+
// Detection is deliberately conservative to avoid mis-classifying valid nested
|
|
145
|
+
// project layouts (e.g. projects/my-novel/notes/...). All three conditions must hold:
|
|
146
|
+
// 1. parts[2] matches a book-* slug pattern (book-1, book-one, book-1-the-lamb, …)
|
|
147
|
+
// 2. parts[3] is a known structural directory (scenes, world, part-N, chapter-N, …)
|
|
148
|
+
// 3. A matching universes/<universe>/<book> directory exists on disk
|
|
149
|
+
if (
|
|
150
|
+
parts.length >= 4
|
|
151
|
+
&& parts[2] !== undefined
|
|
152
|
+
&& parts[3] !== undefined
|
|
153
|
+
&& isBookSlug(parts[2])
|
|
154
|
+
&& isProjectStructuralDir(parts[3])
|
|
155
|
+
&& hasUniverseProjectRoot(syncDir, parts[1], parts[2])
|
|
156
|
+
) {
|
|
157
|
+
return { universe_id: parts[1], project_id: `${parts[1]}/${parts[2]}` };
|
|
158
|
+
}
|
|
110
159
|
return { universe_id: null, project_id: parts[1] };
|
|
111
160
|
}
|
|
112
161
|
return { universe_id: null, project_id: parts[0] ?? "default" };
|
|
@@ -539,6 +588,10 @@ export function indexSceneFile(db, syncDir, file, meta, prose) {
|
|
|
539
588
|
}
|
|
540
589
|
|
|
541
590
|
export function syncAll(db, syncDir, { quiet = false, writable = false } = {}) {
|
|
591
|
+
// Reset per-run inference cache so filesystem changes between sync calls
|
|
592
|
+
// (for example after imports or path repairs) are reflected immediately.
|
|
593
|
+
UNIVERSE_PROJECT_ROOT_CACHE.clear();
|
|
594
|
+
|
|
542
595
|
const files = walkFiles(syncDir);
|
|
543
596
|
let indexed = 0;
|
|
544
597
|
let staleMarked = 0;
|