@hanna84/mcp-writing 3.0.0 → 3.1.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 +21 -1
- package/package.json +1 -1
- package/src/index.js +12 -0
- package/src/runtime/runtime-diagnostics.js +14 -1
- package/src/tools/editing.js +257 -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
|
-
#### [v3.
|
|
7
|
+
#### [v3.1.1](https://github.com/hannasdev/mcp-writing.git
|
|
8
|
+
/compare/v3.1.0...v3.1.1)
|
|
9
|
+
|
|
10
|
+
- fix: surface runtime warning for invalid PROSE_STYLEGUIDE_ENFORCEMENT_MODE [`#167`](https://github.com/hannasdev/mcp-writing.git
|
|
11
|
+
/pull/167)
|
|
12
|
+
|
|
13
|
+
#### [v3.1.0](https://github.com/hannasdev/mcp-writing.git
|
|
14
|
+
/compare/v3.0.0...v3.1.0)
|
|
15
|
+
|
|
16
|
+
> 2 May 2026
|
|
17
|
+
|
|
18
|
+
- feat(editing): enforce styleguide automatically in edit flow [`#166`](https://github.com/hannasdev/mcp-writing.git
|
|
19
|
+
/pull/166)
|
|
20
|
+
- Release 3.1.0 [`ad1106c`](https://github.com/hannasdev/mcp-writing.git
|
|
21
|
+
/commit/ad1106ca4099777d9b05556dfd9cf75de4f030f1)
|
|
22
|
+
|
|
23
|
+
### [v3.0.0](https://github.com/hannasdev/mcp-writing.git
|
|
8
24
|
/compare/v2.18.1...v3.0.0)
|
|
9
25
|
|
|
26
|
+
> 2 May 2026
|
|
27
|
+
|
|
10
28
|
- feat!: improve MCP tooling usability and harden scene-id safety [`#165`](https://github.com/hannasdev/mcp-writing.git
|
|
11
29
|
/pull/165)
|
|
30
|
+
- Release 3.0.0 [`d7541b1`](https://github.com/hannasdev/mcp-writing.git
|
|
31
|
+
/commit/d7541b1511dbe92c514cb0435f94b8626be5ea29)
|
|
12
32
|
|
|
13
33
|
#### [v2.18.1](https://github.com/hannasdev/mcp-writing.git
|
|
14
34
|
/compare/v2.18.0...v2.18.1)
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -71,7 +71,14 @@ const OWNERSHIP_GUARD_MODE_RAW = (process.env.OWNERSHIP_GUARD_MODE ?? "warn").tr
|
|
|
71
71
|
const OWNERSHIP_GUARD_MODE = OWNERSHIP_GUARD_MODE_RAW === "fail" || OWNERSHIP_GUARD_MODE_RAW === "warn"
|
|
72
72
|
? OWNERSHIP_GUARD_MODE_RAW
|
|
73
73
|
: "warn";
|
|
74
|
+
const STYLEGUIDE_ENFORCEMENT_MODE_RAW = (process.env.PROSE_STYLEGUIDE_ENFORCEMENT_MODE ?? "warn").trim().toLowerCase();
|
|
75
|
+
const STYLEGUIDE_ENFORCEMENT_MODE = STYLEGUIDE_ENFORCEMENT_MODE_RAW === "off"
|
|
76
|
+
|| STYLEGUIDE_ENFORCEMENT_MODE_RAW === "warn"
|
|
77
|
+
|| STYLEGUIDE_ENFORCEMENT_MODE_RAW === "required"
|
|
78
|
+
? STYLEGUIDE_ENFORCEMENT_MODE_RAW
|
|
79
|
+
: "warn";
|
|
74
80
|
const OWNERSHIP_GUARD_MODE_RAW_DISPLAY = JSON.stringify(OWNERSHIP_GUARD_MODE_RAW);
|
|
81
|
+
const STYLEGUIDE_ENFORCEMENT_MODE_RAW_DISPLAY = JSON.stringify(STYLEGUIDE_ENFORCEMENT_MODE_RAW);
|
|
75
82
|
const __filename = fileURLToPath(import.meta.url);
|
|
76
83
|
const __dirname = path.dirname(__filename);
|
|
77
84
|
const ROOT_DIR = path.resolve(__dirname, "..");
|
|
@@ -213,6 +220,9 @@ const RUNTIME_DIAGNOSTICS = getRuntimeDiagnostics({
|
|
|
213
220
|
ownershipGuardModeRaw: OWNERSHIP_GUARD_MODE_RAW,
|
|
214
221
|
ownershipGuardMode: OWNERSHIP_GUARD_MODE,
|
|
215
222
|
ownershipGuardModeRawDisplay: OWNERSHIP_GUARD_MODE_RAW_DISPLAY,
|
|
223
|
+
styleguideEnforcementModeRaw: STYLEGUIDE_ENFORCEMENT_MODE_RAW,
|
|
224
|
+
styleguideEnforcementMode: STYLEGUIDE_ENFORCEMENT_MODE,
|
|
225
|
+
styleguideEnforcementModeRawDisplay: STYLEGUIDE_ENFORCEMENT_MODE_RAW_DISPLAY,
|
|
216
226
|
syncDirWritable: SYNC_DIR_WRITABLE,
|
|
217
227
|
syncDirAbs: SYNC_DIR_ABS,
|
|
218
228
|
syncOwnershipDiagnostics: SYNC_OWNERSHIP_DIAGNOSTICS,
|
|
@@ -410,6 +420,7 @@ function createMcpServer() {
|
|
|
410
420
|
isPathCandidateInsideSyncDir,
|
|
411
421
|
pendingProposals,
|
|
412
422
|
generateProposalId,
|
|
423
|
+
STYLEGUIDE_ENFORCEMENT_MODE,
|
|
413
424
|
};
|
|
414
425
|
registerSyncTools(s, toolContext);
|
|
415
426
|
registerSearchTools(s, toolContext);
|
|
@@ -434,6 +445,7 @@ function createMcpServer() {
|
|
|
434
445
|
permission_diagnostics: SYNC_OWNERSHIP_DIAGNOSTICS,
|
|
435
446
|
git_available: GIT_AVAILABLE,
|
|
436
447
|
git_enabled: GIT_ENABLED,
|
|
448
|
+
styleguide_enforcement_mode: STYLEGUIDE_ENFORCEMENT_MODE,
|
|
437
449
|
http_port: HTTP_PORT,
|
|
438
450
|
runtime_warnings: RUNTIME_DIAGNOSTICS.warnings,
|
|
439
451
|
setup_recommendations: RUNTIME_DIAGNOSTICS.recommendations,
|
|
@@ -6,9 +6,12 @@
|
|
|
6
6
|
* is straightforward to test.
|
|
7
7
|
*
|
|
8
8
|
* @param {object} opts
|
|
9
|
-
* @param {string} opts.ownershipGuardModeRaw
|
|
9
|
+
* @param {string} opts.ownershipGuardModeRaw Trimmed/lowercased env token before enum validation
|
|
10
10
|
* @param {string} opts.ownershipGuardMode Normalised value ("warn" | "fail")
|
|
11
11
|
* @param {string} opts.ownershipGuardModeRawDisplay JSON.stringify of the raw value
|
|
12
|
+
* @param {string} opts.styleguideEnforcementModeRaw Trimmed/lowercased env token before enum validation
|
|
13
|
+
* @param {string} opts.styleguideEnforcementMode Normalised value ("off" | "warn" | "required")
|
|
14
|
+
* @param {string} opts.styleguideEnforcementModeRawDisplay JSON.stringify of the raw value
|
|
12
15
|
* @param {boolean} opts.syncDirWritable
|
|
13
16
|
* @param {string} opts.syncDirAbs Resolved absolute path shown in messages
|
|
14
17
|
* @param {object} opts.syncOwnershipDiagnostics Result of getSyncOwnershipDiagnostics()
|
|
@@ -20,6 +23,9 @@ export function getRuntimeDiagnostics({
|
|
|
20
23
|
ownershipGuardModeRaw,
|
|
21
24
|
ownershipGuardMode,
|
|
22
25
|
ownershipGuardModeRawDisplay,
|
|
26
|
+
styleguideEnforcementModeRaw,
|
|
27
|
+
styleguideEnforcementMode,
|
|
28
|
+
styleguideEnforcementModeRawDisplay,
|
|
23
29
|
syncDirWritable,
|
|
24
30
|
syncDirAbs,
|
|
25
31
|
syncOwnershipDiagnostics,
|
|
@@ -36,6 +42,13 @@ export function getRuntimeDiagnostics({
|
|
|
36
42
|
recommendations.push("Set OWNERSHIP_GUARD_MODE to either 'warn' or 'fail'.");
|
|
37
43
|
}
|
|
38
44
|
|
|
45
|
+
if (styleguideEnforcementModeRaw !== styleguideEnforcementMode) {
|
|
46
|
+
warnings.push(
|
|
47
|
+
`STYLEGUIDE_ENFORCEMENT_MODE_INVALID: Unsupported PROSE_STYLEGUIDE_ENFORCEMENT_MODE=${styleguideEnforcementModeRawDisplay}. Falling back to 'warn'.`
|
|
48
|
+
);
|
|
49
|
+
recommendations.push("Set PROSE_STYLEGUIDE_ENFORCEMENT_MODE to one of 'off', 'warn', or 'required'.");
|
|
50
|
+
}
|
|
51
|
+
|
|
39
52
|
if (syncOwnershipDiagnostics.runtime_uid_override_ignored) {
|
|
40
53
|
warnings.push("RUNTIME_UID_OVERRIDE_IGNORED: RUNTIME_UID_OVERRIDE is ignored unless NODE_ENV=test or ALLOW_RUNTIME_UID_OVERRIDE=1.");
|
|
41
54
|
recommendations.push("Avoid RUNTIME_UID_OVERRIDE in production runtime environments.");
|
package/src/tools/editing.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
3
5
|
import matter from "gray-matter";
|
|
4
6
|
import yaml from "js-yaml";
|
|
5
7
|
import { createSnapshot, listSnapshots } from "../core/git.js";
|
|
6
8
|
import { getFileWriteDiagnostics, readMeta, indexSceneFile } from "../sync/sync.js";
|
|
9
|
+
import { resolveStyleguideConfig } from "../styleguide/prose-styleguide.js";
|
|
10
|
+
import {
|
|
11
|
+
PROSE_STYLEGUIDE_SKILL_BASENAME,
|
|
12
|
+
PROSE_STYLEGUIDE_SKILL_DIRNAME,
|
|
13
|
+
} from "../styleguide/prose-styleguide-skill.js";
|
|
14
|
+
import { analyzeSceneStyleguideDrift } from "../styleguide/prose-styleguide-drift.js";
|
|
7
15
|
|
|
8
16
|
function renderSceneContent(metadata, revisedProse) {
|
|
9
17
|
const hasFrontmatter = metadata && Object.keys(metadata).length > 0;
|
|
@@ -13,10 +21,197 @@ function renderSceneContent(metadata, revisedProse) {
|
|
|
13
21
|
: `${normalizedProse}\n`;
|
|
14
22
|
}
|
|
15
23
|
|
|
24
|
+
function digestFor(value) {
|
|
25
|
+
return createHash("sha256").update(value).digest("hex");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function buildStyleguideFingerprint({
|
|
29
|
+
resolvedConfig,
|
|
30
|
+
sources,
|
|
31
|
+
skillDigest,
|
|
32
|
+
setupRequired,
|
|
33
|
+
}) {
|
|
34
|
+
return digestFor(JSON.stringify({
|
|
35
|
+
resolved_config: resolvedConfig ?? null,
|
|
36
|
+
sources: sources ?? [],
|
|
37
|
+
skill_digest: skillDigest ?? null,
|
|
38
|
+
setup_required: Boolean(setupRequired),
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function resolveStyleguideSnapshot({ syncDir, projectId, errorResponse }) {
|
|
43
|
+
const resolved = resolveStyleguideConfig({
|
|
44
|
+
syncDir,
|
|
45
|
+
projectId,
|
|
46
|
+
});
|
|
47
|
+
if (!resolved.ok) {
|
|
48
|
+
return {
|
|
49
|
+
ok: false,
|
|
50
|
+
response: errorResponse(
|
|
51
|
+
resolved.error.code,
|
|
52
|
+
resolved.error.message,
|
|
53
|
+
resolved.error.details
|
|
54
|
+
),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const skillPath = path.join(
|
|
59
|
+
syncDir,
|
|
60
|
+
PROSE_STYLEGUIDE_SKILL_DIRNAME,
|
|
61
|
+
PROSE_STYLEGUIDE_SKILL_BASENAME
|
|
62
|
+
);
|
|
63
|
+
let skillExists;
|
|
64
|
+
let skillDigest = null;
|
|
65
|
+
try {
|
|
66
|
+
skillExists = fs.existsSync(skillPath);
|
|
67
|
+
if (skillExists) {
|
|
68
|
+
skillDigest = digestFor(fs.readFileSync(skillPath, "utf8"));
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
response: errorResponse(
|
|
75
|
+
"STYLEGUIDE_SKILL_IO_ERROR",
|
|
76
|
+
"Failed to read skills/prose-styleguide.md while resolving styleguide snapshot.",
|
|
77
|
+
{
|
|
78
|
+
file_path: skillPath,
|
|
79
|
+
reason: err.message,
|
|
80
|
+
}
|
|
81
|
+
),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const fingerprint = buildStyleguideFingerprint({
|
|
85
|
+
resolvedConfig: resolved.resolved_config,
|
|
86
|
+
sources: resolved.sources,
|
|
87
|
+
skillDigest,
|
|
88
|
+
setupRequired: resolved.setup_required,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
ok: true,
|
|
93
|
+
snapshot: {
|
|
94
|
+
project_id: projectId ?? null,
|
|
95
|
+
config_found: resolved.config_found,
|
|
96
|
+
setup_required: resolved.setup_required,
|
|
97
|
+
sources: resolved.sources,
|
|
98
|
+
resolved_config: resolved.resolved_config,
|
|
99
|
+
skill_file_path: skillPath,
|
|
100
|
+
skill_found: Boolean(skillExists),
|
|
101
|
+
skill_digest: skillDigest,
|
|
102
|
+
fingerprint,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function evaluateStyleguidePolicy({
|
|
108
|
+
snapshot,
|
|
109
|
+
revisedProse,
|
|
110
|
+
bypassStyleguide,
|
|
111
|
+
bypassReason,
|
|
112
|
+
enforcementMode,
|
|
113
|
+
errorResponse,
|
|
114
|
+
}) {
|
|
115
|
+
if (bypassStyleguide && !bypassReason) {
|
|
116
|
+
return {
|
|
117
|
+
ok: false,
|
|
118
|
+
response: errorResponse(
|
|
119
|
+
"STYLEGUIDE_BYPASS_REASON_REQUIRED",
|
|
120
|
+
"bypass_reason is required when bypass_styleguide=true."
|
|
121
|
+
),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const warnings = [];
|
|
126
|
+
if (enforcementMode !== "off") {
|
|
127
|
+
if (snapshot.setup_required || !snapshot.config_found) {
|
|
128
|
+
if (enforcementMode === "required" && !bypassStyleguide) {
|
|
129
|
+
return {
|
|
130
|
+
ok: false,
|
|
131
|
+
response: errorResponse(
|
|
132
|
+
"STYLEGUIDE_CONFIG_REQUIRED",
|
|
133
|
+
"Cannot propose prose edits before prose-styleguide.config.yaml is configured.",
|
|
134
|
+
{
|
|
135
|
+
project_id: snapshot.project_id,
|
|
136
|
+
next_step: "Run setup_prose_styleguide_config (or bootstrap_prose_styleguide_config), then retry propose_edit.",
|
|
137
|
+
}
|
|
138
|
+
),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
warnings.push("No prose-styleguide.config.yaml is currently resolved for this scene.");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!snapshot.skill_found) {
|
|
145
|
+
if (enforcementMode === "required" && !bypassStyleguide) {
|
|
146
|
+
return {
|
|
147
|
+
ok: false,
|
|
148
|
+
response: errorResponse(
|
|
149
|
+
"STYLEGUIDE_SKILL_REQUIRED",
|
|
150
|
+
"Cannot propose prose edits before skills/prose-styleguide.md exists.",
|
|
151
|
+
{
|
|
152
|
+
next_step: "Run setup_prose_styleguide_skill, then retry propose_edit.",
|
|
153
|
+
}
|
|
154
|
+
),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
warnings.push("skills/prose-styleguide.md was not found at sync root.");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const canApply =
|
|
162
|
+
enforcementMode !== "off"
|
|
163
|
+
&& !bypassStyleguide
|
|
164
|
+
&& snapshot.config_found
|
|
165
|
+
&& !snapshot.setup_required
|
|
166
|
+
&& Boolean(snapshot.resolved_config);
|
|
167
|
+
|
|
168
|
+
const analysis = canApply
|
|
169
|
+
? analyzeSceneStyleguideDrift({
|
|
170
|
+
prose: revisedProse,
|
|
171
|
+
resolvedConfig: snapshot.resolved_config,
|
|
172
|
+
})
|
|
173
|
+
: { observed: {}, drift: [] };
|
|
174
|
+
|
|
175
|
+
const violations = analysis.drift.map((entry) => ({
|
|
176
|
+
field: entry.field,
|
|
177
|
+
declared: entry.declared,
|
|
178
|
+
observed: entry.observed,
|
|
179
|
+
severity: "error",
|
|
180
|
+
}));
|
|
181
|
+
|
|
182
|
+
if (enforcementMode === "required" && violations.length > 0 && !bypassStyleguide) {
|
|
183
|
+
return {
|
|
184
|
+
ok: false,
|
|
185
|
+
response: errorResponse(
|
|
186
|
+
"STYLEGUIDE_VIOLATIONS",
|
|
187
|
+
"Revised prose violates required styleguide conventions.",
|
|
188
|
+
{
|
|
189
|
+
violations,
|
|
190
|
+
next_step: "Revise prose to match conventions or retry with bypass_styleguide=true and a bypass_reason.",
|
|
191
|
+
}
|
|
192
|
+
),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
ok: true,
|
|
198
|
+
policy: {
|
|
199
|
+
enforcement_mode: enforcementMode,
|
|
200
|
+
bypass_used: Boolean(bypassStyleguide),
|
|
201
|
+
bypass_reason: bypassStyleguide ? bypassReason : null,
|
|
202
|
+
styleguide_applied: canApply,
|
|
203
|
+
warnings,
|
|
204
|
+
observed_signals: analysis.observed,
|
|
205
|
+
violations,
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
16
210
|
export function registerEditingTools(s, {
|
|
17
211
|
db,
|
|
18
212
|
SYNC_DIR,
|
|
19
213
|
GIT_ENABLED,
|
|
214
|
+
STYLEGUIDE_ENFORCEMENT_MODE,
|
|
20
215
|
errorResponse,
|
|
21
216
|
jsonResponse,
|
|
22
217
|
pendingProposals,
|
|
@@ -31,8 +226,10 @@ export function registerEditingTools(s, {
|
|
|
31
226
|
project_id: z.string().optional().describe("Optional project ID to disambiguate duplicate scene IDs across projects."),
|
|
32
227
|
instruction: z.string().describe("A brief instruction for the edit (e.g. 'Tighten the opening paragraph'). Used in the git commit message."),
|
|
33
228
|
revised_prose: z.string().describe("The complete revised prose text for the scene."),
|
|
229
|
+
bypass_styleguide: z.boolean().optional().describe("If true, bypasses automatic styleguide checks for this proposal."),
|
|
230
|
+
bypass_reason: z.string().optional().describe("Required when bypass_styleguide=true. Explains why this edit should ignore styleguide checks."),
|
|
34
231
|
},
|
|
35
|
-
async ({ scene_id, project_id, instruction, revised_prose }) => {
|
|
232
|
+
async ({ scene_id, project_id, instruction, revised_prose, bypass_styleguide = false, bypass_reason }) => {
|
|
36
233
|
if (!GIT_ENABLED) {
|
|
37
234
|
return errorResponse("GIT_UNAVAILABLE", "Git is not available — prose editing is not supported. Ensure git is installed and the sync directory is writable.");
|
|
38
235
|
}
|
|
@@ -58,6 +255,27 @@ export function registerEditingTools(s, {
|
|
|
58
255
|
scene = scenes[0];
|
|
59
256
|
}
|
|
60
257
|
|
|
258
|
+
const styleguideSnapshotResult = resolveStyleguideSnapshot({
|
|
259
|
+
syncDir: SYNC_DIR,
|
|
260
|
+
projectId: scene.project_id,
|
|
261
|
+
errorResponse,
|
|
262
|
+
});
|
|
263
|
+
if (!styleguideSnapshotResult.ok) {
|
|
264
|
+
return styleguideSnapshotResult.response;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const styleguidePolicy = evaluateStyleguidePolicy({
|
|
268
|
+
snapshot: styleguideSnapshotResult.snapshot,
|
|
269
|
+
revisedProse: revised_prose,
|
|
270
|
+
bypassStyleguide: bypass_styleguide,
|
|
271
|
+
bypassReason: bypass_reason,
|
|
272
|
+
enforcementMode: STYLEGUIDE_ENFORCEMENT_MODE,
|
|
273
|
+
errorResponse,
|
|
274
|
+
});
|
|
275
|
+
if (!styleguidePolicy.ok) {
|
|
276
|
+
return styleguidePolicy.response;
|
|
277
|
+
}
|
|
278
|
+
|
|
61
279
|
try {
|
|
62
280
|
const raw = fs.readFileSync(scene.file_path, "utf8");
|
|
63
281
|
const { data: metadata, content: currentProse } = matter(raw);
|
|
@@ -90,6 +308,8 @@ export function registerEditingTools(s, {
|
|
|
90
308
|
rendered_content: renderedContent,
|
|
91
309
|
original_prose: currentProse,
|
|
92
310
|
metadata,
|
|
311
|
+
styleguide_snapshot: styleguideSnapshotResult.snapshot,
|
|
312
|
+
styleguide_policy: styleguidePolicy.policy,
|
|
93
313
|
created_at: new Date().toISOString(),
|
|
94
314
|
});
|
|
95
315
|
|
|
@@ -107,6 +327,13 @@ export function registerEditingTools(s, {
|
|
|
107
327
|
instruction,
|
|
108
328
|
diff_preview: diffPreview,
|
|
109
329
|
noop,
|
|
330
|
+
styleguide: {
|
|
331
|
+
...styleguidePolicy.policy,
|
|
332
|
+
fingerprint: styleguideSnapshotResult.snapshot.fingerprint,
|
|
333
|
+
config_found: styleguideSnapshotResult.snapshot.config_found,
|
|
334
|
+
skill_found: styleguideSnapshotResult.snapshot.skill_found,
|
|
335
|
+
sources: styleguideSnapshotResult.snapshot.sources,
|
|
336
|
+
},
|
|
110
337
|
note: noop
|
|
111
338
|
? "This proposal matches the current scene file. Calling commit_edit will be a no-op."
|
|
112
339
|
: "Review the diff above. Call commit_edit with this proposal_id to apply the change.",
|
|
@@ -167,6 +394,35 @@ export function registerEditingTools(s, {
|
|
|
167
394
|
return errorResponse("INVALID_EDIT", `Proposal '${proposal_id}' is for project '${proposal.project_id}', not '${project_id}'.`);
|
|
168
395
|
}
|
|
169
396
|
|
|
397
|
+
if (
|
|
398
|
+
proposal.styleguide_snapshot
|
|
399
|
+
&& proposal.styleguide_policy
|
|
400
|
+
&& proposal.styleguide_policy.bypass_used !== true
|
|
401
|
+
) {
|
|
402
|
+
const latestStyleguideSnapshot = resolveStyleguideSnapshot({
|
|
403
|
+
syncDir: SYNC_DIR,
|
|
404
|
+
projectId: proposal.project_id,
|
|
405
|
+
errorResponse,
|
|
406
|
+
});
|
|
407
|
+
if (!latestStyleguideSnapshot.ok) {
|
|
408
|
+
return latestStyleguideSnapshot.response;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (latestStyleguideSnapshot.snapshot.fingerprint !== proposal.styleguide_snapshot.fingerprint) {
|
|
412
|
+
return errorResponse(
|
|
413
|
+
"STYLEGUIDE_CHANGED_SINCE_PROPOSAL",
|
|
414
|
+
"Styleguide inputs changed after this proposal was created. Re-run propose_edit against the latest styleguide before commit.",
|
|
415
|
+
{
|
|
416
|
+
proposal_id,
|
|
417
|
+
project_id: proposal.project_id ?? null,
|
|
418
|
+
previous_fingerprint: proposal.styleguide_snapshot.fingerprint,
|
|
419
|
+
current_fingerprint: latestStyleguideSnapshot.snapshot.fingerprint,
|
|
420
|
+
next_step: "Call propose_edit again to regenerate this revision with current styleguide rules.",
|
|
421
|
+
}
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
170
426
|
try {
|
|
171
427
|
const proseWriteDiagnostics = getFileWriteDiagnostics(proposal.scene_file_path);
|
|
172
428
|
if (proseWriteDiagnostics.stat_error_code === "EACCES" || proseWriteDiagnostics.stat_error_code === "EPERM") {
|