svn-agent-mcp 1.0.0

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.
Files changed (165) hide show
  1. package/.editorconfig +12 -0
  2. package/.gitattributes +7 -0
  3. package/.github/workflows/ci.yml +39 -0
  4. package/.svn-mcp-policy.json +9 -0
  5. package/CHANGELOG.md +248 -0
  6. package/CONTRIBUTING.md +49 -0
  7. package/LICENSE +201 -0
  8. package/README.md +143 -0
  9. package/SECURITY.md +21 -0
  10. package/THIRD_PARTY_CHECKSUMS.txt +52 -0
  11. package/THIRD_PARTY_NOTICES.md +46 -0
  12. package/bin/SlikSvn-DB44-20-x64.dll +0 -0
  13. package/bin/SlikSvn-libapr-1.dll +0 -0
  14. package/bin/SlikSvn-libaprutil-1.dll +0 -0
  15. package/bin/SlikSvn-libcrypto-3-x64.dll +0 -0
  16. package/bin/SlikSvn-libintl.dll +0 -0
  17. package/bin/SlikSvn-libsasl21.dll +0 -0
  18. package/bin/SlikSvn-libssl-3-x64.dll +0 -0
  19. package/bin/SlikSvn-libsvn_client-1.dll +0 -0
  20. package/bin/SlikSvn-libsvn_delta-1.dll +0 -0
  21. package/bin/SlikSvn-libsvn_diff-1.dll +0 -0
  22. package/bin/SlikSvn-libsvn_fs-1.dll +0 -0
  23. package/bin/SlikSvn-libsvn_fs_base-1.dll +0 -0
  24. package/bin/SlikSvn-libsvn_fs_fs-1.dll +0 -0
  25. package/bin/SlikSvn-libsvn_fs_util-1.dll +0 -0
  26. package/bin/SlikSvn-libsvn_fs_x-1.dll +0 -0
  27. package/bin/SlikSvn-libsvn_ra-1.dll +0 -0
  28. package/bin/SlikSvn-libsvn_repos-1.dll +0 -0
  29. package/bin/SlikSvn-libsvn_subr-1.dll +0 -0
  30. package/bin/SlikSvn-libsvn_wc-1.dll +0 -0
  31. package/bin/System64/concrt140.dll +0 -0
  32. package/bin/System64/msvcp140.dll +0 -0
  33. package/bin/System64/msvcp140_1.dll +0 -0
  34. package/bin/System64/msvcp140_2.dll +0 -0
  35. package/bin/System64/msvcp140_atomic_wait.dll +0 -0
  36. package/bin/System64/msvcp140_codecvt_ids.dll +0 -0
  37. package/bin/System64/vccorlib140.dll +0 -0
  38. package/bin/System64/vcruntime140.dll +0 -0
  39. package/bin/System64/vcruntime140_1.dll +0 -0
  40. package/bin/dos2unix.exe +0 -0
  41. package/bin/engines/capi.dll +0 -0
  42. package/bin/libsvnjavahl-1.dll +0 -0
  43. package/bin/mac2unix.exe +0 -0
  44. package/bin/svn-populate-node-origins-index.exe +0 -0
  45. package/bin/svn.exe +0 -0
  46. package/bin/svnadmin.exe +0 -0
  47. package/bin/svnauthz-validate.exe +0 -0
  48. package/bin/svnauthz.exe +0 -0
  49. package/bin/svnbench.exe +0 -0
  50. package/bin/svndumpfilter.exe +0 -0
  51. package/bin/svnfsfs.exe +0 -0
  52. package/bin/svnlook.exe +0 -0
  53. package/bin/svnmucc.exe +0 -0
  54. package/bin/svnrdump.exe +0 -0
  55. package/bin/svnserve.exe +0 -0
  56. package/bin/svnsync.exe +0 -0
  57. package/bin/svnversion.exe +0 -0
  58. package/bin/unix2dos.exe +0 -0
  59. package/bin/unix2mac.exe +0 -0
  60. package/dist/envelope.d.ts +39 -0
  61. package/dist/envelope.d.ts.map +1 -0
  62. package/dist/envelope.js +137 -0
  63. package/dist/envelope.js.map +1 -0
  64. package/dist/eol.d.ts +15 -0
  65. package/dist/eol.d.ts.map +1 -0
  66. package/dist/eol.js +119 -0
  67. package/dist/eol.js.map +1 -0
  68. package/dist/guards.d.ts +26 -0
  69. package/dist/guards.d.ts.map +1 -0
  70. package/dist/guards.js +306 -0
  71. package/dist/guards.js.map +1 -0
  72. package/dist/index.d.ts +8 -0
  73. package/dist/index.d.ts.map +1 -0
  74. package/dist/index.js +221 -0
  75. package/dist/index.js.map +1 -0
  76. package/dist/parse/commitText.d.ts +2 -0
  77. package/dist/parse/commitText.d.ts.map +1 -0
  78. package/dist/parse/commitText.js +9 -0
  79. package/dist/parse/commitText.js.map +1 -0
  80. package/dist/parse/diffText.d.ts +7 -0
  81. package/dist/parse/diffText.d.ts.map +1 -0
  82. package/dist/parse/diffText.js +55 -0
  83. package/dist/parse/diffText.js.map +1 -0
  84. package/dist/parse/infoXml.d.ts +3 -0
  85. package/dist/parse/infoXml.d.ts.map +1 -0
  86. package/dist/parse/infoXml.js +35 -0
  87. package/dist/parse/infoXml.js.map +1 -0
  88. package/dist/parse/logXml.d.ts +10 -0
  89. package/dist/parse/logXml.d.ts.map +1 -0
  90. package/dist/parse/logXml.js +39 -0
  91. package/dist/parse/logXml.js.map +1 -0
  92. package/dist/parse/statusXml.d.ts +6 -0
  93. package/dist/parse/statusXml.d.ts.map +1 -0
  94. package/dist/parse/statusXml.js +64 -0
  95. package/dist/parse/statusXml.js.map +1 -0
  96. package/dist/parse/updateText.d.ts +6 -0
  97. package/dist/parse/updateText.d.ts.map +1 -0
  98. package/dist/parse/updateText.js +67 -0
  99. package/dist/parse/updateText.js.map +1 -0
  100. package/dist/runner.d.ts +41 -0
  101. package/dist/runner.d.ts.map +1 -0
  102. package/dist/runner.js +270 -0
  103. package/dist/runner.js.map +1 -0
  104. package/dist/tools/composite.d.ts +15 -0
  105. package/dist/tools/composite.d.ts.map +1 -0
  106. package/dist/tools/composite.js +238 -0
  107. package/dist/tools/composite.js.map +1 -0
  108. package/dist/tools/diagnose.d.ts +6 -0
  109. package/dist/tools/diagnose.d.ts.map +1 -0
  110. package/dist/tools/diagnose.js +131 -0
  111. package/dist/tools/diagnose.js.map +1 -0
  112. package/dist/tools/mutating.d.ts +80 -0
  113. package/dist/tools/mutating.d.ts.map +1 -0
  114. package/dist/tools/mutating.js +558 -0
  115. package/dist/tools/mutating.js.map +1 -0
  116. package/dist/tools/readonly.d.ts +52 -0
  117. package/dist/tools/readonly.d.ts.map +1 -0
  118. package/dist/tools/readonly.js +638 -0
  119. package/dist/tools/readonly.js.map +1 -0
  120. package/dist/tools/selfcheck.d.ts +5 -0
  121. package/dist/tools/selfcheck.d.ts.map +1 -0
  122. package/dist/tools/selfcheck.js +91 -0
  123. package/dist/tools/selfcheck.js.map +1 -0
  124. package/dist/types.d.ts +66 -0
  125. package/dist/types.d.ts.map +1 -0
  126. package/dist/types.js +2 -0
  127. package/dist/types.js.map +1 -0
  128. package/docs/README.md +11 -0
  129. package/docs/SPEC.md +964 -0
  130. package/docs/decisions/ADR-001-use-spec-v1-architecture.md +25 -0
  131. package/docs/decisions/ADR-002-use-capability-based-generic-svn-policy.md +31 -0
  132. package/docs/decisions/ADR-003-use-plug-and-play-global-registration.md +39 -0
  133. package/docs/decisions/ADR-004-borrow-diagnostics-not-external-mcp-surface.md +39 -0
  134. package/docs/svnrules.md +84 -0
  135. package/docs/toolrules.md +34 -0
  136. package/jest.config.cjs +14 -0
  137. package/package.json +51 -0
  138. package/scripts/clean.mjs +27 -0
  139. package/scripts/prepare-release.mjs +84 -0
  140. package/src/envelope.ts +182 -0
  141. package/src/eol.ts +137 -0
  142. package/src/guards.ts +362 -0
  143. package/src/index.ts +364 -0
  144. package/src/parse/commitText.ts +9 -0
  145. package/src/parse/diffText.ts +66 -0
  146. package/src/parse/infoXml.ts +51 -0
  147. package/src/parse/logXml.ts +64 -0
  148. package/src/parse/statusXml.ts +87 -0
  149. package/src/parse/updateText.ts +76 -0
  150. package/src/runner.ts +344 -0
  151. package/src/tools/composite.ts +289 -0
  152. package/src/tools/diagnose.ts +150 -0
  153. package/src/tools/mutating.ts +694 -0
  154. package/src/tools/readonly.ts +786 -0
  155. package/src/tools/selfcheck.ts +97 -0
  156. package/src/types.ts +76 -0
  157. package/tests/README.md +9 -0
  158. package/tests/diff-redaction.test.ts +146 -0
  159. package/tests/entrypoint.test.ts +49 -0
  160. package/tests/guards-eol.test.ts +170 -0
  161. package/tests/integration-svn.test.ts +709 -0
  162. package/tests/parsers.test.ts +119 -0
  163. package/tests/runner.test.ts +159 -0
  164. package/tests/selfcheck.test.ts +22 -0
  165. package/tsconfig.json +30 -0
