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 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.2`; each source clone can prepare a local runtime under `releases/v1.8.2`, while npm installations run directly from package-root `dist/`.
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. The default remains backward compatible and reports mixed revisions
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
- `workingCopyMixed:true` while the commit remains valid. `baseRevision:null` means no single base
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
- mixedRevision,
1173
- ...(mixedRevision && payload.revision_range ? { revisionRange: payload.revision_range } : {}),
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 compactSafeCommit(payload) {
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
- if (payload.content_hashes !== undefined) {
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) {