@hanna84/mcp-writing 2.12.7 → 2.12.9
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/helpers.js +1 -1
- package/package.json +3 -4
- package/prose-styleguide-drift.js +1 -125
- package/prose-styleguide-skill.js +1 -125
- package/prose-styleguide.js +1 -684
- package/review-bundles-planner.js +1 -345
- package/review-bundles-renderer.js +1 -498
- package/review-bundles-writer.js +1 -163
- package/review-bundles.js +1 -14
- package/scripts/generate-tool-docs.mjs +12 -8
- package/scripts/profile-review-bundles.mjs +1 -1
- package/src/index.js +9 -9
- package/src/review-bundles/review-bundles-planner.js +345 -0
- package/src/review-bundles/review-bundles-renderer.js +498 -0
- package/src/review-bundles/review-bundles-writer.js +163 -0
- package/src/review-bundles/review-bundles.js +14 -0
- package/src/styleguide/prose-styleguide-drift.js +125 -0
- package/src/styleguide/prose-styleguide-skill.js +125 -0
- package/src/styleguide/prose-styleguide.js +684 -0
- package/{tools → src/tools}/editing.js +2 -2
- package/{tools → src/tools}/metadata.js +2 -2
- package/{tools → src/tools}/review-bundles.js +3 -3
- package/{tools → src/tools}/styleguide.js +4 -4
- package/{tools → src/tools}/sync.js +2 -2
- package/src/workflows/workflow-catalogue.js +95 -0
- package/workflow-catalogue.js +1 -95
- /package/{tools → src/tools}/search.js +0 -0
package/prose-styleguide.js
CHANGED
|
@@ -1,684 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import yaml from "js-yaml";
|
|
4
|
-
|
|
5
|
-
export const STYLEGUIDE_CONFIG_BASENAME = "prose-styleguide.config.yaml";
|
|
6
|
-
|
|
7
|
-
const ENUMS = {
|
|
8
|
-
language: [
|
|
9
|
-
"english_us",
|
|
10
|
-
"english_uk",
|
|
11
|
-
"english_au",
|
|
12
|
-
"english_ca",
|
|
13
|
-
"swedish",
|
|
14
|
-
"norwegian",
|
|
15
|
-
"danish",
|
|
16
|
-
"finnish",
|
|
17
|
-
"french",
|
|
18
|
-
"italian",
|
|
19
|
-
"russian",
|
|
20
|
-
"portuguese_pt",
|
|
21
|
-
"portuguese_br",
|
|
22
|
-
"german",
|
|
23
|
-
"dutch",
|
|
24
|
-
"polish",
|
|
25
|
-
"czech",
|
|
26
|
-
"hungarian",
|
|
27
|
-
"spanish",
|
|
28
|
-
"irish",
|
|
29
|
-
"japanese",
|
|
30
|
-
"korean",
|
|
31
|
-
"chinese_traditional",
|
|
32
|
-
"chinese_simplified",
|
|
33
|
-
],
|
|
34
|
-
spelling: ["uk", "us", "au", "ca"],
|
|
35
|
-
quotation_style: [
|
|
36
|
-
"double",
|
|
37
|
-
"single",
|
|
38
|
-
"guillemets",
|
|
39
|
-
"low9",
|
|
40
|
-
"dialogue_dash_en",
|
|
41
|
-
"dialogue_dash_em",
|
|
42
|
-
"corner_brackets",
|
|
43
|
-
],
|
|
44
|
-
quotation_style_nested: [
|
|
45
|
-
"double",
|
|
46
|
-
"single",
|
|
47
|
-
"guillemets_single",
|
|
48
|
-
"low9_single",
|
|
49
|
-
"corner_brackets_double",
|
|
50
|
-
],
|
|
51
|
-
em_dash_spacing: ["closed", "spaced"],
|
|
52
|
-
ellipsis_style: ["three_periods", "ellipsis_char", "spaced"],
|
|
53
|
-
abbreviation_periods: ["with", "without"],
|
|
54
|
-
oxford_comma: ["yes", "no"],
|
|
55
|
-
numbers: ["spell_under_10", "spell_under_100", "always_spell", "numerals"],
|
|
56
|
-
date_format: ["mdy", "dmy"],
|
|
57
|
-
time_format: ["12h", "24h"],
|
|
58
|
-
tense: ["present", "past"],
|
|
59
|
-
pov: ["first", "third_limited", "third_omniscient"],
|
|
60
|
-
dialogue_tags: ["minimal", "expressive"],
|
|
61
|
-
sentence_fragments: ["disallow", "intentional"],
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
export const STYLEGUIDE_ENUMS = Object.freeze(
|
|
65
|
-
Object.fromEntries(
|
|
66
|
-
Object.entries(ENUMS).map(([key, values]) => [key, [...values]])
|
|
67
|
-
)
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
const STYLEGUIDE_FIELD_ORDER = [
|
|
71
|
-
"language",
|
|
72
|
-
"spelling",
|
|
73
|
-
"quotation_style",
|
|
74
|
-
"quotation_style_nested",
|
|
75
|
-
"em_dash_spacing",
|
|
76
|
-
"ellipsis_style",
|
|
77
|
-
"abbreviation_periods",
|
|
78
|
-
"oxford_comma",
|
|
79
|
-
"numbers",
|
|
80
|
-
"date_format",
|
|
81
|
-
"time_format",
|
|
82
|
-
"tense",
|
|
83
|
-
"pov",
|
|
84
|
-
"dialogue_tags",
|
|
85
|
-
"sentence_fragments",
|
|
86
|
-
"voice_notes",
|
|
87
|
-
];
|
|
88
|
-
|
|
89
|
-
// Fields that are valid in a config but are not enum-constrained.
|
|
90
|
-
const SPECIAL_FIELDS = new Set(["voice_notes"]);
|
|
91
|
-
|
|
92
|
-
const LANGUAGE_DEFAULTS = {
|
|
93
|
-
english_us: {
|
|
94
|
-
spelling: "us",
|
|
95
|
-
quotation_style: "double",
|
|
96
|
-
em_dash_spacing: "closed",
|
|
97
|
-
abbreviation_periods: "with",
|
|
98
|
-
oxford_comma: "yes",
|
|
99
|
-
date_format: "mdy",
|
|
100
|
-
},
|
|
101
|
-
english_uk: {
|
|
102
|
-
spelling: "uk",
|
|
103
|
-
quotation_style: "single",
|
|
104
|
-
em_dash_spacing: "spaced",
|
|
105
|
-
abbreviation_periods: "without",
|
|
106
|
-
oxford_comma: "no",
|
|
107
|
-
date_format: "dmy",
|
|
108
|
-
},
|
|
109
|
-
english_au: {
|
|
110
|
-
spelling: "au",
|
|
111
|
-
quotation_style: "double",
|
|
112
|
-
em_dash_spacing: "closed",
|
|
113
|
-
abbreviation_periods: "without",
|
|
114
|
-
oxford_comma: "yes",
|
|
115
|
-
date_format: "dmy",
|
|
116
|
-
},
|
|
117
|
-
english_ca: {
|
|
118
|
-
spelling: "ca",
|
|
119
|
-
quotation_style: "double",
|
|
120
|
-
em_dash_spacing: "spaced",
|
|
121
|
-
abbreviation_periods: "without",
|
|
122
|
-
oxford_comma: "yes",
|
|
123
|
-
date_format: "dmy",
|
|
124
|
-
},
|
|
125
|
-
swedish: {
|
|
126
|
-
quotation_style: "dialogue_dash_en",
|
|
127
|
-
em_dash_spacing: "spaced",
|
|
128
|
-
date_format: "dmy",
|
|
129
|
-
},
|
|
130
|
-
norwegian: {
|
|
131
|
-
quotation_style: "dialogue_dash_en",
|
|
132
|
-
em_dash_spacing: "spaced",
|
|
133
|
-
date_format: "dmy",
|
|
134
|
-
},
|
|
135
|
-
danish: {
|
|
136
|
-
quotation_style: "dialogue_dash_en",
|
|
137
|
-
em_dash_spacing: "spaced",
|
|
138
|
-
date_format: "dmy",
|
|
139
|
-
},
|
|
140
|
-
finnish: {
|
|
141
|
-
quotation_style: "guillemets",
|
|
142
|
-
em_dash_spacing: "spaced",
|
|
143
|
-
date_format: "dmy",
|
|
144
|
-
},
|
|
145
|
-
french: {
|
|
146
|
-
quotation_style: "guillemets",
|
|
147
|
-
em_dash_spacing: "spaced",
|
|
148
|
-
date_format: "dmy",
|
|
149
|
-
},
|
|
150
|
-
italian: {
|
|
151
|
-
quotation_style: "guillemets",
|
|
152
|
-
em_dash_spacing: "spaced",
|
|
153
|
-
date_format: "dmy",
|
|
154
|
-
},
|
|
155
|
-
russian: {
|
|
156
|
-
quotation_style: "guillemets",
|
|
157
|
-
em_dash_spacing: "spaced",
|
|
158
|
-
date_format: "dmy",
|
|
159
|
-
},
|
|
160
|
-
portuguese_pt: {
|
|
161
|
-
quotation_style: "guillemets",
|
|
162
|
-
em_dash_spacing: "spaced",
|
|
163
|
-
date_format: "dmy",
|
|
164
|
-
},
|
|
165
|
-
portuguese_br: {
|
|
166
|
-
quotation_style: "double",
|
|
167
|
-
em_dash_spacing: "closed",
|
|
168
|
-
date_format: "dmy",
|
|
169
|
-
},
|
|
170
|
-
german: {
|
|
171
|
-
quotation_style: "low9",
|
|
172
|
-
em_dash_spacing: "spaced",
|
|
173
|
-
date_format: "dmy",
|
|
174
|
-
},
|
|
175
|
-
dutch: {
|
|
176
|
-
quotation_style: "low9",
|
|
177
|
-
em_dash_spacing: "spaced",
|
|
178
|
-
date_format: "dmy",
|
|
179
|
-
},
|
|
180
|
-
polish: {
|
|
181
|
-
quotation_style: "low9",
|
|
182
|
-
em_dash_spacing: "spaced",
|
|
183
|
-
date_format: "dmy",
|
|
184
|
-
},
|
|
185
|
-
czech: {
|
|
186
|
-
quotation_style: "low9",
|
|
187
|
-
em_dash_spacing: "spaced",
|
|
188
|
-
date_format: "dmy",
|
|
189
|
-
},
|
|
190
|
-
hungarian: {
|
|
191
|
-
quotation_style: "low9",
|
|
192
|
-
em_dash_spacing: "spaced",
|
|
193
|
-
date_format: "dmy",
|
|
194
|
-
},
|
|
195
|
-
spanish: {
|
|
196
|
-
quotation_style: "dialogue_dash_em",
|
|
197
|
-
em_dash_spacing: "spaced",
|
|
198
|
-
date_format: "dmy",
|
|
199
|
-
},
|
|
200
|
-
irish: {
|
|
201
|
-
quotation_style: "dialogue_dash_em",
|
|
202
|
-
em_dash_spacing: "spaced",
|
|
203
|
-
date_format: "dmy",
|
|
204
|
-
},
|
|
205
|
-
japanese: {
|
|
206
|
-
quotation_style: "corner_brackets",
|
|
207
|
-
},
|
|
208
|
-
korean: {
|
|
209
|
-
quotation_style: "corner_brackets",
|
|
210
|
-
},
|
|
211
|
-
chinese_traditional: {
|
|
212
|
-
quotation_style: "corner_brackets",
|
|
213
|
-
},
|
|
214
|
-
chinese_simplified: {
|
|
215
|
-
quotation_style: "double",
|
|
216
|
-
},
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
function projectRootFromId(syncDir, projectId) {
|
|
220
|
-
if (!projectId.includes("/")) {
|
|
221
|
-
return path.join(syncDir, "projects", projectId);
|
|
222
|
-
}
|
|
223
|
-
const [universeId, projectSlug] = projectId.split("/");
|
|
224
|
-
return path.join(syncDir, "universes", universeId, projectSlug);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function inferNestedQuotationStyle(quotationStyle) {
|
|
228
|
-
if (quotationStyle === "double") return "single";
|
|
229
|
-
if (quotationStyle === "single") return "double";
|
|
230
|
-
if (quotationStyle === "guillemets") return "guillemets_single";
|
|
231
|
-
if (quotationStyle === "low9") return "low9_single";
|
|
232
|
-
if (quotationStyle === "corner_brackets") return "corner_brackets_double";
|
|
233
|
-
return null;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function normalizeTense(value) {
|
|
237
|
-
if (typeof value !== "string") return null;
|
|
238
|
-
const trimmed = value.trim().toLowerCase();
|
|
239
|
-
if (!trimmed) return null;
|
|
240
|
-
|
|
241
|
-
if (trimmed.startsWith("present")) return "present";
|
|
242
|
-
if (trimmed.startsWith("past")) return "past";
|
|
243
|
-
return trimmed;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function normalizeConfigShape(rawConfig) {
|
|
247
|
-
const normalized = Object.create(null);
|
|
248
|
-
for (const [key, value] of Object.entries(rawConfig ?? {})) {
|
|
249
|
-
// Skip null/undefined — treat as unset, same as a missing key.
|
|
250
|
-
if (value === null || value === undefined) continue;
|
|
251
|
-
if (typeof value === "string") {
|
|
252
|
-
const trimmed = value.trim();
|
|
253
|
-
if (trimmed !== "") {
|
|
254
|
-
normalized[key] = trimmed;
|
|
255
|
-
}
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
normalized[key] = value;
|
|
259
|
-
}
|
|
260
|
-
return normalized;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function validateConfig(config, sourcePath) {
|
|
264
|
-
const normalized = normalizeConfigShape(config);
|
|
265
|
-
const sanitized = Object.create(null);
|
|
266
|
-
const errors = [];
|
|
267
|
-
const unknownFields = [];
|
|
268
|
-
|
|
269
|
-
for (const [key, value] of Object.entries(normalized)) {
|
|
270
|
-
if (!Object.hasOwn(ENUMS, key) && !SPECIAL_FIELDS.has(key)) {
|
|
271
|
-
unknownFields.push(key);
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (SPECIAL_FIELDS.has(key)) {
|
|
276
|
-
if (typeof value !== "string") {
|
|
277
|
-
errors.push({
|
|
278
|
-
code: "INVALID_TYPE",
|
|
279
|
-
field: key,
|
|
280
|
-
message: `${key} must be a string.`,
|
|
281
|
-
source: sourcePath,
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
if (typeof value === "string") {
|
|
285
|
-
sanitized[key] = value;
|
|
286
|
-
}
|
|
287
|
-
continue;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (typeof value !== "string") {
|
|
291
|
-
errors.push({
|
|
292
|
-
code: "INVALID_TYPE",
|
|
293
|
-
field: key,
|
|
294
|
-
message: `${key} must be a string enum value.`,
|
|
295
|
-
source: sourcePath,
|
|
296
|
-
});
|
|
297
|
-
continue;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
const valueToCheck = key === "tense" ? normalizeTense(value) : value;
|
|
301
|
-
if (!ENUMS[key].includes(valueToCheck)) {
|
|
302
|
-
errors.push({
|
|
303
|
-
code: "INVALID_ENUM",
|
|
304
|
-
field: key,
|
|
305
|
-
message: `${key} must be one of: ${ENUMS[key].join(", ")}.`,
|
|
306
|
-
source: sourcePath,
|
|
307
|
-
received: value,
|
|
308
|
-
});
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
sanitized[key] = value;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return {
|
|
316
|
-
normalized: sanitized,
|
|
317
|
-
errors,
|
|
318
|
-
unknownFields,
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function readConfigFile(filePath) {
|
|
323
|
-
if (!fs.existsSync(filePath)) return null;
|
|
324
|
-
|
|
325
|
-
let parsed;
|
|
326
|
-
try {
|
|
327
|
-
parsed = yaml.load(fs.readFileSync(filePath, "utf8"));
|
|
328
|
-
} catch (error) {
|
|
329
|
-
return {
|
|
330
|
-
ok: false,
|
|
331
|
-
errors: [{
|
|
332
|
-
code: "INVALID_YAML",
|
|
333
|
-
message: error instanceof Error ? error.message : "Invalid YAML.",
|
|
334
|
-
source: filePath,
|
|
335
|
-
}],
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
if (parsed === null || parsed === undefined) {
|
|
340
|
-
return { ok: true, config: {} };
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if (typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
344
|
-
return {
|
|
345
|
-
ok: false,
|
|
346
|
-
errors: [{
|
|
347
|
-
code: "INVALID_CONFIG",
|
|
348
|
-
message: "Config file must parse to an object.",
|
|
349
|
-
source: filePath,
|
|
350
|
-
}],
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
const { normalized, errors, unknownFields } = validateConfig(parsed, filePath);
|
|
355
|
-
if (errors.length > 0) {
|
|
356
|
-
return { ok: false, errors };
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
return {
|
|
360
|
-
ok: true,
|
|
361
|
-
config: normalized,
|
|
362
|
-
unknown_fields: unknownFields,
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
function configPathForScope(syncDir, scope, projectId) {
|
|
367
|
-
if (scope === "sync_root") {
|
|
368
|
-
return path.join(syncDir, STYLEGUIDE_CONFIG_BASENAME);
|
|
369
|
-
}
|
|
370
|
-
return path.join(projectRootFromId(syncDir, projectId), STYLEGUIDE_CONFIG_BASENAME);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
function prepareStyleguideConfigUpdate({ syncDir, scope, projectId, updates = {} }) {
|
|
374
|
-
if (scope === "project_root" && !projectId) {
|
|
375
|
-
return {
|
|
376
|
-
ok: false,
|
|
377
|
-
error: {
|
|
378
|
-
code: "PROJECT_ID_REQUIRED",
|
|
379
|
-
message: "project_id is required when scope=project_root.",
|
|
380
|
-
details: { scope, project_id: projectId ?? null },
|
|
381
|
-
},
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
const filePath = configPathForScope(syncDir, scope, projectId);
|
|
386
|
-
const current = readConfigFile(filePath);
|
|
387
|
-
|
|
388
|
-
if (current === null) {
|
|
389
|
-
return {
|
|
390
|
-
ok: false,
|
|
391
|
-
error: {
|
|
392
|
-
code: "STYLEGUIDE_CONFIG_NOT_FOUND",
|
|
393
|
-
message: "Cannot update styleguide config because no config exists at the requested scope.",
|
|
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
|
-
},
|
|
400
|
-
},
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
if (!current.ok) {
|
|
405
|
-
return {
|
|
406
|
-
ok: false,
|
|
407
|
-
error: {
|
|
408
|
-
code: "INVALID_STYLEGUIDE_CONFIG",
|
|
409
|
-
message: "Styleguide config validation failed.",
|
|
410
|
-
details: { file_path: filePath, issues: current.errors },
|
|
411
|
-
},
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
const validatedUpdates = validateConfig(updates, "<updates>");
|
|
416
|
-
if (validatedUpdates.errors.length > 0) {
|
|
417
|
-
return {
|
|
418
|
-
ok: false,
|
|
419
|
-
error: {
|
|
420
|
-
code: "INVALID_STYLEGUIDE_UPDATE",
|
|
421
|
-
message: "Requested styleguide updates failed validation.",
|
|
422
|
-
details: validatedUpdates.errors,
|
|
423
|
-
},
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
const merged = Object.create(null);
|
|
428
|
-
Object.assign(merged, current.config, validatedUpdates.normalized);
|
|
429
|
-
|
|
430
|
-
const ordered = Object.create(null);
|
|
431
|
-
for (const key of STYLEGUIDE_FIELD_ORDER) {
|
|
432
|
-
if (merged[key] !== undefined) {
|
|
433
|
-
ordered[key] = merged[key];
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const changedFields = [];
|
|
438
|
-
const allKeys = new Set([...Object.keys(current.config ?? {}), ...Object.keys(ordered)]);
|
|
439
|
-
for (const key of allKeys) {
|
|
440
|
-
if (current.config?.[key] !== ordered[key]) {
|
|
441
|
-
changedFields.push({
|
|
442
|
-
field: key,
|
|
443
|
-
before: current.config?.[key],
|
|
444
|
-
after: ordered[key],
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
return {
|
|
450
|
-
ok: true,
|
|
451
|
-
file_path: filePath,
|
|
452
|
-
scope,
|
|
453
|
-
project_id: projectId ?? null,
|
|
454
|
-
current_config: current.config,
|
|
455
|
-
config: ordered,
|
|
456
|
-
changed_fields: changedFields,
|
|
457
|
-
warnings: {
|
|
458
|
-
unknown_fields: validatedUpdates.unknownFields,
|
|
459
|
-
},
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
function getConfigCandidates(syncDir, projectId) {
|
|
464
|
-
const candidates = [
|
|
465
|
-
{
|
|
466
|
-
scope: "sync_root",
|
|
467
|
-
file_path: path.join(syncDir, STYLEGUIDE_CONFIG_BASENAME),
|
|
468
|
-
},
|
|
469
|
-
];
|
|
470
|
-
|
|
471
|
-
if (!projectId) return candidates;
|
|
472
|
-
|
|
473
|
-
if (projectId.includes("/")) {
|
|
474
|
-
const [universeId] = projectId.split("/");
|
|
475
|
-
candidates.push({
|
|
476
|
-
scope: "universe_root",
|
|
477
|
-
file_path: path.join(syncDir, "universes", universeId, STYLEGUIDE_CONFIG_BASENAME),
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
candidates.push({
|
|
482
|
-
scope: "project_root",
|
|
483
|
-
file_path: path.join(projectRootFromId(syncDir, projectId), STYLEGUIDE_CONFIG_BASENAME),
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
return candidates;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
function applyDerivedDefaults(config) {
|
|
490
|
-
const resolved = { ...config };
|
|
491
|
-
const inferred_defaults = {};
|
|
492
|
-
|
|
493
|
-
if (resolved.language && LANGUAGE_DEFAULTS[resolved.language]) {
|
|
494
|
-
const defaults = LANGUAGE_DEFAULTS[resolved.language];
|
|
495
|
-
for (const [key, value] of Object.entries(defaults)) {
|
|
496
|
-
if (resolved[key] === undefined) {
|
|
497
|
-
resolved[key] = value;
|
|
498
|
-
inferred_defaults[key] = value;
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
if (!resolved.quotation_style_nested && resolved.quotation_style) {
|
|
504
|
-
const nested = inferNestedQuotationStyle(resolved.quotation_style);
|
|
505
|
-
if (nested) {
|
|
506
|
-
resolved.quotation_style_nested = nested;
|
|
507
|
-
inferred_defaults.quotation_style_nested = nested;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
if (resolved.tense) {
|
|
512
|
-
resolved.tense = normalizeTense(resolved.tense);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
return { resolved, inferred_defaults };
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
export function buildStyleguideConfigDraft({ language, overrides = {}, voice_notes }) {
|
|
519
|
-
const overrideValidation = validateConfig(overrides, "<overrides>");
|
|
520
|
-
if (overrideValidation.errors.length > 0) {
|
|
521
|
-
return {
|
|
522
|
-
ok: false,
|
|
523
|
-
error: {
|
|
524
|
-
code: "INVALID_STYLEGUIDE_OVERRIDE",
|
|
525
|
-
message: "Requested styleguide overrides failed validation.",
|
|
526
|
-
details: overrideValidation.errors,
|
|
527
|
-
},
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
if (!ENUMS.language.includes(language)) {
|
|
532
|
-
return {
|
|
533
|
-
ok: false,
|
|
534
|
-
error: {
|
|
535
|
-
code: "INVALID_STYLEGUIDE_LANGUAGE",
|
|
536
|
-
message: `language must be one of: ${ENUMS.language.join(", ")}.`,
|
|
537
|
-
},
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
const merged = {
|
|
542
|
-
...overrideValidation.normalized,
|
|
543
|
-
language,
|
|
544
|
-
};
|
|
545
|
-
|
|
546
|
-
if (typeof voice_notes === "string" && voice_notes.trim()) {
|
|
547
|
-
merged.voice_notes = voice_notes.trim();
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
const { resolved, inferred_defaults } = applyDerivedDefaults(merged);
|
|
551
|
-
return {
|
|
552
|
-
ok: true,
|
|
553
|
-
config: resolved,
|
|
554
|
-
inferred_defaults,
|
|
555
|
-
warnings: {
|
|
556
|
-
unknown_fields: overrideValidation.unknownFields,
|
|
557
|
-
},
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
export function summarizeStyleguideConfig({ resolvedConfig, inferredDefaults = {} }) {
|
|
562
|
-
if (!resolvedConfig || typeof resolvedConfig !== "object") {
|
|
563
|
-
return {
|
|
564
|
-
ok: false,
|
|
565
|
-
error: {
|
|
566
|
-
code: "INVALID_STYLEGUIDE_CONFIG",
|
|
567
|
-
message: "Cannot summarize styleguide config without a resolved config object.",
|
|
568
|
-
},
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
const lines = [];
|
|
573
|
-
if (resolvedConfig.language) lines.push(`Writing language: ${resolvedConfig.language}.`);
|
|
574
|
-
if (resolvedConfig.spelling) lines.push(`Spelling variant: ${resolvedConfig.spelling}.`);
|
|
575
|
-
if (resolvedConfig.quotation_style) lines.push(`Dialogue punctuation uses ${resolvedConfig.quotation_style}.`);
|
|
576
|
-
if (resolvedConfig.quotation_style_nested) lines.push(`Nested quotations use ${resolvedConfig.quotation_style_nested}.`);
|
|
577
|
-
if (resolvedConfig.tense) lines.push(`Default narrative tense: ${resolvedConfig.tense}.`);
|
|
578
|
-
if (resolvedConfig.pov) lines.push(`Default POV: ${resolvedConfig.pov}.`);
|
|
579
|
-
if (resolvedConfig.dialogue_tags) lines.push(`Dialogue tag policy: ${resolvedConfig.dialogue_tags}.`);
|
|
580
|
-
if (resolvedConfig.sentence_fragments) lines.push(`Sentence fragments: ${resolvedConfig.sentence_fragments}.`);
|
|
581
|
-
if (resolvedConfig.date_format) lines.push(`Date format: ${resolvedConfig.date_format}.`);
|
|
582
|
-
if (resolvedConfig.time_format) lines.push(`Time format: ${resolvedConfig.time_format}.`);
|
|
583
|
-
if (resolvedConfig.voice_notes) lines.push(`Voice notes: ${resolvedConfig.voice_notes}`);
|
|
584
|
-
|
|
585
|
-
const inferred = Object.keys(inferredDefaults);
|
|
586
|
-
if (inferred.length > 0) {
|
|
587
|
-
lines.push(`Inferred defaults currently fill: ${inferred.join(", ")}.`);
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
return {
|
|
591
|
-
ok: true,
|
|
592
|
-
summary_text: lines.join(" "),
|
|
593
|
-
summary_lines: lines,
|
|
594
|
-
};
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
export function updateStyleguideConfig({ syncDir, scope, projectId, updates = {} }) {
|
|
598
|
-
const prepared = prepareStyleguideConfigUpdate({ syncDir, scope, projectId, updates });
|
|
599
|
-
if (!prepared.ok) return prepared;
|
|
600
|
-
|
|
601
|
-
if (prepared.changed_fields.length === 0) {
|
|
602
|
-
return {
|
|
603
|
-
ok: true,
|
|
604
|
-
file_path: prepared.file_path,
|
|
605
|
-
scope: prepared.scope,
|
|
606
|
-
project_id: prepared.project_id,
|
|
607
|
-
config: prepared.config,
|
|
608
|
-
changed_fields: prepared.changed_fields,
|
|
609
|
-
warnings: prepared.warnings,
|
|
610
|
-
noop: true,
|
|
611
|
-
message: "No changes detected for requested styleguide updates.",
|
|
612
|
-
};
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
fs.mkdirSync(path.dirname(prepared.file_path), { recursive: true });
|
|
616
|
-
fs.writeFileSync(prepared.file_path, yaml.dump(prepared.config, { lineWidth: 120 }), "utf8");
|
|
617
|
-
|
|
618
|
-
return {
|
|
619
|
-
ok: true,
|
|
620
|
-
file_path: prepared.file_path,
|
|
621
|
-
scope: prepared.scope,
|
|
622
|
-
project_id: prepared.project_id,
|
|
623
|
-
config: prepared.config,
|
|
624
|
-
changed_fields: prepared.changed_fields,
|
|
625
|
-
warnings: prepared.warnings,
|
|
626
|
-
noop: false,
|
|
627
|
-
message: "Styleguide config updated.",
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
export function previewStyleguideConfigUpdate({ syncDir, scope, projectId, updates = {} }) {
|
|
632
|
-
return prepareStyleguideConfigUpdate({ syncDir, scope, projectId, updates });
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
export function resolveStyleguideConfig({ syncDir, projectId }) {
|
|
636
|
-
const candidates = getConfigCandidates(syncDir, projectId);
|
|
637
|
-
const sources = [];
|
|
638
|
-
const unknownFields = [];
|
|
639
|
-
const merged = Object.create(null);
|
|
640
|
-
|
|
641
|
-
for (const candidate of candidates) {
|
|
642
|
-
const loaded = readConfigFile(candidate.file_path);
|
|
643
|
-
if (loaded === null) continue;
|
|
644
|
-
|
|
645
|
-
if (!loaded.ok) {
|
|
646
|
-
return {
|
|
647
|
-
ok: false,
|
|
648
|
-
error: {
|
|
649
|
-
code: "INVALID_STYLEGUIDE_CONFIG",
|
|
650
|
-
message: "Styleguide config validation failed.",
|
|
651
|
-
details: {
|
|
652
|
-
file_path: candidate.file_path,
|
|
653
|
-
issues: loaded.errors,
|
|
654
|
-
},
|
|
655
|
-
},
|
|
656
|
-
};
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
Object.assign(merged, loaded.config);
|
|
660
|
-
if (loaded.unknown_fields?.length) {
|
|
661
|
-
for (const field of loaded.unknown_fields) {
|
|
662
|
-
unknownFields.push({ scope: candidate.scope, field, source: candidate.file_path });
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
sources.push({
|
|
667
|
-
scope: candidate.scope,
|
|
668
|
-
file_path: candidate.file_path,
|
|
669
|
-
});
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
const { resolved, inferred_defaults } = applyDerivedDefaults(merged);
|
|
673
|
-
return {
|
|
674
|
-
ok: true,
|
|
675
|
-
config_found: sources.length > 0,
|
|
676
|
-
setup_required: sources.length === 0,
|
|
677
|
-
resolved_config: sources.length > 0 ? resolved : null,
|
|
678
|
-
inferred_defaults,
|
|
679
|
-
sources,
|
|
680
|
-
warnings: {
|
|
681
|
-
unknown_fields: unknownFields,
|
|
682
|
-
},
|
|
683
|
-
};
|
|
684
|
-
}
|
|
1
|
+
export * from "./src/styleguide/prose-styleguide.js";
|