svn-agent-mcp 1.8.2 → 1.8.3
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 +19 -0
- package/README.md +11 -5
- package/dist/response.js +40 -6
- package/dist/response.js.map +1 -1
- package/dist/tools/composite.d.ts.map +1 -1
- package/dist/tools/composite.js +6 -10
- package/dist/tools/composite.js.map +1 -1
- package/dist/tools/mutating.d.ts.map +1 -1
- package/dist/tools/mutating.js +21 -6
- package/dist/tools/mutating.js.map +1 -1
- package/docs/MCP_API.json +1 -1
- package/docs/SPEC.md +26 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@ All notable changes to the SVN MCP are recorded here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.8.3] - 2026-09-07
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Refreshed compatible transitive dependencies in the development lockfile to resolve
|
|
12
|
+
the current fast-uri, qs, and browserslist audit findings.
|
|
13
|
+
- Automatic added-file EOL normalization now works alongside ordinary changes in the same
|
|
14
|
+
explicit scope. EOL-only proof applies to repaired files, and the entire precommit is rerun.
|
|
15
|
+
- EOL repair resolves exact file targets correctly when the caller uses a subdirectory cwd.
|
|
16
|
+
- Routine mixed-revision state no longer adds a commit/precommit warning; diagnostic state
|
|
17
|
+
remains in full output and failures retain stale-path evidence.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Compact commit receipts omit SHA-256 lists; use `fields:["contentHashes"]` or full output.
|
|
22
|
+
- Commit receipts carry line statistics from the revalidated precommit diff, without another
|
|
23
|
+
SVN call. Compact statistics include totals and at most 25 paths / 4 KiB of file entries;
|
|
24
|
+
`filesTruncated:true` signals omitted entries, available in full output.
|
|
25
|
+
|
|
7
26
|
## [1.8.2] - 2026-08-27
|
|
8
27
|
|
|
9
28
|
### Changed
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Strict SVN Model Context Protocol server for agent-safe status, diff, EOL diagnosis, precommit checks, and guarded SVN mutations.
|
|
4
4
|
|
|
5
|
-
The implementation contract lives in `docs/SPEC.md`. The current source release is `1.8.
|
|
5
|
+
The implementation contract lives in `docs/SPEC.md`. The current source release is `1.8.3`; each source clone can prepare a local runtime under `releases/v1.8.3`, while npm installations run directly from package-root `dist/`.
|
|
6
6
|
|
|
7
7
|
Requirements: Node.js 24.18.0 or newer, npm 11.16.0 or newer, Git, and access to the public npm registry. Windows uses the
|
|
8
8
|
bundled VisualSVN Apache Subversion command-line package and dos2unix payload. On macOS and Linux, `svn`, `svnversion`, `svnadmin`,
|
|
@@ -243,11 +243,10 @@ Release workflows can pin `svn_update` with an exact `revision`; it still requir
|
|
|
243
243
|
or `updateAll:true` and always postpones conflicts. Add `expectedRemoteHead` with a numeric revision
|
|
244
244
|
to refuse if repository HEAD moved since the caller's probe. Use
|
|
245
245
|
`svn_precommit requireUniformRevision:true` when a release handoff must not proceed from a
|
|
246
|
-
mixed-revision working copy.
|
|
247
|
-
without blocking ordinary precommit work.
|
|
246
|
+
mixed-revision working copy. Ordinary precommit work does not warn about mixed revisions.
|
|
248
247
|
|
|
249
248
|
Parallel agents can create a valid mixed-revision working copy. A commit receipt may report
|
|
250
|
-
`
|
|
249
|
+
`working_copy_mixed:true` in full mode while the commit remains valid. `baseRevision:null` means no single base
|
|
251
250
|
revision describes every committed path; use `baseRevisionRange` for the useful range. Treat mixed
|
|
252
251
|
state as evidence, not failure by itself. Check the separate out-of-date and conflict diagnostics
|
|
253
252
|
before deciding whether to stop.
|
|
@@ -388,7 +387,14 @@ Newly added text files have no `BASE`. For status `A`, the same safe mode uses a
|
|
|
388
387
|
`svn:eol-style` or `.svn-mcp-policy.json normalizeEol` target, verifies valid UTF-8/no BOM and
|
|
389
388
|
normalized-content identity, applies only that explicit file, and continues to issue the precommit
|
|
390
389
|
token in the same call. An explicit file property takes precedence over repository fallback policy;
|
|
391
|
-
the implementation never infers EOL policy from neighboring files.
|
|
390
|
+
the implementation never infers EOL policy from neighboring files. Added files can be normalized
|
|
391
|
+
alongside ordinary modifications in the same explicit scope, including calls from a subdirectory.
|
|
392
|
+
|
|
393
|
+
Compact commit receipts omit per-file hashes; request `fields:["contentHashes"]` or
|
|
394
|
+
`responseMode:"full"` when needed. Token-bound commits include `diffStat` from the validated
|
|
395
|
+
precommit diff: total added/removed lines and up to 25 paths / 4 KiB of file entries. Larger
|
|
396
|
+
scopes report `filesTruncated:true`; full output includes all available entries. Statistics
|
|
397
|
+
describe the checked content diff, ignoring EOL, and `complete:false` means counts are partial.
|
|
392
398
|
|
|
393
399
|
## Commands
|
|
394
400
|
|
package/dist/response.js
CHANGED
|
@@ -105,7 +105,7 @@ function shapePayload(tool, payload, mode, request) {
|
|
|
105
105
|
}
|
|
106
106
|
if (compactMode && tool === "svn_commit"
|
|
107
107
|
&& (payload.operation === "safe_commit" || payload.operation === "safe_commit_detail")) {
|
|
108
|
-
return compactSafeCommit(payload);
|
|
108
|
+
return compactSafeCommit(payload, request);
|
|
109
109
|
}
|
|
110
110
|
if (compactMode && (tool === "svn_prepare_commit" || (tool === "svn_commit" && payload.operation === "prepare_commit"))) {
|
|
111
111
|
return compactPrepareCommit(payload, request);
|
|
@@ -241,6 +241,7 @@ function receiptPayload(tool, payload, request) {
|
|
|
241
241
|
assignDefined(result, "scopeCheckUnavailableReason", payload.scope_check_unavailable_reason);
|
|
242
242
|
}
|
|
243
243
|
if (tool === "svn_commit") {
|
|
244
|
+
Object.assign(result, compactCommitDiffStat(payload));
|
|
244
245
|
assignDefined(result, "postStatusClean", payload.post_status_clean);
|
|
245
246
|
assignDefined(result, "postStatusScope", payload.post_status_scope);
|
|
246
247
|
const postStatusPaths = stringArray(payload.post_status_paths).slice(0, 100);
|
|
@@ -332,6 +333,7 @@ function applyFieldProjection(tool, payload, shaped, request) {
|
|
|
332
333
|
revision: payload.revision,
|
|
333
334
|
revisionRange: payload.revision_range,
|
|
334
335
|
mixedRevision: payload.mixed_revision,
|
|
336
|
+
contentHashes: recordArray(payload.content_hashes).slice(0, 100),
|
|
335
337
|
remoteHeadRevision: payload.remote_head_revision !== undefined
|
|
336
338
|
? payload.remote_head_revision
|
|
337
339
|
: payload.observed_remote_head,
|
|
@@ -360,6 +362,10 @@ function applyFieldProjection(tool, payload, shaped, request) {
|
|
|
360
362
|
&& projected.conflictCount === undefined) {
|
|
361
363
|
projected.conflictCount = source.conflictCount;
|
|
362
364
|
}
|
|
365
|
+
if (fields.includes("contentHashes") && recordArray(payload.content_hashes).length > 100) {
|
|
366
|
+
projected.contentHashCount = recordArray(payload.content_hashes).length;
|
|
367
|
+
projected.contentHashesTruncated = true;
|
|
368
|
+
}
|
|
363
369
|
return { ok: true, ...projected };
|
|
364
370
|
}
|
|
365
371
|
const ROOT_RELATIVE_PATH_ARRAY_KEYS = new Set([
|
|
@@ -1169,8 +1175,9 @@ function compactPrecommit(payload, request) {
|
|
|
1169
1175
|
? { rollbackFailedPaths, rollbackFailedPathCount: allRollbackFailedPaths.length,
|
|
1170
1176
|
...(allRollbackFailedPaths.length > rollbackFailedPaths.length ? { rollbackFailedPathsTruncated: true } : {}) }
|
|
1171
1177
|
: {}),
|
|
1172
|
-
|
|
1173
|
-
|
|
1178
|
+
...(!payload.ok || verdict === "REVISION_NORMALIZATION_NEEDED"
|
|
1179
|
+
? { mixedRevision, ...(payload.revision_range ? { revisionRange: payload.revision_range } : {}) }
|
|
1180
|
+
: {}),
|
|
1174
1181
|
...(payload.remediation ? { remediation: payload.remediation } : {}),
|
|
1175
1182
|
...(nextAction ? { nextAction } : {}),
|
|
1176
1183
|
...(guardFailures.length > 0 ? { guardFailures } : {}),
|
|
@@ -1271,7 +1278,31 @@ function compactPrepareReceipt(payload) {
|
|
|
1271
1278
|
...(!payload.ok && payload.note ? { note: payload.note } : {})
|
|
1272
1279
|
};
|
|
1273
1280
|
}
|
|
1274
|
-
function
|
|
1281
|
+
function compactCommitDiffStat(payload) {
|
|
1282
|
+
if (!payload.diff_stat || typeof payload.diff_stat !== "object")
|
|
1283
|
+
return {};
|
|
1284
|
+
const stat = payload.diff_stat;
|
|
1285
|
+
const allFiles = recordArray(stat.files);
|
|
1286
|
+
const files = [];
|
|
1287
|
+
let bytes = 0;
|
|
1288
|
+
for (const file of allFiles.slice(0, 25)) {
|
|
1289
|
+
const size = Buffer.byteLength(JSON.stringify(file), "utf8");
|
|
1290
|
+
if (bytes + size > 4096)
|
|
1291
|
+
break;
|
|
1292
|
+
files.push(file);
|
|
1293
|
+
bytes += size;
|
|
1294
|
+
}
|
|
1295
|
+
return { diffStat: {
|
|
1296
|
+
source: stat.source,
|
|
1297
|
+
complete: stat.complete,
|
|
1298
|
+
added: stat.added,
|
|
1299
|
+
removed: stat.removed,
|
|
1300
|
+
files,
|
|
1301
|
+
fileCount: allFiles.length,
|
|
1302
|
+
...(files.length < allFiles.length ? { filesTruncated: true } : {})
|
|
1303
|
+
} };
|
|
1304
|
+
}
|
|
1305
|
+
function compactSafeCommit(payload, request = {}) {
|
|
1275
1306
|
if (payload.operation === "safe_commit_detail") {
|
|
1276
1307
|
return {
|
|
1277
1308
|
ok: payload.ok,
|
|
@@ -1306,6 +1337,9 @@ function compactSafeCommit(payload) {
|
|
|
1306
1337
|
...(payload.detail_expires_at ? { detailExpiresAt: payload.detail_expires_at } : {}),
|
|
1307
1338
|
...(payload.detail_cursor ? { detailCursor: payload.detail_cursor } : {}),
|
|
1308
1339
|
...(payload.code ? { code: payload.code } : {}),
|
|
1340
|
+
...compactCommitDiffStat(payload),
|
|
1341
|
+
...(stringArray(request.fields).includes("contentHashes") && payload.content_hashes !== undefined
|
|
1342
|
+
? { contentHashes: recordArray(payload.content_hashes).slice(0, 100) } : {}),
|
|
1309
1343
|
...compactAutoEol(payload),
|
|
1310
1344
|
...(!payload.ok && payload.note ? { note: payload.note } : {})
|
|
1311
1345
|
};
|
|
@@ -1412,7 +1446,6 @@ function compactMutation(tool, payload, request) {
|
|
|
1412
1446
|
assignDefined(receipt, "baseRevisionRange", payload.base_revision_range);
|
|
1413
1447
|
assignDefined(receipt, "remoteHeadRevision", payload.remote_head_revision);
|
|
1414
1448
|
assignDefined(receipt, "eolVerdict", payload.eol_verdict);
|
|
1415
|
-
assignDefined(receipt, "workingCopyMixed", payload.working_copy_mixed);
|
|
1416
1449
|
assignDefined(receipt, "postStatusScope", payload.post_status_scope);
|
|
1417
1450
|
const postStatusPaths = stringArray(payload.post_status_paths).slice(0, 100);
|
|
1418
1451
|
if (payload.post_status_paths !== undefined)
|
|
@@ -1422,7 +1455,8 @@ function compactMutation(tool, payload, request) {
|
|
|
1422
1455
|
receipt.postStatusPathsTruncated = true;
|
|
1423
1456
|
assignDefined(receipt, "workingCopyClean", payload.working_copy_clean);
|
|
1424
1457
|
assignDefined(receipt, "precommitToken", payload.precommit_token);
|
|
1425
|
-
|
|
1458
|
+
Object.assign(receipt, compactCommitDiffStat(payload));
|
|
1459
|
+
if (payload.content_hashes !== undefined && stringArray(request.fields).includes("contentHashes")) {
|
|
1426
1460
|
receipt.contentHashes = recordArray(payload.content_hashes).slice(0, 100);
|
|
1427
1461
|
}
|
|
1428
1462
|
if (committedPaths.length > 100) {
|