package/docs/SPEC.md ADDED
@@ -0,0 +1,964 @@
1
+ # svn-agent — Generic Implementation Spec
2
+
3
+ **Spec version 1.17 — public implementation contract. Single source of truth.**
4
+ This document describes the current generic SVN MCP design without deployment-specific paths,
5
+ hostnames, or product-specific role assignments. Date: 2026-07-08.
6
+
7
+ **What this is:** one document containing the pain points, the resolution strategy, the full
8
+ architecture and tool contracts for a strict SVN MCP server, companion operational guidance, and
9
+ the historical development plan with verification gates. A maintainer should be able to implement
10
+ or review the server from this document and the source tree.
11
+
12
+ ---
13
+
14
+ ## 1. Pain points (why this exists)
15
+
16
+ Automated clients working SVN checkouts across multiple projects waste large amounts of time on SVN
17
+ housekeeping. The workflow usually has write-capable clients that may mutate SVN state and
18
+ read-only clients that may inspect but must not mutate. Deployments choose which client fills
19
+ each capability. The MCP must enforce permissions by configuration, not by product name.
20
+
21
+ Common SVN automation friction splits into three buckets:
22
+
23
+ **P1 — The client loop (dominant, ~70–80%).** A single commit-prep today costs 5–8 separate
24
+ shell calls (status → diff → EOL check → EOL fix → re-diff → commit → post-status). Every call
25
+ is a full model round trip; every shell call can hit a permission prompt, and in an unattended
26
+ session one prompt stalls the run until a human returns. Clients also re-derive the SVN policy
27
+ (exact diff flags, `-F` rule, never-commit list) from `docs/svnrules.md` every session —
28
+ repeated reasoning overhead, and each hand-composed command is a chance to get policy wrong.
29
+
30
+ **P2 — EOL damage that shouldn't exist.** Many Windows SVN source trees require CRLF +
31
+ `svn:eol-style=native`, no BOM. Automated file-creation tools can create new files with bare LF (`\n` in the
32
+ content string goes to disk as-is); shell heredocs/redirects do the same. The repo property only
33
+ normalizes at commit, so the working file stays LF, `svn diff` shows whole-file churn, and the
34
+ client burns a detect → `unix2dos` → re-diff loop. All of it is remediation for damage that is
35
+ preventable at write time. (Patch-based edits often preserve EOL; the damage paths are new-file writes and
36
+ shell output.)
37
+
38
+ **P3 — Raw `svn.exe` speed on Windows.** `svn status/diff` stat thousands of files plus the
39
+ `.svn` pristine store; Microsoft Defender real-time scanning taxes each of those file
40
+ operations (typically 2–10× on file-heavy svn work). Unscoped status/diff over the whole
41
+ working copy multiplies the cost.
42
+
43
+ ## 2. Resolution strategy (pain → fix mapping)
44
+
45
+ | Pain | Fix | Where in this doc |
46
+ |---|---|---|
47
+ | P1: 5–8 round trips per commit | Composite MCP tools: `svn_precommit` + `svn_commit` = 2 calls total | §8.3, §8.4 |
48
+ | P1: permission-prompt stalls | Allowlist read-only tools; only mutations prompt | §10.4 |
49
+ | P1: policy re-derivation + drift | Policy baked into the MCP as defaults & guards; read-only clients hard-READONLY | §7 |
50
+ | P1: raw-diff dumping into context | Structured JSON envelope, per-file ± counts, line-capped excerpts | §6.4, §8.3 |
51
+ | P2: LF files born from file-creation tools | Client write hook or MCP repair normalizes CRLF/no-BOM | §10.1 |
52
+ | P2: new files missing eol-style prop | Repo-root inherited `svn:auto-props` (SVN ≥1.8) | §10.2 |
53
+ | P2: remediation loop when it does happen | `eol_fix_verified`: fix + proof re-diff in one call | §8.3 |
54
+ | P3: Defender tax | Optional path exclusion for a chosen working-copy root (operator decision) | §10.3 |
55
+ | P3: unscoped commands | Tools require/target explicit paths structurally | §7 |
56
+
57
+ Rejected options (so maintainers do not re-litigate them): **forking an existing permissive SVN
58
+ MCP** (optional commit paths, dangerous update accept-modes, PATH/env setup, credentials in
59
+ environment variables; the safety layer is most of the code, so write from scratch). Safe ideas
60
+ from external projects may be borrowed only when they preserve this MCP's guard model; the current
61
+ baseline borrows the read-only diagnose/error-taxonomy pattern, not permissive mutating or
62
+ configuration semantics.
63
+ **`exclusive-locking=true`** in the svn runtime config
64
+ (write-capable clients, read-only clients, and GUI SVN tools may share the WC; exclusive SQLite locking
65
+ makes them error on each other); **pristine-less checkout `--store-pristine=no`** (needs SVN ≥1.15, and makes `diff` —
66
+ the hottest path — hit the network); **svn client upgrade** (no measurable win for this
67
+ workflow).
68
+
69
+ ## 3. Reference environment assumptions
70
+
71
+ - The full Windows SVN `bin` payload and full EOL converter `bin` payload are bundled under
72
+ `<MCP_HOME>\svn-agent\bin` and copied into every release under `releases\v<version>\bin`.
73
+ The reference implementation targets SVN 1.14+ behavior and should probe the exact client at
74
+ startup.
75
+ - Normal end-user configuration needs no environment variables and no project-specific `cwd`.
76
+ `SVN_AGENT_BIN_DIR`, `SVN_AGENT_SVN_PATH`, and `SVN_AGENT_DOS2UNIX_DIR` are development/test
77
+ overrides; PATH lookup is only a final fallback when the bundled tools are missing.
78
+ - Each tool call operates against a caller-provided `cwd` or an inferred working copy from absolute
79
+ paths. Relative paths require explicit per-call `cwd`. A single MCP registration may service many
80
+ SVN working copies on the same machine.
81
+ - Project text policy is discovered from SVN properties and local bytes. The default Windows
82
+ remediation target is CRLF + `svn:eol-style=native`, no BOM, but this must not hard-code one
83
+ repository's language or folder names.
84
+ - MCP installation home is chosen by the deployer, for example `<MCP_HOME>\svn-agent`.
85
+ - Node ≥ 20 is required.
86
+
87
+ ## 4. Locked decisions (no open questions)
88
+
89
+ | # | Decision | Rationale |
90
+ |---|---|---|
91
+ | D1 | Write from scratch in TypeScript/Node; do **not** fork an existing SVN MCP | Safety layer is the product; forking inherits a permissive surface |
92
+ | D2 | Read-only safety = launch with `--readonly` (legacy/dev env `SVN_AGENT_READONLY=1` also works); every mutating tool refuses | Simple, unbypassable, matches "read-only clients never change SVN state" |
93
+ | D3 | Mixed-revision WC on commit → **warn in `note`, proceed** | Caller decides; refusing blocks legitimate scoped commits |
94
+ | D4 | `riskAck:true` required for mechanically detectable risky slices (§7 G6); undetectable risk categories stay the calling client's approval-gate duty | Encodes the risky-slice gate without pretending to detect the undetectable |
95
+ | D5 | Branch/switch/merge/relocate/delete: **out of v0.1** | Not needed for daily flow; each is high-risk |
96
+ | D6 | Versioning: **semver**, first release `v0.1.0`; `current` junction → `releases\v0.1.0` | One pin, easy rollback |
97
+ | D7 | Prefer bundled `bin` tools; env overrides win; PATH is final fallback | Self-contained runtime, no client time spent locating tools |
98
+ | D8 | Commit message format checked, **warn not refuse** | Format is policy but judgment; a hard block would fight legitimate cases |
99
+ | D9 | Commit message via temp **`-F` file outside the WC**, never `-m` | Encodes the shared SVN policy |
100
+ | D10 | `svn_update` needs explicit `paths[]` or `updateAll:true`; always `--accept postpone` | Update is operator-gated; conflicts must surface, never auto-resolve |
101
+ | D11 | XML output (`--xml`) for status/info/log parsing; regex only where svn has no XML (diff, update, commit) | Locale-proof, stable parsing |
102
+ | D12 | ESM TypeScript, strict mode; deps only `@modelcontextprotocol/sdk`, `zod`, `fast-xml-parser` | Small, auditable |
103
+ | D13 | Server registered under the name **`svn`**; tools named `svn_*` / `eol_*` | Short, unambiguous |
104
+ | D14 | External SVN MCPs are reference material, not the base implementation | Borrow diagnostics/docs lessons; reject force flags, optional broad commits, shell execution, credential env vars, and repo-specific registration |
105
+
106
+ ## 5. Generic SVN policy inlined
107
+
108
+ These are restated here so the implementer does not need deployment-specific rule files:
109
+
110
+ 1. Scoped commands only; commit prep = scoped MCP `svn_status` + scoped MCP `svn_diff` on
111
+ intended paths. `svn_diff` owns the internal ignored-EOL diff command by default.
112
+ 2. `svn update` only on explicit operator request; **never** as a default preflight.
113
+ 3. Commit: message file + explicit file list — `svn commit -F <msgfile> <path1> <path2>`;
114
+ never bare inline `-m`. After commit, report the revision and clean scoped status.
115
+ 4. Risky slices (large, destructive, schema-changing, version-bumping, build-system-changing,
116
+ delete-heavy, security-sensitive, scope-unclear) stop for operator approval before commit.
117
+ 5. Read-only instances never commit, stage, revert, update, or change SVN state. They
118
+ may report the intended fix or commit plan for a write-capable client.
119
+ 6. EOL: preserve existing encoding/EOL; never normalize whole trees; EOL-only churn is not a
120
+ code change (check = empty MCP `svn_diff`); fix a single failing file with MCP
121
+ `eol_fix_verified`, which invokes `unix2dos`/`dos2unix` directly; never rewrite bytes via
122
+ PowerShell/in-process.
123
+ 7. For managed project working copies, never commit: `bin/`, `obj/`, `.vs/`, generated output,
124
+ `*.db`, `scratch/**`, secrets, keys, certificates, tool caches, or unrelated drive-by changes.
125
+ These guards are segment-aware so nested build output such as `src/App/bin/Debug/**` is also
126
+ blocked. A repository may version an optional `.svn-mcp-policy.json` to allow intentional
127
+ payloads; the MCP repository uses that to allow its root `bin/` runtime toolchain and
128
+ versioned release payloads without weakening normal project defaults.
129
+ 8. Commit message format:
130
+ ```
131
+ <short summary>
132
+
133
+ - <logical change group>
134
+ - <verification performed>
135
+ - <behavior impact, or "No behavior changes">
136
+ ```
137
+
138
+ ## 6. Architecture
139
+
140
+ ### 6.1 Process model & layout
141
+
142
+ Stdio MCP server, one process per client instance. Write-capable clients launch normally;
143
+ read-only clients launch with `--readonly`. Thin wrapper: every tool call launches `svn.exe` (or
144
+ `unix2dos`/`dos2unix`) through `execFile` or streaming `spawn` with `shell:false` — **no shell**,
145
+ no quoting pitfalls, and never an in-process rewrite of tracked file bytes.
146
+
147
+ ```
148
+ <MCP_HOME>\svn-agent\
149
+ src\
150
+ index.ts # server bootstrap, tool registration, READONLY gate
151
+ runner.ts # no-shell process wrappers: timeout, bundled-bin lookup, streaming, redaction
152
+ envelope.ts # Envelope type + builders (ok/fail)
153
+ guards.ts # G1–G7 guard framework (§7)
154
+ parse\
155
+ statusXml.ts, infoXml.ts, logXml.ts # --xml parsers (fast-xml-parser)
156
+ diffText.ts # unified-diff → per-file {added, removed}; lineLimit excerpting
157
+ updateText.ts # U/G/C/E line parser + "Summary of conflicts"
158
+ commitText.ts # /Committed revision (\d+)\./
159
+ eol.ts # byte sniffing (EOL kind, BOM, binary), dos2unix/unix2dos invocation
160
+ tools\
161
+ readonly.ts # svn_status, svn_info, svn_diff, svn_log, eol_check, svn_propget
162
+ composite.ts # svn_precommit, eol_fix_verified
163
+ mutating.ts # svn_add, svn_commit, svn_move, svn_rename, svn_copy,
164
+ # svn_update, svn_revert, svn_resolved, svn_cleanup,
165
+ # svn_propset_eol_style, svn_propset, svn_export, svn_import
166
+ tests\ # jest: unit + integration (temp file:// repo)
167
+ bin\ # versioned full Windows SVN and dos2unix runtime payloads
168
+ dist\ # tsc output (committed into releases\, not into src tree)
169
+ releases\v<version>\dist\index.js
170
+ releases\v<version>\bin\...
171
+ current -> releases\v<version> (directory junction)
172
+ docs\SPEC.md (this file)
173
+ package.json, tsconfig.json
174
+ ```
175
+
176
+ Launch write-capable clients: `node <MCP_HOME>\svn-agent\current\dist\index.js`.
177
+ Launch read-only clients: `node <MCP_HOME>\svn-agent\current\dist\index.js --readonly`.
178
+
179
+ ### 6.2 Environment variables
180
+
181
+ No environment variable is required for normal end-user operation. These variables are retained
182
+ only as development/test escape hatches:
183
+
184
+ | Var | Normal user? | Meaning |
185
+ |---|---:|---|
186
+ | `SVN_AGENT_READONLY` | No | Legacy/dev equivalent of `--readonly`; mutating tools return `ok:false`, `note:"READONLY instance"` |
187
+ | `SVN_AGENT_BIN_DIR` | No | Dev/test override for directory containing bundled svn/EOL tools |
188
+ | `SVN_AGENT_SVN_PATH` | No | Dev/test full path override for svn.exe |
189
+ | `SVN_AGENT_DOS2UNIX_DIR` | No | Dev/test directory override containing dos2unix.exe/unix2dos.exe |
190
+ | `SVN_AGENT_MAX_DIFF_LINES` | No | Dev/test default diff excerpt cap; tools also accept `lineLimit` |
191
+ | `SVN_AGENT_TIMEOUT_MS` | No | Dev/test per-process timeout |
192
+
193
+ ### 6.3 Path & cwd rules
194
+
195
+ Every tool accepts optional `cwd` (absolute). For plug-and-play global registration, clients do
196
+ not set a launch `cwd`; when a tool call supplies absolute paths and omits `cwd`, the MCP locates
197
+ the nearest SVN working copy for those paths. Relative paths still resolve against explicit `cwd`
198
+ when provided; without `cwd` or absolute path hints, the call is refused. Resolved paths **must stay inside one working
199
+ copy root** (found via `.svn` ancestor discovery + `svn info --xml`) — anything outside → guard
200
+ refusal. Empty `paths: []` where paths are required → refusal (`note:"explicit paths required"`),
201
+ never silently `.`.
202
+
203
+ ### 6.4 Response envelope (every tool, success and failure)
204
+
205
+ ```ts
206
+ interface Envelope {
207
+ ok: boolean;
208
+ command: string; // argv joined for display; credentials redacted (flags, URL userinfo/query secrets)
209
+ cwd: string;
210
+ revision: number | null; // resulting/queried revision when meaningful
211
+ changed_paths: { status: string; path: string }[];
212
+ conflicts: { path: string; type: "text" | "tree" | "prop" }[];
213
+ stdout_summary: string; // capped (200 lines default); "truncated" set if capped
214
+ stderr_summary: string;
215
+ truncated: boolean;
216
+ note: string; // one-liner: guard fired / warning / verdict
217
+ }
218
+ ```
219
+
220
+ Tools add tool-specific fields beside these (documented per tool). Errors are always envelopes
221
+ (`ok:false`), never thrown raw stacks. Redaction applies to `command`, `stdout_summary`, and
222
+ `stderr_summary`: `--password`/`--username` values, inline `--password=...`/`--username=...`
223
+ values, URL userinfo (including malformed userinfo with raw `@`), and sensitive URL query
224
+ parameters are replaced with `***` (the server itself never passes credentials; svn uses its
225
+ cached auth).
226
+
227
+ ### 6.5 Startup probe
228
+
229
+ All SVN child processes run without a shell, with `--non-interactive`, a stable `C` locale, bounded
230
+ stderr capture, timeout settlement, and latin1 fallback for non-UTF8 bytes. On boot: resolve bundled-or-overridden svn (`--version --quiet`), resolve bundled-or-overridden
231
+ dos2unix/unix2dos (`--version`), detect READONLY. Failures don't kill the server — the affected
232
+ tools return `ok:false` with an explanatory `note` (e.g. `eol_*` unavailable when dos2unix
233
+ missing).
234
+
235
+ ### 6.6 Error taxonomy (svn stderr → structured notes)
236
+
237
+ | svn error | Mapping |
238
+ |---|---|
239
+ | `E155004`/`E155036` (WC locked) | `note:"working copy locked - run svn_cleanup"` |
240
+ | `E170001`/`E215004`/auth | `note:"authentication failed - fix svn cached auth outside the MCP"` |
241
+ | `E175002`/connection failures | `note:"network or repository connection failed"` |
242
+ | `E155007` (not a WC) | `note:"path is not inside a working copy"` |
243
+ | `E135000` / inconsistent EOL | `note:"inconsistent line endings - run eol_fix_verified on affected files"` plus EOL diagnostics where available |
244
+ | `E200009` / unversioned target | `note:"target not versioned"` |
245
+ | `E200030`/SQLite database failures | `note:"working copy database problem - run svn_cleanup"` |
246
+ | timeout | `ok:false`, `note:"svn timed out after <ms>"`, process killed |
247
+ | non-UTF8 output bytes | decode lossy (`latin1` fallback), never crash |
248
+
249
+ ## 7. Guard framework (applies across tools)
250
+
251
+ - **G1 explicit targets:** mutating path-list tools require non-empty `paths[]`; source/destination tools require explicit `src` and `dest` (exceptions: `svn_update` with `updateAll:true`; `svn_cleanup` takes one `path`). No implicit `.`, no recursive default anywhere.
252
+ - **G2 READONLY:** `--readonly` (or legacy/dev `SVN_AGENT_READONLY=1`) → all §8.4 tools + `eol_fix_verified` refuse.
253
+ - **G3 WC containment:** resolved paths must be inside the working copy (§6.3).
254
+ - **G4 never-commit globs** (block in `svn_add`, `svn_move`, `svn_rename`, `svn_copy`, and `svn_commit`; case-insensitive, match on repo-relative path): `**/bin/**`, `**/dist/**`, `**/node_modules/**`, `**/coverage/**`, `**/obj/**`, `**/.vs/**`, `**/.cache/**`, `**/*.db`, `**/*.tsbuildinfo`, `scratch/**`, `packages/**`, `tags/**`, `.graphify/**`, `graphify-out/**`, `**/*.pfx`, `**/*.key`, `**/*.pem`, `**/*.p12`, `**/*.snk`, `**/.env*`. Optional repo-local `.svn-mcp-policy.json` may add strict allow/deny exceptions, for example to version a toolchain payload in the MCP repository itself. ("Unrelated drive-by changes" cannot be a glob — mitigated by G1 + G5; stays agent judgment.)
255
+ Policy shape:
256
+ ```json
257
+ { "neverCommit": { "allow": ["bin/**"], "deny": ["custom-generated/**"] } }
258
+ ```
259
+ Defaults stay strict when no policy file is present; policy is read from the working-copy root and never from an environment variable. Policy files are cached by working-copy root and mtime/size, and malformed or pathological policy globs fail with a `policy-error:` guard note.
260
+ Repository-local `deny` rules are evaluated before repository-local `allow` rules, so a broad
261
+ allow exception cannot bypass a stricter project-specific deny.
262
+ - **G5 must-be-changed:** `svn_commit` verifies every listed path is actually modified/added/deleted per scoped status; unknown/clean path → refusal naming the path.
263
+ - **G6 risky-slice ack:** `svn_commit` requires `riskAck:true` when any mechanical signal is present: a delete-scheduled path (status `D`), **more than 8 paths**, `version.ver` among the paths, or a build-system file among the paths (`*.sln`, `*.csproj`, `Directory.Build.props`, `Directory.Build.targets`, `*.props`, `*.targets`, `packages.config`). Refusal lists the triggered signals. Schema-changing / security-sensitive / scope-unclear risk is **not detectable** — the calling client's responsibility (§5.4).
264
+ - **G7 no dangerous flags:** `--force` is never emitted. `svn_update` always gets `--accept postpone`. `svn_cleanup` never gets `--remove-unversioned`/`--remove-ignored`/`--vacuum-pristines`. `svn_resolved` requires an explicit `accept` value from the caller.
265
+
266
+ ## 8. Tool contracts
267
+
268
+ All inputs validated with zod; all outputs = Envelope + the extra fields listed. "argv" shows
269
+ the exact svn invocation (before path resolution).
270
+
271
+ ### 8.1 Read-only tools (allowed under READONLY)
272
+
273
+ **`svn_status`** — `{ cwd?, paths?: string[] }`
274
+ argv: `svn status --xml [--no-ignore] [paths…]` (default target: `.` of explicit `cwd`;
275
+ when both `cwd` and absolute path hints are absent, the MCP refuses instead of falling back to
276
+ its launch directory). Inputs also accept
277
+ `includeIgnored?: boolean` for explicit ignored-path audits and `hideNoise?: boolean` to remove
278
+ common local runtime clutter (`node_modules`, `dist`, `current`, `.cache`, `coverage`)
279
+ from `changed_paths` while reporting filtered paths in `filtered_paths`. Parses `wc-status` into
280
+ `changed_paths` (status letters `M A D R C ? ! ~ I`, plus `_M` for property-only changes),
281
+ property conflicts into `{type:"prop"}`, and tree/text conflicts into `conflicts`.
282
+
283
+ **`svn_info`** — `{ cwd?, paths?: string[] }`
284
+ argv: `svn info --xml [paths…]`. Extra fields: `url`, `repo_root`, `wc_root`.
285
+ Mixed-revision detection: additionally run `svnversion <wc-root>` once; output containing
286
+ `:` → `mixed_revision:true` (+ `note`), suffix `M`→modified, `S`→switched, `P`→partial reported
287
+ in `note`. The MCP also returns `svnversion`, `revision_range:{min,max}`, `local_modifications`,
288
+ `switched`, `partial`, `remote_head_revision`, and `stale_base` so clients can distinguish a
289
+ mixed-revision working copy from dirty local edits.
290
+
291
+ **`svn_diff`** — `{ cwd?, paths: string[], ignoreEol?: boolean = true, lineLimit?: number = 800 }`
292
+ argv (default): `svn diff --internal-diff -x --ignore-eol-style <paths…>` — the generic
293
+ commit-prep standard. `ignoreEol:false` → `svn diff --internal-diff <paths…>` (raw, for EOL
294
+ diagnosis). Extra fields: `per_file: [{path, added, removed, binary}]` (parsed from unified
295
+ diff; `binary:true` when svn prints "Cannot display"), `diff_excerpt` (first `lineLimit`
296
+ lines), `truncated`, `ignore_eol:boolean`.
297
+
298
+ **`svn_log`** — `{ cwd?, paths?: string[], limit?: number = 10, verbose?: boolean = true }`
299
+ argv: `svn log --xml -l <limit> [-v] [targets…]`. For working-copy targets, the MCP
300
+ resolves target URLs and queries repository URLs at HEAD when possible. This avoids the common
301
+ mixed-revision working-copy peg problem where `svn log <wc-root>` only shows history through the
302
+ root directory's older BASE revision. If URL resolution fails, the MCP falls back to the original
303
+ working-copy paths. Extra: `entries: [{rev, author, date, msg, changed_paths}]`,
304
+ `target_mode: "repository-url"|"working-copy-path"`.
305
+
306
+ **`eol_check`** — `{ cwd?, paths: string[] }`
307
+ Pure read: batched `svn propget svn:eol-style --xml` + async byte sniff (cap 5 MB, larger →
308
+ `sniff:"skipped-too-large"`; NUL byte in first 8 KB → `kind:"binary"`; directory or other
309
+ non-file target → `kind:"not-a-file"`, never a thrown error). Extra per file:
310
+ `{ path, kind: "crlf"|"lf"|"mixed"|"none"|"binary"|"not-a-file", eol_style: string|null,
311
+ has_bom: boolean, mismatch: boolean }`. `mismatch` = text file whose working EOL does not match `svn:eol-style`
312
+ (`native` resolves to platform-native CRLF on Windows and LF elsewhere; explicit `LF`/`CRLF`
313
+ resolve literally). Files with no line breaks (`kind:"none"`) are not mismatches.
314
+
315
+ **`svn_propget`** — `{ cwd?, paths: string[], name: string }`
316
+ Pure read: `svn propget <name> --xml <paths…>`. Property names are bounded to ordinary SVN
317
+ property-name characters (`A-Za-z0-9_.:-`, starting with a letter/underscore). Extra:
318
+ `properties:[{path,name,value}]` and `missing_paths:string[]`; absent properties are
319
+ successful reads with missing targets listed in `missing_paths`, not fatal SVN failures.
320
+ When a batch has mixed presence, `properties` contains the found values and `missing_paths`
321
+ contains only the absent targets. Purpose:
322
+ let clients inspect property-only slices without dropping to raw SVN or special-casing only
323
+ `svn:eol-style`.
324
+
325
+ **`svn_self_check`** — `{ cwd? }`
326
+ Pure read/self-diagnostic tool. Reports MCP package/runtime version, `current` junction target,
327
+ whether `current` matches the package version, release `bin` and `dist` payload counts, startup
328
+ probe results for bundled tools, whether the bundled SVN/EOL toolchain is healthy, and whether
329
+ release/clean scripts use the Node-based paths. Purpose: avoid manual checks for ignored `current`
330
+ drift and noisy release payload adds.
331
+
332
+ **`svn_diagnose`** — `{ cwd?, paths?: string[] }`
333
+ Pure read working-copy diagnostic tool. Runs startup SVN availability, then local status, remote
334
+ status with `--show-updates`, `svn info -r HEAD`, and latest-log reachability checks against the
335
+ resolved working copy/targets in parallel. Extra fields: `health:"healthy"|"warning"|"error"`,
336
+ `svn_available`, `working_copy_valid`, `wc_root`, `remote_accessible`,
337
+ `checks:[{name, ok, command, note}]`, and `suggestions:string[]`. Purpose: collapse common
338
+ SVN failure triage into one structured call without changing the working copy or clearing
339
+ credentials.
340
+
341
+ ### 8.2 (reserved)
342
+
343
+ ### 8.3 Composite tools (the P1 killers)
344
+
345
+ **`svn_precommit`** — `{ cwd?, paths: string[], lineLimit?: number = 800 }` *(read-only; allowed under READONLY)*
346
+ One call = scoped status + scoped ignore-EOL diff + `eol_check` + G4/G5/G6 dry evaluation +
347
+ mixed-revision check. Extra fields:
348
+
349
+ ```jsonc
350
+ {
351
+ "verdict": "READY" | "EOL_FIX_NEEDED" | "GUARD_BLOCKED" | "NOTHING_TO_COMMIT" | "DIFF_FAILED",
352
+ "per_file": [{ "path": "...", "status": "M", "added": 12, "removed": 3,
353
+ "eol": "crlf", "eol_style": "native", "bom": false,
354
+ "pure_eol_churn": false, "guard": null }],
355
+ "risk_signals": ["build-system file touched"], // what svn_commit will demand riskAck for
356
+ "diff_excerpt": "...", "truncated": true
357
+ }
358
+ ```
359
+
360
+ Verdict rules (first match): any G3/G4 hit, ignored path, or listed-but-clean path → `GUARD_BLOCKED` (offender
361
+ named in `guard`); `svn_diff` failure with `recovery_tool:"eol_fix_verified"` →
362
+ `EOL_FIX_NEEDED`; other `svn_diff` failure → `DIFF_FAILED`; any text file with `mismatch` or
363
+ `pure_eol_churn` → `EOL_FIX_NEEDED`; no path with a real change → `NOTHING_TO_COMMIT`; else
364
+ `READY`. `pure_eol_churn` = file shows as modified in status but its ignore-EOL diff is empty.
365
+ Intended flow: **precommit → (review summary; fetch full per-file diff only if a count looks
366
+ wrong) → commit.** Two round trips.
367
+
368
+ **`eol_fix_verified`** — `{ cwd?, path: string, target?: "crlf"|"lf", removeBom?: boolean = true, dryRun?: boolean = false, allowLarge?: boolean = false }` *(mutating; refused under READONLY)*
369
+ One call = read `svn:eol-style`, infer the target (`native` → platform native, `LF` → lf,
370
+ `CRLF` → crlf, no property → platform native), execute the real converter via `execFile`
371
+ (`unix2dos` for crlf / `dos2unix` for lf; `--remove-bom` when `removeBom`) on **one file**,
372
+ then automatically re-run the ignore-EOL diff on it. Clients normally pass only `{path}` when
373
+ using absolute paths; `cwd` is optional and mainly for relative paths.
374
+ Extra: `{ before: {kind, has_bom}, after: {kind, has_bom}, target, eol_style, converter,
375
+ verification_command, diff_ignored_eol:true, pure_eol_churn: boolean }` —
376
+ `pure_eol_churn:true` is the proof the fix changed nothing but line endings. `dryRun:true`
377
+ reports `before` + inferred converter/target, touches nothing. Never invoked implicitly by any
378
+ other tool (fixing is always an explicit caller decision). Missing paths, non-files, binary files,
379
+ and `sniff:"skipped-too-large"` files return structured refusals; oversized files require
380
+ explicit `allowLarge:true`. No PowerShell scripts, byte rewrites, pipes, redirects, or shell
381
+ quoting are involved.
382
+
383
+ ### 8.4 Mutating tools (all refused under READONLY)
384
+
385
+ **`svn_add`** — `{ cwd?, paths: string[], allowRecursive?: boolean = false }`
386
+ argv: `svn add --parents --depth empty <paths…>` (files); intermediate parent directories are
387
+ scheduled as needed without recursively adding siblings. A directory path requires
388
+ `allowRecursive:true` (then `--parents --depth infinity`). G4 enforced — can't add what may never be committed
389
+ (`scratch/**` is reserved for local scratch files; never add).
390
+
391
+ **`svn_commit`** — `{ cwd?, paths: string[], message: string, riskAck?: boolean = false }`
392
+ Sequence: G1→G6 checks → message format check against §5.8 template (summary line + blank +
393
+ ≥1 `- ` bullet; deviation → warning appended to `note`, not refusal) → write message to temp
394
+ file **outside the WC** (secure temp dir, UTF-8 **no BOM**, leading BOM stripped) → argv:
395
+ `svn commit -F <tmpfile> --depth empty <paths…>` → delete tmpfile (always, incl. on failure) →
396
+ parse `Committed revision N.` → run scoped `svn status --xml <paths…>`.
397
+ If an explicit file path is under newly-added parent directories, the commit argv includes only
398
+ those scheduled-added ancestors plus the explicit path, so the caller does not need to name parent
399
+ directories manually.
400
+ Extra: `{ revision, post_status_clean: boolean, risk_signals: string[] }`. Mixed-revision WC →
401
+ warning in `note`, commit proceeds (D3).
402
+
403
+ **`svn_move`** — `{ cwd?, src: string, dest: string }`
404
+ argv: `svn move --parents <src> <dest>`. Working-copy path → working-copy path only; repository
405
+ URL forms are refused because URL moves create revisions immediately and require message-file
406
+ handling. `src` must exist and both `src` and `dest` must resolve inside the working copy.
407
+ Intermediate destination directories are created/scheduled by SVN. G4 enforced on both `src` and
408
+ `dest`. Extra: `{ operation:"move", src, dest }` plus scoped `changed_paths` for review/commit.
409
+ Committing a move normally requires both old and new paths; because the old path is scheduled
410
+ delete, `svn_commit` requires `riskAck:true` by G6.
411
+
412
+ **`svn_rename`** — `{ cwd?, src: string, dest: string }`
413
+ Alias for `svn_move`, registered separately so clients can use the natural verb when doing a
414
+ rename. Same argv, guards, and response shape as `svn_move`.
415
+
416
+ **`svn_copy`** — `{ cwd?, src: string, dest: string }`
417
+ argv: `svn copy --parents <src> <dest>`. Working-copy path → working-copy path only; repository
418
+ URL forms are refused. `src` must exist and both paths must be inside the working copy.
419
+ Intermediate destination directories are created/scheduled by SVN. G4 enforced on both `src` and
420
+ `dest`. Extra: `{ operation:"copy", src, dest }` plus scoped `changed_paths`.
421
+
422
+ **`svn_update`** — `{ cwd?, paths?: string[], updateAll?: boolean = false }`
423
+ Refuses unless `paths` non-empty or `updateAll:true` (deliberate friction; the operator-request
424
+ requirement in §5.2 remains the caller's responsibility). argv:
425
+ `svn update --accept postpone [paths…]`. Parses multi-column update output + "Summary of conflicts" →
426
+ `changed_paths` + `conflicts`; any conflict ⇒ prominent `note`. Never auto-resolves.
427
+
428
+ **`svn_revert`** — `{ cwd?, paths: string[], allowRecursive?: boolean = false, dryRun?: boolean = true }`
429
+ `dryRun:true` (default) = preview: returns scoped status + per-file ± counts of what would be
430
+ **lost**, changes nothing. `dryRun:false` → argv `svn revert <file paths…>` for files and a
431
+ separate `svn revert --depth infinity <directory paths…>` for directories; a directory or `.`
432
+ requires `allowRecursive:true`. Reverting the WC root path is refused unconditionally.
433
+
434
+ **`svn_resolved`** — `{ cwd?, path: string, accept: "working"|"mine-full"|"theirs-full"|"base" }`
435
+ argv: `svn resolve --accept <accept> <path>`. Single path; `accept` has **no default** — the
436
+ caller must state the resolution. Intended only after an operator asked for conflict resolution.
437
+
438
+ **`svn_cleanup`** — `{ cwd?, path?: string }`
439
+ argv: `svn cleanup [path]` — releases stale WC locks (the `E155004` remedy). **Never** passes
440
+ `--remove-unversioned`, `--remove-ignored`, or `--vacuum-pristines`. Mutating classification
441
+ (refused under READONLY) because it rewrites WC metadata.
442
+
443
+ **`svn_propset_eol_style`** — `{ cwd?, paths: string[], style?: "native"|"LF"|"CRLF" = "native" }`
444
+ argv: `svn propset svn:eol-style <style> <paths…>`. Guard: each target must currently be
445
+ **missing or mismatched** on the prop (checked via propget first) — mass re-propset of
446
+ already-correct files is refused (preserve-existing rule, §5.6). Rarely needed once §10.2 lands.
447
+
448
+ **`svn_propset`** — `{ cwd?, paths: string[], name: string, value: string, riskAck?: boolean = false }`
449
+ argv: `svn propset <name> <value> <paths…>`. Guard: explicit existing paths inside one working
450
+ copy, READONLY refusal, never-commit target checks, bounded property names/values. `riskAck:true`
451
+ is required for high-risk properties that can hide or redirect repository behavior:
452
+ `svn:ignore`, `svn:global-ignores`, `svn:externals`, and `svn:auto-props`.
453
+
454
+ **`svn_export`** — `{ cwd?, src: string, dest: string, revision?: string }` /
455
+ **`svn_import`** — `{ cwd?, src: string, url: string, message: string }`
456
+ argv: `svn export [-r rev] <src> <dest>` / `svn import -F <tmpfile> <src> <url>`. Explicit
457
+ src+dest/url; `svn_export` validates revision strings before invoking SVN, and `svn_import`
458
+ scans the source tree for never-commit descendants before invoking SVN. `svn_import` uses the
459
+ same secure `-F` tempfile mechanics as commit. Purpose: MCP release packaging.
460
+
461
+ ## 9. Edge cases (defined so no doubts remain)
462
+
463
+ - `paths: []` where required → `ok:false`, `note:"explicit paths required"`.
464
+ - Nonexistent path → `ok:false`, naming the path (fail before spawning svn).
465
+ - Path outside WC root → G3 refusal.
466
+ - WC locked (`E155004`) on any tool → mapped note pointing at `svn_cleanup` (§6.6).
467
+ - Binary file in `svn_diff`/`eol_check` → flagged `binary`, never sniffed/converted; `eol_fix_verified` on a binary → refusal.
468
+ - File > 5 MB in `eol_check` → prop still reported, byte sniff skipped with note.
469
+ - Diff larger than `lineLimit` → excerpt + `truncated:true`; per-file counts always complete (counted while streaming, not from the excerpt).
470
+ - Two svn-agent instances on one WC → fine: svn handles concurrent readers; the only writer is the non-READONLY instance, and svn's own wc locking covers overlap.
471
+ - Message containing `"""`, backticks, non-ASCII → irrelevant: message goes through a file (`-F`), never a shell string. Process launches never use a shell, so there is no shell interpolation.
472
+ - Commit succeeds but post-status shows residue → `post_status_clean:false` + note (caller decides).
473
+ - svn prints warnings on stderr with exit 0 → `ok:true`, stderr preserved in `stderr_summary`.
474
+
475
+ ## 10. Companion fixes outside the MCP (part of the plan, not the server)
476
+
477
+ ### 10.1 EOL handling
478
+
479
+ EOL remediation belongs inside this MCP. Callers should call `eol_fix_verified` with a file path;
480
+ the MCP infers the target from `svn:eol-style`, runs bundled `unix2dos`/`dos2unix` directly via
481
+ `execFile`, and rechecks the ignored-EOL diff. Do not install or generate PowerShell EOL
482
+ hooks/scripts for this workflow.
483
+
484
+ ### 10.2 Repo-dictated auto-props — *one commit at the repository root, maintainer approval*
485
+
486
+ SVN ≥1.8 inherited property; every 1.8+ client then auto-applies on `svn add`, no client config.
487
+ This is an example policy; repositories should adapt patterns to their own text files:
488
+
489
+ ```
490
+ svn propset svn:auto-props "*.cs = svn:eol-style=native
491
+ *.xaml = svn:eol-style=native
492
+ *.csproj = svn:eol-style=native
493
+ *.config = svn:eol-style=native
494
+ *.props = svn:eol-style=native
495
+ *.targets = svn:eol-style=native
496
+ *.md = svn:eol-style=native
497
+ *.resx = svn:eol-style=native
498
+ *.ts = svn:eol-style=native
499
+ *.js = svn:eol-style=native
500
+ *.json = svn:eol-style=native" <REPOSITORY_ROOT>
501
+ svn commit -F <msgfile> <REPOSITORY_ROOT> # prop-only commit
502
+ ```
503
+
504
+ ### 10.3 Defender exclusion — *operator decision (security trade-off), admin shell*
505
+
506
+ ```powershell
507
+ Add-MpPreference -ExclusionPath '<WORKING_COPY_ROOT>'
508
+ ```
509
+
510
+ Measure before/after: `Measure-Command { svn status <PROJECT_ROOT>\src }`. Expected 2–10× on
511
+ file-heavy svn ops. Trade-off: files under the path are not scanned on access. No process-level
512
+ exclusions.
513
+
514
+ ### 10.4 Permission allowlist
515
+
516
+ Interim (before MCP): allow read-only `svn status`, `svn diff`, `svn info`, and `svn log`
517
+ commands in the client permission system.
518
+ Final (after Phase 4): allow `mcp__svn__svn_self_check`, `mcp__svn__svn_diagnose`,
519
+ `mcp__svn__svn_status`, `mcp__svn__svn_info`, `mcp__svn__svn_diff`, `mcp__svn__svn_log`,
520
+ `mcp__svn__eol_check`, `mcp__svn__svn_propget`, and `mcp__svn__svn_precommit`; leave every
521
+ mutating tool prompt-gated.
522
+
523
+ ## 11. Historical development phases
524
+
525
+ Phases 1-4 are complete in the shipped v1.0.0 baseline. This section remains as traceability
526
+ for why the implementation was built in this order and how future release phases should be
527
+ gated.
528
+
529
+ **Phase 0 — no-code quick wins** *(operator executes/approves; independent of the MCP)*
530
+ 0a Defender exclusion (§10.3) · 0b repo auto-props (§10.2) · 0c interim allowlist (§10.4).
531
+ Gate: Defender win measured with before/after `Measure-Command`; EOL repair is verified through
532
+ `eol_fix_verified`, not an external hook.
533
+
534
+ **Phase 1 — scaffold + read-only tools**
535
+ `package.json` (ESM, `"engines": {"node": ">=20"}`), `tsconfig` (strict, ES2022), deps per D12;
536
+ `runner`, `envelope`, `guards`, XML parsers; tools `svn_status`, `svn_info`, `svn_diff`,
537
+ `svn_log`, `eol_check`; startup probe.
538
+ Gate: jest unit tests green (guard matrix, envelope shape, parser fixtures incl. locale-odd and
539
+ truncated outputs); manual smoke of all five tools against a sample working copy (read-only).
540
+
541
+ **Phase 2 — composite tools**
542
+ `svn_precommit`, `eol_fix_verified`.
543
+ Gate: integration tests on a **throwaway temp repo** (`svnadmin create` + `file:///` checkout in
544
+ a temporary directory — never a production working copy): LF-damaged file → precommit `EOL_FIX_NEEDED` → fix →
545
+ `pure_eol_churn:true` → precommit `READY`. Unit tests for verdict precedence and per-file
546
+ counting.
547
+
548
+ **Phase 3 — mutating tools**
549
+ All §8.4 tools. Gate: temp-repo integration matrix — commit happy path (`-F` file used and
550
+ cleaned up; revision parsed; post-status clean), every guard refusal (G1–G7, incl. commit
551
+ without riskAck on a 9-file slice, revert of WC root refused, update without paths/updateAll
552
+ refused), READONLY instance refuses every mutating tool + `eol_fix_verified`. **No mutating
553
+ test ever touches a production working copy.**
554
+
555
+ **Phase 4 — release + registration**
556
+ `tsc` build → `npm run release:prepare` copies `dist` and source-tree `bin` to
557
+ `releases\v<version>\`, validates payload counts, and repoints the `current` junction without
558
+ PowerShell wildcard/copy commands → register:
559
+ write-capable client: `<client mcp add svn> node <MCP_HOME>\svn-agent\current\dist\index.js`;
560
+ read-only client config:
561
+ ```toml
562
+ [mcp_servers.svn]
563
+ command = "node"
564
+ args = ["<MCP_HOME>\\svn-agent\\current\\dist\\index.js", "--readonly"]
565
+ ```
566
+ Also: final allowlist (§10.4) and optional auto-props commit (§10.2, maintainer approved).
567
+ Gate: from a sample write-capable client session, `svn_precommit` on a touched path returns a
568
+ correct verdict; from a read-only client session, `svn_status` works and `svn_commit` refuses
569
+ with the READONLY note.
570
+
571
+ **Phase 5 — retire the manual workflow**
572
+ Slim `docs/svnrules.md` to "use the `svn` MCP tools; raw svn only where the MCP has no tool",
573
+ keeping the policy prose as the reference the MCP encodes. Maintainer-approved docs edit.
574
+ Gate: one full sample commit slice executed end-to-end through the MCP (precommit → commit),
575
+ 2 round trips, zero prompts on the read path.
576
+
577
+ ## 12. Historical definition of done (v0.1.0)
578
+
579
+ 1. All Phase 1–4 gates green; jest suite green; zero mutating-tool tests against production working copies.
580
+ 2. A sample working-copy slice committed via `svn_precommit` + `svn_commit` in 2 calls, with correct
581
+ revision + clean post-status in the envelope.
582
+ 3. Read-only instance demonstrably refuses mutating tools.
583
+ 4. EOL hook + (if approved) auto-props live → a week of normal work produces zero EOL
584
+ remediation loops.
585
+ 5. This SPEC.md updated only via a new version header (spec changes are deliberate, not drift).
586
+
587
+ ## 13. Out of scope / future
588
+
589
+ Branch, switch, merge, relocate, delete (v0.2+ candidates, each with its own guard
590
+ design); blame/annotate; lock/unlock; changelist support; any Git interop; any mass
591
+ reformatting, ever. Project build/test time can dominate total slice time, but it is not SVN
592
+ housekeeping — separate initiative.
593
+
594
+ ## 14. Change Log
595
+
596
+ The complete release history lives in `../CHANGELOG.md`. Spec-affecting changes:
597
+
598
+ ### Spec 1.17 / v1.0.0 — 2026-07-08
599
+
600
+ - Declares the first public open-source release as `1.0.0`.
601
+ - Documents the GitHub clone -> `npm install` -> `npm run prepare:local` setup path for automated clients.
602
+ - Keeps generated `releases/`, `current`, and root `dist/` ignored; only the source tree and root
603
+ bundled Windows runtime payload are versioned.
604
+
605
+ ### Spec 1.16 / v0.1.15 — 2026-07-08
606
+
607
+ - Adds generic working-copy property tools: read-only `svn_propget` and guarded `svn_propset`.
608
+ - Defines property guard boundaries: explicit paths, one working copy, READONLY refusal for
609
+ writes, never-commit target checks, bounded property names/values, and `riskAck` for high-risk
610
+ ignore/externals/auto-props properties.
611
+ - Keeps `svn_propset_eol_style` as the stricter EOL-specific shortcut.
612
+
613
+ ### Spec 1.15 / v0.1.14 — 2026-07-07
614
+
615
+ - Adds §15.7 CLI failsafe mode: on mechanical MCP failure (server down, runtime broken), callers
616
+ fall back to scoped raw svn CLI for the session under the same §5 policy; guard refusals are
617
+ explicitly not failures and must never be bypassed via CLI.
618
+ - `noteFromRun` flags executable-launch failures (`ENOENT`/`EACCES`/`EPERM`) as
619
+ "MCP svn runtime unavailable" with the failsafe hint; `svn_diagnose` adds the failsafe
620
+ suggestion when the bundled SVN toolchain is unavailable.
621
+
622
+ ### Spec 1.14 / v0.1.13 — 2026-07-07
623
+
624
+ - Fixes the critical junction-launch defect: the ESM launched-directly check now compares real
625
+ paths, so `node <MCP_HOME>\svn-agent\current\dist\index.js` (the documented registration)
626
+ actually starts the server instead of exiting silently.
627
+ - Update-output parsing accepts only structurally valid status lines, so informational trailers
628
+ ("Updated to revision N.", "At revision N.", "Updating '.':", "Restored ...") can no longer
629
+ appear as phantom changed paths.
630
+ - `eol_check`/`svn_precommit` return a structured `kind:"not-a-file"` for directory targets
631
+ instead of failing with a thrown filesystem error.
632
+ - Streamed stdout/stderr (the `svn_diff` hot path) now decode with the same latin1 fallback as
633
+ buffered output, honoring §6.5 for non-UTF8 bytes.
634
+ - Working-copy containment (G3) verifies physical paths, so junctions/symlinks under a working
635
+ copy cannot redirect tools to files outside it.
636
+ - Policy globs additionally cap total wildcard count to bound regex backtracking.
637
+ - The read-only working-copy probe no longer falls back to the MCP launch directory under any
638
+ input combination.
639
+
640
+
641
+
642
+ - Defines the full hardening pass: non-interactive stable-locale SVN execution, bounded
643
+ streaming stderr, timeout settlement, latin1 output fallback, stronger redaction, parser
644
+ correctness, ignored-path guards, secure message files, split recursive reverts, guarded import
645
+ source scanning, export revision validation, policy validation/caching, nullable working-copy
646
+ roots, and no ambiguous process-cwd fallback.
647
+ - Updates read-only performance contracts: batched EOL propget, async EOL sniffing, parallel
648
+ diagnostics, and reduced repeated `svn_info` process spawns.
649
+ - Records coverage for the hardening items.
650
+
651
+ ### Spec 1.12 / v0.1.11 — 2026-07-07
652
+
653
+ - Defines repository-local never-commit policy precedence: `deny` rules override broad `allow`
654
+ exceptions, while `allow` rules may still override the default generated-artifact guard set.
655
+ - Defines envelope summary redaction for `stdout_summary` and `stderr_summary`, matching command
656
+ redaction for URL userinfo and sensitive query parameters.
657
+ - Updates release references to v0.1.11 after the hardening fixes.
658
+
659
+ ### Spec 1.11 — 2026-07-07
660
+
661
+ - Updates project documentation guidance for the v0.1.10 shipped baseline.
662
+ - Adds `svn_self_check` and `svn_diagnose` to the read-only MCP allowlist guidance.
663
+ - Marks the implementation phase plan and v0.1.0 definition of done as historical traceability.
664
+ - Adds ADR-004 as the formal decision record for borrowing diagnostic ideas from external SVN MCPs
665
+ without adopting their mutating/configuration semantics.
666
+
667
+ ### Spec 1.10 / v0.1.10 — 2026-07-07
668
+
669
+ - Defines `svn_diagnose` as a read-only working-copy diagnostic tool for local status, remote
670
+ status, HEAD info, and latest-log reachability.
671
+ - Expands the SVN error taxonomy for `E215004` auth exhaustion, `E175002` network/repository
672
+ failures, `E155036` working-copy locks, and `E200030` SQLite working-copy database failures.
673
+ - Records the external SVN MCP comparison decision: borrow safe diagnostic patterns
674
+ and reject permissive commit/update/force/auth/shell/plain-text semantics.
675
+
676
+ ### Spec 1.9 / v0.1.9 — 2026-07-07
677
+
678
+ - Defines segment-aware never-commit guards plus optional repo-local `.svn-mcp-policy.json`
679
+ allow/deny exceptions, including recursive-add descendant scanning.
680
+ - Defines streamed `svn_diff` counting so per-file summaries remain complete when excerpts are
681
+ truncated.
682
+ - Defines `DIFF_FAILED` precommit verdict behavior and EOL-recoverable diff failure handling.
683
+ - Defines structured `eol_fix_verified` refusals for missing/non-file/binary/too-large targets
684
+ and the explicit `allowLarge` escape hatch.
685
+ - Defines URL userinfo and sensitive query-parameter redaction for command display.
686
+
687
+ ### Spec 1.8 / v0.1.8 — 2026-07-07
688
+
689
+ - Defines richer `svn_info` mixed-revision interpretation and remote HEAD/stale-base fields.
690
+ - Defines `svn_status` `hideNoise` and `includeIgnored` controls for daily noise reduction and
691
+ explicit review passes.
692
+ - Defines `svn_self_check` for release pointer, payload count, startup probe, and packaging
693
+ script health.
694
+ - Replaces remaining PowerShell clean behavior with Node-based cleanup.
695
+
696
+ ### Spec 1.7 — 2026-07-07
697
+
698
+ - Adds the SVN/Subversion pain-point matrix in §16, including which issues the MCP
699
+ already handles, which v0.1.x changes addressed, and which items remain workflow or future
700
+ tooling concerns.
701
+
702
+ ### Spec 1.6 / v0.1.7 — 2026-07-07
703
+
704
+ - Defines `svn_log` repository-URL-at-HEAD targeting for working-copy paths to avoid
705
+ mixed-revision root log gaps.
706
+ - Defines inconsistent-EOL diff recovery diagnostics that point callers to `eol_fix_verified`
707
+ instead of surfacing a generic SVN failure.
708
+ - Defines the Node-based `npm run release:prepare` release packaging path to avoid PowerShell
709
+ copy/junction friction.
710
+
711
+ ### Spec 1.5 — 2026-07-07
712
+
713
+ - Adds the plug-and-play operator guidance in §15, clarifying configuration, automatic
714
+ working-copy discovery, expected benefits, and known trade-offs.
715
+
716
+ ### v0.1.6 — 2026-07-07
717
+
718
+ - Expands G4 never-commit guards for common generated output and dependency/cache artifacts:
719
+ `dist/**`, `node_modules/**`, `coverage/**`, `.cache/**`, and `*.tsbuildinfo`.
720
+
721
+ ### v0.1.5 — 2026-07-07
722
+
723
+ - Defines plug-and-play global registration: no end-user environment variables and no
724
+ project-specific launch `cwd`.
725
+ - Defines working-copy inference from absolute path inputs so one MCP registration can serve
726
+ multiple SVN working copies.
727
+ - Defines `--readonly` as the normal read-only launch mode; `SVN_AGENT_READONLY=1` remains only as
728
+ a legacy/dev override.
729
+
730
+ ### v0.1.4 — 2026-07-07
731
+
732
+ - Defines the root `bin/` source-tree runtime payload and matching release `bin/` payload.
733
+ - Makes bundled SVN and EOL converter binaries the normal runtime path.
734
+
735
+ ### v0.1.3 — 2026-07-07
736
+
737
+ - Adds guarded `svn_move`, `svn_rename`, and `svn_copy` contracts.
738
+
739
+ ### v0.1.2 — 2026-07-07
740
+
741
+ - Makes ignored-EOL diffs and EOL repair MCP-owned through bundled converter binaries.
742
+
743
+ ### v0.1.1 — 2026-07-07
744
+
745
+ - Defines parent-directory handling for nested explicit file adds and commits.
746
+
747
+ ### v0.1.0 — 2026-07-07
748
+
749
+ - Establishes the generic TypeScript/Node stdio MCP architecture, tool families, guards, and
750
+ versioned release layout.
751
+
752
+ ## 15. Plug-and-play operating model
753
+
754
+ ### 15.1 Corrected requirement
755
+
756
+ The intended operating model is:
757
+
758
+ - The SVN MCP is configured once in each MCP-capable client.
759
+ - The MCP is not tied to one SVN repository, project, product, checkout, or launch directory.
760
+ - A machine may contain many unrelated SVN working copies; one MCP registration must serve all
761
+ of them.
762
+ - Normal users do not set environment variables.
763
+ - Environment variables exist only for development and testing this MCP.
764
+ - Clients should not spend turns locating SVN binaries, composing special diff flags, fixing EOL
765
+ by hand, or re-reading SVN policy for routine work.
766
+
767
+ The phrase "auto register itself when an existing SVN repository is located" means automatic
768
+ working-copy discovery after the MCP has been registered once globally. A stdio MCP server should
769
+ not rewrite arbitrary client configuration files at runtime. Client registration is a one-time
770
+ client setup step; repository selection happens per tool call.
771
+
772
+ ### 15.2 End-user configuration
773
+
774
+ Write-capable client:
775
+
776
+ ```json
777
+ {
778
+ "mcpServers": {
779
+ "svn": {
780
+ "command": "node",
781
+ "args": ["<MCP_HOME>\\svn-agent\\current\\dist\\index.js"]
782
+ }
783
+ }
784
+ }
785
+ ```
786
+
787
+ Read-only launch using the same generic server name:
788
+
789
+ ```json
790
+ {
791
+ "mcpServers": {
792
+ "svn": {
793
+ "command": "node",
794
+ "args": ["<MCP_HOME>\\svn-agent\\current\\dist\\index.js", "--readonly"]
795
+ }
796
+ }
797
+ }
798
+ ```
799
+
800
+ Do not set a project-specific launch `cwd`. Do not set normal-use environment variables. For
801
+ zero-friction multi-repository use, pass absolute paths to MCP tools. When absolute paths are
802
+ provided, the MCP finds the nearest SVN working copy root and runs the command there. Relative
803
+ paths remain supported, but they require an explicit per-call `cwd`.
804
+
805
+ ### 15.3 Environment-variable policy
806
+
807
+ No environment variable is required for normal end-user operation.
808
+
809
+ `SVN_AGENT_BIN_DIR`, `SVN_AGENT_SVN_PATH`, `SVN_AGENT_DOS2UNIX_DIR`,
810
+ `SVN_AGENT_TIMEOUT_MS`, `SVN_AGENT_MAX_DIFF_LINES`, and legacy `SVN_AGENT_READONLY` are reserved
811
+ for development, tests, diagnostics, and compatibility checks. They must not be required in
812
+ ordinary client setup because they add friction, make the MCP look project-specific, and invite
813
+ configuration drift between machines.
814
+
815
+ Readonly production use should prefer the explicit `--readonly` launch argument.
816
+
817
+ ### 15.4 What the MCP handles for clients
818
+
819
+ - Bundled SVN and EOL converter binaries, including required DLLs.
820
+ - Working-copy inference from absolute paths across multiple SVN checkouts.
821
+ - Scoped status, info, log, and diff commands.
822
+ - Rich mixed-revision interpretation that separates revision ranges, local modifications, remote
823
+ HEAD, and stale-base warnings.
824
+ - Status noise filtering and explicit ignored-path audit mode.
825
+ - Repository-URL-at-HEAD log targeting so mixed-revision working-copy roots still show current
826
+ repository history.
827
+ - Ignored-EOL diffs by default: `svn diff --internal-diff -x --ignore-eol-style`.
828
+ - Inconsistent-EOL diff diagnostics with `eol_fix_verified` as the recovery tool.
829
+ - `svn_precommit` as one structured call for status, ignored-EOL diff, EOL inspection, guards,
830
+ and mixed-revision warning.
831
+ - `svn_commit` with a temporary `-F` message file, explicit paths, guard checks, revision
832
+ parsing, and post-status.
833
+ - `svn_add` with parent-directory scheduling for explicit nested file paths.
834
+ - `svn_move`, `svn_rename`, and `svn_copy` with working-copy containment and parent handling.
835
+ - `eol_fix_verified` using bundled `unix2dos` or `dos2unix`, followed by an ignored-EOL diff
836
+ proof.
837
+ - Hard readonly mode for read-only clients.
838
+ - Never-commit guards for generated output, dependency/cache folders, secrets, and other
839
+ high-risk paths.
840
+ - Node-based release preparation through `npm run release:prepare`, avoiding PowerShell wildcard
841
+ and junction-copy pitfalls during MCP packaging.
842
+ - `svn_self_check` for checking the local `current` pointer, release payload counts, startup
843
+ probe, and packaging script health.
844
+ - `svn_diagnose` for one-call troubleshooting of local SVN health, remote reachability,
845
+ authentication failures, lock problems, and working-copy database failures.
846
+
847
+ ### 15.5 How it improves client speed and token use
848
+
849
+ The MCP reduces client work by turning repeated shell recipes into structured tool calls. Clients
850
+ no longer need to:
851
+
852
+ - Search for `svn`, `svnadmin`, `svnversion`, `dos2unix`, `unix2dos`, or their DLLs.
853
+ - Reconstruct SVN policy from rules files for every session.
854
+ - Hand-compose ignored-EOL diff commands.
855
+ - Dump large raw diffs into assistant context when a structured per-file summary is enough.
856
+ - Run separate status, diff, EOL check, and guard commands during every commit-prep loop.
857
+ - Manually create commit message files.
858
+ - Add missing parent directories before adding a nested file.
859
+ - Diagnose and repair common EOL churn with ad hoc PowerShell or byte rewrites.
860
+
861
+ The intended daily flow is:
862
+
863
+ 1. Client edits files.
864
+ 2. Client calls `svn_precommit` on the intended paths.
865
+ 3. Client reviews the structured result and asks for targeted diffs only when needed.
866
+ 4. Client fixes EOL through `eol_fix_verified` if required.
867
+ 5. Client calls `svn_commit` with explicit paths and a message when the slice is verified and
868
+ safe to commit.
869
+
870
+ For common slices this changes a 5-8 command shell loop into one or two MCP calls. That saves
871
+ model turns, reduces repeated command text, keeps diffs smaller, and makes unattended work less
872
+ likely to stall on avoidable prompts.
873
+
874
+ ### 15.6 Workflow improvement
875
+
876
+ One global MCP registration supports many SVN repositories on the same machine. Write-capable clients
877
+ can make guarded changes, while read-only clients use the same tool surface in `--readonly` mode
878
+ and cannot mutate SVN state. This gives both roles the same structured evidence without relying
879
+ on product-specific assumptions or project-specific configuration.
880
+
881
+ The workflow is safer because high-risk actions are explicit: updates need paths or
882
+ `updateAll:true`, revert defaults to dry-run, cleanup never removes unversioned files, URL
883
+ copy/move is refused, and commit checks enforce explicit paths plus mechanical risk signals.
884
+
885
+ ### 15.7 CLI failsafe mode
886
+
887
+ If the MCP itself fails mechanically, the caller falls back to scoped raw `svn` CLI **for the
888
+ rest of that session** instead of stalling.
889
+
890
+ **Triggers (mechanical failures only):**
891
+
892
+ - The `svn` MCP server is not registered, not running, or tool calls fail at the protocol level
893
+ (client-side tool errors, no envelope returned).
894
+ - Envelopes report the MCP runtime itself broken: `note` contains
895
+ `"MCP svn runtime unavailable"`, or `svn_diagnose`/`svn_self_check` report the bundled
896
+ toolchain unhealthy and unrecoverable in-session.
897
+ - The same read-only tool call fails twice consecutively for reasons that are clearly not
898
+ SVN-level errors (auth, network, locks are SVN-level — CLI would fail identically and is not
899
+ a remedy for them).
900
+
901
+ **Explicit non-triggers:** a guard refusal is a policy decision, not a failure. READONLY
902
+ refusals, never-commit hits, `riskAck` demands, explicit-paths refusals, and working-copy
903
+ containment refusals must **never** be retried through the CLI. A read-only instance
904
+ stays read-only in failsafe mode.
905
+
906
+ **Failsafe behavior:** the same policy in §5 applies, hand-executed:
907
+
908
+ - Scoped commands only; explicit paths; no whole-tree status/diff.
909
+ - Diff: `svn diff --internal-diff -x --ignore-eol-style <paths…>`.
910
+ - Commit: message file + explicit file list (`svn commit -F <msgfile> <path1> …`), never inline
911
+ `-m`; message file created outside the working copy.
912
+ - Never `--force`; updates only on operator request and with `--accept postpone`.
913
+ - The never-commit list (§7 G4) and risky-slice stops (§5.4) remain in force as caller judgment.
914
+ - EOL repair via `unix2dos`/`dos2unix` binaries (bundled `<MCP_HOME>\svn-agent\current\bin` if
915
+ reachable, otherwise PATH), never PowerShell byte rewrites.
916
+
917
+ **Exit:** failsafe lasts for the session. The caller reports that the MCP was unavailable so the
918
+ operator can repair it (`svn_self_check` / `svn_diagnose` once the server is back).
919
+
920
+ ### 15.8 Overheads and trade-offs
921
+
922
+ - A one-time MCP client registration is still required.
923
+ - A stdio MCP cannot safely rewrite every possible client configuration file at runtime.
924
+ - Bundled Windows runtime binaries make the source and release payloads larger.
925
+ - The bundled runtime is Windows-oriented; cross-platform packaging would need matching binaries
926
+ or a documented PATH fallback strategy for those platforms.
927
+ - Absolute paths give the best zero-`cwd` multi-repository behavior. Relative-path-only workflows
928
+ need explicit per-call `cwd`.
929
+ - The MCP reduces SVN housekeeping, but it does not remove the caller's responsibility to inspect
930
+ the requested scope, run project-specific tests, and avoid unrelated changes.
931
+
932
+ ## 16. Observed SVN/Subversion pain-point matrix
933
+
934
+ This matrix records SVN pain points that shaped the MCP design. The purpose is to keep future
935
+ work grounded in practical workflow friction, not abstract SVN theory.
936
+
937
+ | # | Pain point | How the MCP helps now | Pending / still human or future-tooling work |
938
+ |---:|---|---|---|
939
+ | 1 | Mixed-revision confusion: a working-copy root can be at an older BASE revision while children are newer. | `svn_info` reports parsed revision ranges, local modification flags, remote HEAD, and stale-base state; `svn_log` queries repository URLs at HEAD when possible. | Callers must still understand whether mixed revision is acceptable for the task. |
940
+ | 2 | `svn log <wc-root>` can stop at the root node's old peg revision and hide newer commits. | v0.1.7 resolves working-copy targets to repository URLs and returns `target_mode:"repository-url"`. | URL fallback can fail if `svn info` cannot resolve a URL; then the MCP returns `working-copy-path` mode. |
941
+ | 3 | Concurrent-client overlap: another actor may commit while local work exists; update can merge `G` files silently. | `svn_update` requires explicit paths or `updateAll:true` and always uses `--accept postpone`; status/conflicts are structured. | Semantic overlap still needs review by the operator or caller. |
942
+ | 4 | Unversioned files are easy to miss; `svn commit` does not include them automatically. | `svn_status` exposes `?` paths; `svn_precommit` blocks uncommittable paths; `svn_add` is explicit. | Caller must decide which unversioned files belong to the current slice. |
943
+ | 5 | SVN cannot add a child file under a brand-new unversioned parent directory without adding parents first. | `svn_add` uses `--parents --depth empty` for files and schedules needed parent dirs without adding siblings. | Recursive directory adds still require `allowRecursive:true`. |
944
+ | 6 | Commit scope ambiguity: no Git-style staging area; broad commits can include unrelated WIP. | Mutating tools require explicit paths; `svn_commit` verifies each path is changed/scheduled. | "Commit everything" remains unsafe unless the caller intentionally scopes all paths. |
945
+ | 7 | Partial slice commits create bookkeeping overhead when some work must remain uncommitted. | `svn_precommit`, scoped `svn_status`, scoped `svn_diff`, and explicit `svn_commit` paths support small slices. | The operator or caller still chooses slice boundaries. |
946
+ | 8 | Update-before-commit discipline matters when others are committing remotely. | `svn_update` is guarded and conflict-safe; `svn_info` reports remote HEAD and stale-base state. | The MCP does not force an update before every commit because some workflows deliberately avoid it. |
947
+ | 9 | Direct-to-trunk workflow means every bad commit lands immediately. | Guarded commit, risk signals, read-only mode, and explicit paths reduce accidental commits. | Branch/PR-like review remains outside SVN/MCP v0.1. |
948
+ | 10 | Old design file sprawl: many versioned variants make "current" unclear. | Never-commit guards reduce future generated clutter; scoped status/diff make touched files visible. | The MCP cannot infer canonical design intent; project docs or an archive map are needed. |
949
+ | 11 | Archiving or moving files is risky because external notes or checklists may reference exact paths. | `svn_move`/`svn_rename` are guarded, scoped, and report changed paths. | The MCP does not yet maintain a reference map or warn about out-of-repo links. |
950
+ | 12 | Noisy status from ignored local runtime folders and generated artifacts. | Never-commit guards block common generated/dependency/cache paths; `svn_status hideNoise:true` filters common local clutter. | Project-specific noise may need local conventions or future custom filters. |
951
+ | 13 | `svn status --no-ignore` is useful for audit but noisy for daily work. | Normal MCP status avoids `--no-ignore`; `includeIgnored:true` enables explicit ignored-path audits. | Callers must choose audit mode deliberately. |
952
+ | 14 | EOL problems can make `svn diff` fail instead of merely showing a messy diff. | `svn_diff` defaults to ignored-EOL internal diff; v0.1.7 returns EOL diagnostics and `recovery_tool:"eol_fix_verified"` on inconsistent EOL failures. | Files still need explicit repair through `eol_fix_verified`; the MCP will not mutate implicitly. |
953
+ | 15 | Editing tools can introduce LF into native-CRLF SVN files, causing diff/commit friction. | `eol_check` and `eol_fix_verified` detect and repair single files through bundled converters. | Preventing bad writes at source depends on editor/client behavior outside the MCP. |
954
+ | 16 | PowerShell byte rewrites or redirects are risky for tracked text files. | The MCP uses `unix2dos`/`dos2unix` binaries through `execFile`; docs forbid PowerShell EOL repair. | Callers should use MCP tools rather than ad hoc shell rewrites. |
955
+ | 17 | Raw `svn diff` command flags are easy to forget and waste tokens. | `svn_diff` owns `svn diff --internal-diff -x --ignore-eol-style` by default and returns structured summaries. | Full raw diffs may still be needed for detailed review. |
956
+ | 18 | SVN history lookup is clunkier than modern Git workflows. | `svn_log` returns structured XML-parsed entries and now avoids mixed-root log gaps. | Higher-level "what changed between these revisions" summaries are future tooling. |
957
+ | 19 | Versioned generated/test artifacts can become permanent repository weight if added accidentally. | Never-commit guards now block `dist/**`, `node_modules/**`, `coverage/**`, `.cache/**`, `*.tsbuildinfo`, secrets, and other risky paths. | Project-specific generated paths may need additional local guard rules later. |
958
+ | 20 | Adding bundled binary release payloads is noisy and easy to miscount. | `npm run release:prepare` validates release `dist` and `bin` counts before repointing `current`; `svn_self_check` reports counts. | SVN add output is still verbose for binary payloads. |
959
+ | 21 | Local `current` junction is intentionally ignored, so a clean SVN status does not prove the local runtime pointer is correct. | `svn_self_check` reports `current` target and whether it matches the package version. | None for normal use. |
960
+ | 22 | PowerShell wildcard/copy/junction command differences caused release-packaging hiccups. | `npm run release:prepare` and `npm run clean` are Node scripts with path containment checks. | None for MCP release/clean paths. |
961
+ | 23 | Message quoting and shell command construction are fragile for commits/imports. | `svn_commit` and `svn_import` use temporary UTF-8 `-F` message files and `execFile`, not shell strings. | Human-written raw SVN commits can still bypass this discipline. |
962
+ | 24 | Root-clean does not mean conceptually clean: a clean status can still represent mixed concerns in the last commit. | Post-commit scoped status proves no local residue; risk signals and explicit paths reduce mixed slices. | Conceptual scope review remains a human or caller responsibility. |
963
+ | 25 | Opaque SVN failures waste turns: auth exhaustion, server connection failures, WC locks, and WC database issues all look like generic command failures when raw stderr is fed back into an assistant transcript. | v0.1.10 adds `svn_diagnose` and expands `noteFromRun` so these classes produce structured notes and next-step suggestions. | Repository-specific server outages and credential fixes still happen outside the MCP. |
964
+ | 26 | Existing generic SVN MCPs contain useful diagnostics but may reintroduce friction or risk through PATH/env setup, credential env vars, force flags, optional broad commits, and shell/string command execution. | This MCP borrows the safe read-only diagnostic/error-taxonomy ideas while keeping bundled binaries, no normal-use env vars, no-shell execution, explicit path commits, and guarded mutating tools. | Future external comparisons should be treated as design input, not as a reason to fork or loosen guards. |