@mseep/csv-editor 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 (106) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +53 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +38 -0
  3. package/.github/workflows/deploy-docs.yml +62 -0
  4. package/.github/workflows/publish-github.yml +52 -0
  5. package/.github/workflows/publish.yml +44 -0
  6. package/.github/workflows/test.yml +32 -0
  7. package/.pre-commit-config.yaml +157 -0
  8. package/ALTERNATIVE_PUBLISHING.md +175 -0
  9. package/ARCHITECTURE.md +1011 -0
  10. package/CHANGELOG.md +99 -0
  11. package/CODE_OF_CONDUCT.md +41 -0
  12. package/CONTRIBUTING.md +427 -0
  13. package/Dockerfile +22 -0
  14. package/LICENSE +21 -0
  15. package/MCP_CONFIG.md +505 -0
  16. package/PUBLISHING.md +210 -0
  17. package/README.md +400 -0
  18. package/SECURITY.md +61 -0
  19. package/docs/README.md +41 -0
  20. package/docs/blog/2019-05-28-first-blog-post.md +12 -0
  21. package/docs/blog/2019-05-29-long-blog-post.md +44 -0
  22. package/docs/blog/2021-08-01-mdx-blog-post.mdx +24 -0
  23. package/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
  24. package/docs/blog/2021-08-26-welcome/index.md +29 -0
  25. package/docs/blog/authors.yml +25 -0
  26. package/docs/blog/tags.yml +19 -0
  27. package/docs/docs/api/overview.md +183 -0
  28. package/docs/docs/installation.md +252 -0
  29. package/docs/docs/intro.md +87 -0
  30. package/docs/docs/tutorial-basics/_category_.json +8 -0
  31. package/docs/docs/tutorial-basics/congratulations.md +23 -0
  32. package/docs/docs/tutorial-basics/create-a-blog-post.md +34 -0
  33. package/docs/docs/tutorial-basics/create-a-document.md +57 -0
  34. package/docs/docs/tutorial-basics/create-a-page.md +43 -0
  35. package/docs/docs/tutorial-basics/deploy-your-site.md +31 -0
  36. package/docs/docs/tutorial-basics/markdown-features.mdx +152 -0
  37. package/docs/docs/tutorial-extras/_category_.json +7 -0
  38. package/docs/docs/tutorial-extras/img/docsVersionDropdown.png +0 -0
  39. package/docs/docs/tutorial-extras/img/localeDropdown.png +0 -0
  40. package/docs/docs/tutorial-extras/manage-docs-versions.md +55 -0
  41. package/docs/docs/tutorial-extras/translate-your-site.md +88 -0
  42. package/docs/docs/tutorials/quickstart.md +365 -0
  43. package/docs/docusaurus.config.ts +163 -0
  44. package/docs/package-lock.json +17493 -0
  45. package/docs/package.json +48 -0
  46. package/docs/sidebars.ts +33 -0
  47. package/docs/src/components/HomepageFeatures/index.tsx +71 -0
  48. package/docs/src/components/HomepageFeatures/styles.module.css +11 -0
  49. package/docs/src/css/custom.css +30 -0
  50. package/docs/src/pages/index.module.css +23 -0
  51. package/docs/src/pages/index.tsx +44 -0
  52. package/docs/src/pages/markdown-page.md +7 -0
  53. package/docs/static/.nojekyll +0 -0
  54. package/docs/static/img/docusaurus-social-card.jpg +0 -0
  55. package/docs/static/img/docusaurus.png +0 -0
  56. package/docs/static/img/favicon.ico +0 -0
  57. package/docs/static/img/logo.svg +1 -0
  58. package/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
  59. package/docs/static/img/undraw_docusaurus_react.svg +170 -0
  60. package/docs/static/img/undraw_docusaurus_tree.svg +40 -0
  61. package/docs/tsconfig.json +8 -0
  62. package/examples/README.md +48 -0
  63. package/examples/auto_save_demo.py +206 -0
  64. package/examples/auto_save_overwrite.py +201 -0
  65. package/examples/basic_usage.py +135 -0
  66. package/examples/demo.py +139 -0
  67. package/examples/history_demo.py +317 -0
  68. package/examples/test_default_autosave.py +124 -0
  69. package/examples/update_consignee_example.py +179 -0
  70. package/package.json +51 -0
  71. package/plans/2026-04-19-fastmcp3-migration-plan.md +1045 -0
  72. package/pyproject.toml +331 -0
  73. package/requirements-dev.txt +30 -0
  74. package/requirements.txt +22 -0
  75. package/scripts/publish.py +67 -0
  76. package/smithery.yaml +15 -0
  77. package/specs/2026-04-19-fastmcp3-migration-design.md +243 -0
  78. package/src/csv_editor/__init__.py +8 -0
  79. package/src/csv_editor/models/__init__.py +39 -0
  80. package/src/csv_editor/models/auto_save.py +246 -0
  81. package/src/csv_editor/models/csv_session.py +468 -0
  82. package/src/csv_editor/models/data_models.py +244 -0
  83. package/src/csv_editor/models/history_manager.py +456 -0
  84. package/src/csv_editor/prompts/__init__.py +0 -0
  85. package/src/csv_editor/prompts/data_prompts.py +13 -0
  86. package/src/csv_editor/resources/__init__.py +0 -0
  87. package/src/csv_editor/resources/csv_resources.py +22 -0
  88. package/src/csv_editor/server.py +640 -0
  89. package/src/csv_editor/tools/__init__.py +5 -0
  90. package/src/csv_editor/tools/analytics.py +700 -0
  91. package/src/csv_editor/tools/auto_save_operations.py +235 -0
  92. package/src/csv_editor/tools/data_operations.py +3 -0
  93. package/src/csv_editor/tools/history_operations.py +315 -0
  94. package/src/csv_editor/tools/io_operations.py +431 -0
  95. package/src/csv_editor/tools/transformations.py +663 -0
  96. package/src/csv_editor/tools/validation.py +822 -0
  97. package/src/csv_editor/utils/__init__.py +0 -0
  98. package/src/csv_editor/utils/validators.py +205 -0
  99. package/tests/README.md +65 -0
  100. package/tests/__init__.py +7 -0
  101. package/tests/conftest.py +50 -0
  102. package/tests/test_auto_save.py +378 -0
  103. package/tests/test_basic.py +103 -0
  104. package/tests/test_integration.py +356 -0
  105. package/tests/test_server_boot.py +50 -0
  106. package/tests/test_settings.py +184 -0
@@ -0,0 +1,1045 @@
1
+ # FastMCP 3 Migration + v2.0.0 Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Ship `csv-editor` v2.0.0 with Python 3.11 floor, FastMCP 3.2, non-breaking dep bumps, SSE transport removed, and automated test CI — as four sequential PRs against `main`.
6
+
7
+ **Architecture:** Phased rollout — each PR is independently reviewable, lands on `main`, and is green in CI before the next begins. No long-lived release branch. Four PRs total:
8
+ 1. Add CI test workflow
9
+ 2. Python floor 3.11 + non-breaking dep bumps
10
+ 3. FastMCP 2 → 3 migration + SSE removal
11
+ 4. CHANGELOG + version bump + v2.0.0 tag
12
+
13
+ **Tech Stack:** Python 3.11+, FastMCP 3.2, pydantic 2.13, pyarrow 23, pytest 8, uv, GitHub Actions.
14
+
15
+ **Spec:** [`specs/2026-04-19-fastmcp3-migration-design.md`](../specs/2026-04-19-fastmcp3-migration-design.md)
16
+
17
+ ---
18
+
19
+ ## File Structure
20
+
21
+ **New files:**
22
+ - `.github/workflows/test.yml` — pytest matrix workflow
23
+ - `tests/test_server_boot.py` — regression tests for server boot, tool registry, SSE rejection
24
+
25
+ **Modified files:**
26
+ - `pyproject.toml` — Python floor, classifiers, deps, tool target-versions, version bump
27
+ - `src/csv_editor/server.py` — argparse `--transport` choices, `health_check` version
28
+ - `README.md` — Python badge, remove 3.8/3.9 claims, remove SSE references
29
+ - `Dockerfile` — pin base image to `python:3.11-slim-bookworm`
30
+ - `MCP_CONFIG.md` — remove SSE references
31
+ - `CONTRIBUTING.md` — add `.venv` rebuild note
32
+ - `CHANGELOG.md` — new `[2.0.0]` section
33
+
34
+ **Unchanged (verify only):**
35
+ - `smithery.yaml` — uses stdio transport only; no SSE reference
36
+ - `.github/workflows/publish.yml`, `publish-github.yml`, `deploy-docs.yml` — unrelated
37
+ - `src/csv_editor/tools/*.py` — no code changes needed
38
+ - `src/csv_editor/models/*.py` — no code changes needed
39
+
40
+ ---
41
+
42
+ ## Pre-flight: Fix local dev environment
43
+
44
+ The maintainer's `.venv` points at a removed conda interpreter. This blocks local test runs. Fix once before starting.
45
+
46
+ ### Task 0: Rebuild local venv
47
+
48
+ **Files:** none (local only)
49
+
50
+ - [ ] **Step 1: Remove broken venv**
51
+
52
+ ```bash
53
+ rm -rf .venv
54
+ ```
55
+
56
+ - [ ] **Step 2: Verify uv is installed**
57
+
58
+ Run: `uv --version`
59
+ Expected: prints a version (e.g., `uv 0.5.x`). If not: `curl -LsSf https://astral.sh/uv/install.sh | sh`
60
+
61
+ - [ ] **Step 3: Sync dependencies**
62
+
63
+ Run: `uv sync --all-extras`
64
+ Expected: creates fresh `.venv/`, installs deps, exits 0.
65
+
66
+ - [ ] **Step 4: Verify tests can run**
67
+
68
+ Run: `uv run pytest tests/ --collect-only -q`
69
+ Expected: pytest collects test files without import errors. Record the total count in a scratch note (needed for PR 1 baseline).
70
+
71
+ - [ ] **Step 5: Run full suite to establish baseline**
72
+
73
+ Run: `uv run pytest tests/ -v`
74
+ Expected: tests execute; record PASS/FAIL counts. Any failures are pre-existing and not this plan's concern — they'll be tracked as a follow-up issue in Task 5.
75
+
76
+ No commit for Task 0 (local-only, `.venv` is gitignored).
77
+
78
+ ---
79
+
80
+ ## PR 1: CI Workflow Baseline
81
+
82
+ **Branch name:** `ci/add-test-workflow`
83
+ **Goal:** add pytest matrix GitHub Actions workflow that runs on every PR and push to main.
84
+
85
+ ### Task 1: Add test workflow file
86
+
87
+ **Files:**
88
+ - Create: `.github/workflows/test.yml`
89
+
90
+ - [ ] **Step 1: Create branch**
91
+
92
+ ```bash
93
+ git checkout main
94
+ git pull --ff-only origin main
95
+ git checkout -b ci/add-test-workflow
96
+ ```
97
+
98
+ - [ ] **Step 2: Write the workflow file**
99
+
100
+ Create `.github/workflows/test.yml` with:
101
+
102
+ ```yaml
103
+ name: test
104
+
105
+ on:
106
+ pull_request:
107
+ push:
108
+ branches: [main]
109
+
110
+ jobs:
111
+ pytest:
112
+ runs-on: ubuntu-latest
113
+ strategy:
114
+ fail-fast: false
115
+ matrix:
116
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
117
+ steps:
118
+ - uses: actions/checkout@v4
119
+
120
+ - name: Set up Python ${{ matrix.python-version }}
121
+ uses: actions/setup-python@v5
122
+ with:
123
+ python-version: ${{ matrix.python-version }}
124
+
125
+ - name: Install uv
126
+ uses: astral-sh/setup-uv@v4
127
+ with:
128
+ enable-cache: true
129
+
130
+ - name: Sync dependencies
131
+ run: uv sync --all-extras
132
+
133
+ - name: Run pytest
134
+ run: uv run pytest tests/ -v --tb=short
135
+ ```
136
+
137
+ - [ ] **Step 3: Commit**
138
+
139
+ ```bash
140
+ git add .github/workflows/test.yml
141
+ git commit -m "ci: add pytest matrix workflow (3.10-3.14 on ubuntu)"
142
+ ```
143
+
144
+ - [ ] **Step 4: Push and open PR**
145
+
146
+ ```bash
147
+ git push -u origin ci/add-test-workflow
148
+ ```
149
+
150
+ Then open a PR on GitHub with title `ci: add pytest matrix workflow` and body describing: "Adds a GitHub Actions workflow that runs pytest on Python 3.10–3.14 for every PR and push to main. Prerequisite for PR 2 (dep bumps) and PR 3 (FastMCP 3 migration)."
151
+
152
+ - [ ] **Step 5: Verify workflow triggers**
153
+
154
+ On the PR page, confirm a `test` check appears. Wait for matrix to complete.
155
+
156
+ Expected: All 5 matrix rows complete (green or red is both OK — the runner existing is the deliverable).
157
+
158
+ - [ ] **Step 6: Document baseline result**
159
+
160
+ If any rows are red, open a new GitHub issue titled "Pre-existing test failures on main" listing the failing tests per Python version. Link the issue in the PR description.
161
+
162
+ If all green: note that in the PR description ("baseline clean").
163
+
164
+ - [ ] **Step 7: Merge PR 1**
165
+
166
+ Merge via GitHub UI (squash merge). Confirm `main` now runs the workflow on subsequent pushes.
167
+
168
+ ---
169
+
170
+ ## PR 2: Python 3.11 Floor + Non-Breaking Dep Bumps
171
+
172
+ **Branch name:** `deps/python-311-and-bumps`
173
+ **Goal:** bump Python floor and non-breaking dependencies; update tooling target-versions; CI matrix drops 3.10.
174
+
175
+ ### Task 2: Create branch and bump Python floor in pyproject.toml
176
+
177
+ **Files:**
178
+ - Modify: `pyproject.toml`
179
+
180
+ - [ ] **Step 1: Create branch**
181
+
182
+ ```bash
183
+ git checkout main
184
+ git pull --ff-only origin main
185
+ git checkout -b deps/python-311-and-bumps
186
+ ```
187
+
188
+ - [ ] **Step 2: Bump `requires-python` and classifiers**
189
+
190
+ In `pyproject.toml`:
191
+
192
+ Change:
193
+ ```toml
194
+ requires-python = ">=3.10"
195
+ ```
196
+ to:
197
+ ```toml
198
+ requires-python = ">=3.11"
199
+ ```
200
+
201
+ Change classifiers from:
202
+ ```toml
203
+ "Programming Language :: Python :: 3.8",
204
+ "Programming Language :: Python :: 3.9",
205
+ "Programming Language :: Python :: 3.10",
206
+ "Programming Language :: Python :: 3.11",
207
+ "Programming Language :: Python :: 3.12",
208
+ "Programming Language :: Python :: 3.13",
209
+ ```
210
+ to:
211
+ ```toml
212
+ "Programming Language :: Python :: 3.11",
213
+ "Programming Language :: Python :: 3.12",
214
+ "Programming Language :: Python :: 3.13",
215
+ "Programming Language :: Python :: 3.14",
216
+ ```
217
+
218
+ (Delete the 3.8, 3.9, 3.10 lines; add the 3.14 line.)
219
+
220
+ - [ ] **Step 3: Commit**
221
+
222
+ ```bash
223
+ git add pyproject.toml
224
+ git commit -m "chore: bump Python floor to 3.11, add 3.14 classifier"
225
+ ```
226
+
227
+ ### Task 3: Bump non-breaking dependencies
228
+
229
+ **Files:**
230
+ - Modify: `pyproject.toml`
231
+
232
+ - [ ] **Step 1: Update dependency pins**
233
+
234
+ In `pyproject.toml`, change the `dependencies` block from:
235
+
236
+ ```toml
237
+ dependencies = [
238
+ "fastmcp>=2.11.3",
239
+ "pandas>=2.2.3",
240
+ "numpy>=2.1.3",
241
+ "pydantic>=2.10.4",
242
+ "aiofiles>=24.1.0",
243
+ "python-dateutil>=2.9.0",
244
+ "httpx>=0.27.0",
245
+ "openpyxl>=3.1.5",
246
+ "pyarrow>=17.0.0",
247
+ "tabulate>=0.9.0",
248
+ "pytz>=2024.2",
249
+ "pydantic-settings>=2.10.1",
250
+ ]
251
+ ```
252
+
253
+ to (note: `fastmcp` pin is **unchanged** in this PR; PR 3 handles it):
254
+
255
+ ```toml
256
+ dependencies = [
257
+ "fastmcp>=2.11.3",
258
+ "pandas>=2.2.3",
259
+ "numpy>=2.1.3",
260
+ "pydantic>=2.13",
261
+ "aiofiles>=25",
262
+ "python-dateutil>=2.9.0",
263
+ "httpx>=0.28",
264
+ "openpyxl>=3.1.5",
265
+ "pyarrow>=23",
266
+ "tabulate>=0.10",
267
+ "pytz>=2024.2",
268
+ "pydantic-settings>=2.13",
269
+ ]
270
+ ```
271
+
272
+ - [ ] **Step 2: Sync and run tests locally**
273
+
274
+ ```bash
275
+ uv sync --all-extras
276
+ uv run pytest tests/ -v --tb=short
277
+ ```
278
+
279
+ Expected: tests execute; pass rate matches PR 1 baseline (from Task 0 Step 5 / Task 1 Step 6).
280
+
281
+ If any test that was green on baseline now fails, **stop**: the bump introduced a regression. Bisect by reverting dep pins one at a time to identify which dep caused it. Most likely suspect: pydantic 2.13 union serialization. Hotfix by pinning `pydantic<2.13` and opening a follow-up issue.
282
+
283
+ - [ ] **Step 3: Commit**
284
+
285
+ ```bash
286
+ git add pyproject.toml
287
+ git commit -m "chore: bump non-breaking deps (pydantic 2.13, pyarrow 23, httpx 0.28, aiofiles 25, tabulate 0.10)"
288
+ ```
289
+
290
+ ### Task 4: Bump tool target-versions
291
+
292
+ **Files:**
293
+ - Modify: `pyproject.toml`
294
+
295
+ - [ ] **Step 1: Update black target-version**
296
+
297
+ In `pyproject.toml`:
298
+
299
+ Change:
300
+ ```toml
301
+ [tool.black]
302
+ line-length = 100
303
+ target-version = ["py38", "py39", "py310", "py311", "py312", "py313"]
304
+ ```
305
+ to:
306
+ ```toml
307
+ [tool.black]
308
+ line-length = 100
309
+ target-version = ["py311", "py312", "py313", "py314"]
310
+ ```
311
+
312
+ - [ ] **Step 2: Update ruff target-version**
313
+
314
+ Change:
315
+ ```toml
316
+ [tool.ruff]
317
+ line-length = 100
318
+ target-version = "py38"
319
+ ```
320
+ to:
321
+ ```toml
322
+ [tool.ruff]
323
+ line-length = 100
324
+ target-version = "py311"
325
+ ```
326
+
327
+ - [ ] **Step 3: Update mypy python_version**
328
+
329
+ Change:
330
+ ```toml
331
+ [tool.mypy]
332
+ python_version = "3.8"
333
+ ```
334
+ to:
335
+ ```toml
336
+ [tool.mypy]
337
+ python_version = "3.11"
338
+ ```
339
+
340
+ - [ ] **Step 4: Run linters to confirm no regressions**
341
+
342
+ ```bash
343
+ uv run ruff check src/ tests/
344
+ uv run black --check src/ tests/
345
+ ```
346
+
347
+ Expected: both exit 0 (no formatting/lint changes needed from the target-version bump alone).
348
+
349
+ If ruff suggests any `UP` (pyupgrade) rule changes because 3.11+ is now the floor, accept them as a separate step below.
350
+
351
+ - [ ] **Step 5: If ruff proposed UP fixes, apply them**
352
+
353
+ ```bash
354
+ uv run ruff check src/ tests/ --fix
355
+ ```
356
+
357
+ - [ ] **Step 6: Re-run tests**
358
+
359
+ ```bash
360
+ uv run pytest tests/ -v --tb=short
361
+ ```
362
+
363
+ Expected: pass rate matches baseline.
364
+
365
+ - [ ] **Step 7: Commit**
366
+
367
+ ```bash
368
+ git add pyproject.toml src/ tests/
369
+ git commit -m "chore: bump black/ruff/mypy target-version to Python 3.11"
370
+ ```
371
+
372
+ ### Task 5: Update Dockerfile, README, CONTRIBUTING
373
+
374
+ **Files:**
375
+ - Modify: `Dockerfile`
376
+ - Modify: `README.md`
377
+ - Modify: `CONTRIBUTING.md`
378
+
379
+ - [ ] **Step 1: Pin Dockerfile base image**
380
+
381
+ In `Dockerfile`, change line 2 from:
382
+ ```dockerfile
383
+ FROM python:3.11-slim
384
+ ```
385
+ to:
386
+ ```dockerfile
387
+ FROM python:3.11-slim-bookworm
388
+ ```
389
+
390
+ - [ ] **Step 2: Update README Python badge**
391
+
392
+ In `README.md`, find the Python badge near the top:
393
+ ```markdown
394
+ [![Python](https://img.shields.io/badge/Python-3.8%2B-blue)](https://www.python.org/)
395
+ ```
396
+ Change to:
397
+ ```markdown
398
+ [![Python](https://img.shields.io/badge/Python-3.11%2B-blue)](https://www.python.org/)
399
+ ```
400
+
401
+ - [ ] **Step 3: Scrub 3.8/3.9/3.10 claims from README**
402
+
403
+ Grep: `grep -n "3\.8\|3\.9\|3\.10" README.md`
404
+
405
+ For each match, update the sentence to reflect the 3.11 floor (or delete if the line is an obsolete compatibility note). Do **not** touch version numbers of libraries that happen to contain these digits.
406
+
407
+ - [ ] **Step 4: Add .venv rebuild note to CONTRIBUTING.md**
408
+
409
+ In `CONTRIBUTING.md`, find the "Development Setup" or equivalent section. Add (or update if one exists) a subsection:
410
+
411
+ ```markdown
412
+ ### Rebuilding the local virtualenv
413
+
414
+ If your `.venv/` points at a missing Python interpreter (common after upgrading Python or removing a conda env), rebuild it:
415
+
416
+ ```bash
417
+ rm -rf .venv
418
+ uv sync --all-extras
419
+ ```
420
+
421
+ This requires Python 3.11+ available on your PATH (3.14 recommended).
422
+ ```
423
+
424
+ If `CONTRIBUTING.md` has no "Development Setup" section, append the subsection at the end of the file with a `## Development` heading.
425
+
426
+ - [ ] **Step 5: Commit**
427
+
428
+ ```bash
429
+ git add Dockerfile README.md CONTRIBUTING.md
430
+ git commit -m "docs: pin Dockerfile base, update README badge, add venv rebuild guide"
431
+ ```
432
+
433
+ ### Task 6: Drop Python 3.10 from CI matrix
434
+
435
+ **Files:**
436
+ - Modify: `.github/workflows/test.yml`
437
+
438
+ - [ ] **Step 1: Remove 3.10 from matrix**
439
+
440
+ In `.github/workflows/test.yml`, change:
441
+ ```yaml
442
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
443
+ ```
444
+ to:
445
+ ```yaml
446
+ python-version: ["3.11", "3.12", "3.13", "3.14"]
447
+ ```
448
+
449
+ - [ ] **Step 2: Commit**
450
+
451
+ ```bash
452
+ git add .github/workflows/test.yml
453
+ git commit -m "ci: drop Python 3.10 from matrix (floor bumped to 3.11)"
454
+ ```
455
+
456
+ ### Task 7: Push and merge PR 2
457
+
458
+ - [ ] **Step 1: Push branch**
459
+
460
+ ```bash
461
+ git push -u origin deps/python-311-and-bumps
462
+ ```
463
+
464
+ - [ ] **Step 2: Open PR**
465
+
466
+ Title: `deps: bump Python floor to 3.11 and non-breaking dependencies`
467
+ Body: "Bumps Python floor from 3.10 to 3.11. Bumps pydantic, pydantic-settings, pyarrow, httpx, aiofiles, tabulate to current major versions. FastMCP and pandas are **not** bumped in this PR. Drops 3.10 from CI matrix. Part of v2.0.0 preparation (spec: `specs/2026-04-19-fastmcp3-migration-design.md`)."
468
+
469
+ - [ ] **Step 3: Wait for CI green**
470
+
471
+ All four matrix rows (3.11, 3.12, 3.13, 3.14) must be green.
472
+
473
+ If red on pydantic 2.13: follow the risk mitigation in the spec (pin `pydantic<2.13`, open issue).
474
+
475
+ - [ ] **Step 4: Merge**
476
+
477
+ Squash merge via GitHub UI. Delete the branch.
478
+
479
+ ---
480
+
481
+ ## PR 3: FastMCP 3 Migration + SSE Removal
482
+
483
+ **Branch name:** `feat/fastmcp-3-migration`
484
+ **Goal:** bump FastMCP to 3.2; remove `sse` from CLI `--transport` choices; add boot regression tests.
485
+
486
+ ### Task 8: Create branch and add failing boot tests
487
+
488
+ **Files:**
489
+ - Create: `tests/test_server_boot.py`
490
+
491
+ - [ ] **Step 1: Create branch**
492
+
493
+ ```bash
494
+ git checkout main
495
+ git pull --ff-only origin main
496
+ git checkout -b feat/fastmcp-3-migration
497
+ ```
498
+
499
+ - [ ] **Step 2: Write test file**
500
+
501
+ Create `tests/test_server_boot.py`:
502
+
503
+ ```python
504
+ """Smoke tests for server boot, tool registry, and CLI argument handling."""
505
+
506
+ import pytest
507
+
508
+
509
+ def test_server_imports_clean():
510
+ """Importing the server module must not raise."""
511
+ import csv_editor.server # noqa: F401
512
+
513
+
514
+ def test_tool_registry_populated():
515
+ """After import, the FastMCP instance must have at least 40 registered tools."""
516
+ from csv_editor.server import mcp
517
+
518
+ # FastMCP exposes registered tools via _tool_manager in 3.x; fall back to list_tools-style APIs
519
+ # if the attribute name differs. We probe several plausible locations to stay robust.
520
+ tool_count = _count_registered_tools(mcp)
521
+
522
+ assert tool_count >= 40, f"Expected at least 40 tools registered, got {tool_count}"
523
+
524
+
525
+ def test_cli_rejects_sse_transport():
526
+ """The CLI must reject --transport sse with a non-zero exit."""
527
+ from csv_editor.server import main
528
+
529
+ with pytest.raises(SystemExit) as exc_info:
530
+ main(["--transport", "sse"]) # argparse exits on invalid choice
531
+
532
+ # argparse uses exit code 2 for argument errors
533
+ assert exc_info.value.code == 2
534
+
535
+
536
+ def _count_registered_tools(mcp) -> int:
537
+ """Robustly count registered tools across FastMCP 2.x/3.x attribute naming."""
538
+ for attr in ("_tool_manager", "tool_manager", "_tools", "tools"):
539
+ obj = getattr(mcp, attr, None)
540
+ if obj is None:
541
+ continue
542
+ tools = getattr(obj, "_tools", None) or getattr(obj, "tools", None) or obj
543
+ try:
544
+ return len(tools)
545
+ except TypeError:
546
+ continue
547
+ # Fall back to calling a list-tools coroutine if present (FastMCP 3)
548
+ list_tools = getattr(mcp, "list_tools", None)
549
+ if callable(list_tools):
550
+ import asyncio
551
+ result = asyncio.run(list_tools())
552
+ return len(result)
553
+ raise RuntimeError("Could not locate FastMCP tool registry")
554
+ ```
555
+
556
+ - [ ] **Step 3: Run the tests — two must fail**
557
+
558
+ ```bash
559
+ uv run pytest tests/test_server_boot.py -v
560
+ ```
561
+
562
+ Expected:
563
+ - `test_server_imports_clean` — **PASS** (server already imports cleanly)
564
+ - `test_tool_registry_populated` — **PASS or FAIL** depending on how FastMCP 2.11 exposes the registry; if FAIL, the `_count_registered_tools` helper needs adjustment. If you cannot locate the registry in 2.x, mark the test `@pytest.mark.skip(reason="registry location verified after FastMCP 3 migration")` and remove the skip in Task 10.
565
+ - `test_cli_rejects_sse_transport` — **FAIL**: `sse` is currently a valid choice, so `main` will proceed rather than SystemExit. This is the red we want — PR 3 will turn it green.
566
+
567
+ Note: `main` is defined but does not currently accept `argv` as a parameter. You will need to refactor `main` in Task 9 to accept `argv: list[str] | None = None` so tests can invoke it. Record this as a prerequisite for Task 9.
568
+
569
+ - [ ] **Step 4: Commit the failing tests**
570
+
571
+ ```bash
572
+ git add tests/test_server_boot.py
573
+ git commit -m "test: add server boot + CLI regression tests (one intentionally failing)"
574
+ ```
575
+
576
+ ### Task 9: Refactor `main` to accept argv and drop `sse` choice
577
+
578
+ **Files:**
579
+ - Modify: `src/csv_editor/server.py` (lines around 635–679)
580
+
581
+ - [ ] **Step 1: Refactor `main` signature**
582
+
583
+ In `src/csv_editor/server.py`, change the `def main():` line to accept an optional argv parameter. Change the `parser.parse_args()` call to pass `argv` through.
584
+
585
+ Find:
586
+ ```python
587
+ def main():
588
+ """Main entry point for the server."""
589
+ import argparse
590
+
591
+ parser = argparse.ArgumentParser(description="CSV Editor")
592
+ ```
593
+
594
+ Change to:
595
+ ```python
596
+ def main(argv: list[str] | None = None):
597
+ """Main entry point for the server."""
598
+ import argparse
599
+
600
+ parser = argparse.ArgumentParser(description="CSV Editor")
601
+ ```
602
+
603
+ Find:
604
+ ```python
605
+ args = parser.parse_args()
606
+ ```
607
+
608
+ Change to:
609
+ ```python
610
+ args = parser.parse_args(argv)
611
+ ```
612
+
613
+ - [ ] **Step 2: Drop `sse` from `--transport` choices**
614
+
615
+ Find:
616
+ ```python
617
+ parser.add_argument(
618
+ "--transport",
619
+ choices=["stdio", "http", "sse"],
620
+ default="stdio",
621
+ help="Transport method"
622
+ )
623
+ ```
624
+
625
+ Change to:
626
+ ```python
627
+ parser.add_argument(
628
+ "--transport",
629
+ choices=["stdio", "http"],
630
+ default="stdio",
631
+ help="Transport method (stdio for local clients, http for Streamable HTTP remote)"
632
+ )
633
+ ```
634
+
635
+ - [ ] **Step 3: Run tests — expect test_cli_rejects_sse_transport to pass now**
636
+
637
+ ```bash
638
+ uv run pytest tests/test_server_boot.py -v
639
+ ```
640
+
641
+ Expected: `test_cli_rejects_sse_transport` now **PASS**.
642
+
643
+ - [ ] **Step 4: Commit**
644
+
645
+ ```bash
646
+ git add src/csv_editor/server.py
647
+ git commit -m "feat: drop sse from --transport CLI choices, refactor main to accept argv"
648
+ ```
649
+
650
+ ### Task 10: Bump FastMCP pin to 3.2
651
+
652
+ **Files:**
653
+ - Modify: `pyproject.toml`
654
+
655
+ - [ ] **Step 1: Update FastMCP pin**
656
+
657
+ In `pyproject.toml`, change:
658
+ ```toml
659
+ "fastmcp>=2.11.3",
660
+ ```
661
+ to:
662
+ ```toml
663
+ "fastmcp>=3.2,<4",
664
+ ```
665
+
666
+ - [ ] **Step 2: Sync**
667
+
668
+ ```bash
669
+ uv sync --all-extras
670
+ ```
671
+
672
+ Expected: resolves to FastMCP 3.2.x, no conflicts.
673
+
674
+ - [ ] **Step 3: Run full test suite**
675
+
676
+ ```bash
677
+ uv run pytest tests/ -v --tb=short
678
+ ```
679
+
680
+ Expected:
681
+ - All three tests in `test_server_boot.py` pass (including `test_tool_registry_populated` — if it was skipped in Task 8 Step 3, remove the skip marker now and confirm pass).
682
+ - Other tests pass at the rate matching baseline.
683
+
684
+ If any tool-decorator test fails due to FastMCP API drift (e.g., `Context` API changed), consult the [FastMCP 3 upgrade guide](https://gofastmcp.com/getting-started/upgrading/from-fastmcp-2) and patch `src/csv_editor/server.py` or the affected tool module. Common fixes:
685
+ - `Context` import path unchanged.
686
+ - Tool decorator (`@mcp.tool`) unchanged.
687
+ - Constructor already minimal (`FastMCP("CSV Editor")`), no kwargs to strip.
688
+
689
+ - [ ] **Step 4: Manually smoke-test stdio transport**
690
+
691
+ Start the server in one terminal:
692
+ ```bash
693
+ uv run csv-editor --transport stdio
694
+ ```
695
+
696
+ In a second terminal, send a `tools/list` JSON-RPC request via stdin (or use an MCP client like Claude Desktop configured against the local build).
697
+
698
+ Expected: server responds with a list of ≥40 tools.
699
+
700
+ Kill the server with Ctrl-C.
701
+
702
+ - [ ] **Step 5: Manually smoke-test http transport**
703
+
704
+ ```bash
705
+ uv run csv-editor --transport http --port 8765
706
+ ```
707
+
708
+ In a second terminal:
709
+ ```bash
710
+ curl -sv http://127.0.0.1:8765/mcp
711
+ ```
712
+
713
+ Expected: HTTP response (likely 400 or 405 for a bare GET, since Streamable HTTP expects JSON-RPC POST — the point is the server bound and is listening, not that curl completes a handshake).
714
+
715
+ Kill the server.
716
+
717
+ - [ ] **Step 6: Commit**
718
+
719
+ ```bash
720
+ git add pyproject.toml tests/test_server_boot.py
721
+ git commit -m "feat: migrate to FastMCP 3.2"
722
+ ```
723
+
724
+ (`tests/test_server_boot.py` is included in case the skip marker was removed in Step 3.)
725
+
726
+ ### Task 11: Scrub SSE references from docs
727
+
728
+ **Files:**
729
+ - Modify: `README.md`
730
+ - Modify: `MCP_CONFIG.md`
731
+
732
+ - [ ] **Step 1: Find SSE references**
733
+
734
+ ```bash
735
+ grep -n -i "sse\|server-sent" README.md MCP_CONFIG.md
736
+ ```
737
+
738
+ Record each match.
739
+
740
+ - [ ] **Step 2: Update each reference**
741
+
742
+ For each match:
743
+ - If the line documents `--transport sse` as a valid option: **delete** the line or replace with `--transport http`.
744
+ - If the line explains SSE as a transport alternative: **delete** the paragraph.
745
+ - If the line is in a table of transports: **remove the SSE row**.
746
+
747
+ Do not touch any line that happens to contain "SSE" as part of an unrelated word (search is case-insensitive to catch `sse` fragments; verify context before editing).
748
+
749
+ - [ ] **Step 3: Verify no remaining SSE references**
750
+
751
+ ```bash
752
+ grep -n -i "sse\|server-sent" README.md MCP_CONFIG.md
753
+ ```
754
+
755
+ Expected: no matches (or only matches in unrelated words you've already verified).
756
+
757
+ - [ ] **Step 4: Commit**
758
+
759
+ ```bash
760
+ git add README.md MCP_CONFIG.md
761
+ git commit -m "docs: remove SSE transport references"
762
+ ```
763
+
764
+ ### Task 12: Verify Dockerfile and Smithery config
765
+
766
+ **Files:** none changed (verification only)
767
+
768
+ - [ ] **Step 1: Confirm Dockerfile CMD uses stdio**
769
+
770
+ ```bash
771
+ grep "CMD\|ENTRYPOINT" Dockerfile
772
+ ```
773
+
774
+ Expected: `CMD ["csv-editor", "--transport", "stdio"]` — no SSE reference.
775
+
776
+ - [ ] **Step 2: Confirm smithery.yaml uses stdio**
777
+
778
+ ```bash
779
+ grep -i "transport\|sse\|http" smithery.yaml
780
+ ```
781
+
782
+ Expected: references to `stdio` only; no `sse`. If any `sse` appears, fix by replacing with `stdio` (or `http` if the intent was remote) and commit separately with message `fix: update smithery config to stdio-only transport`.
783
+
784
+ ### Task 13: Push and merge PR 3
785
+
786
+ - [ ] **Step 1: Push branch**
787
+
788
+ ```bash
789
+ git push -u origin feat/fastmcp-3-migration
790
+ ```
791
+
792
+ - [ ] **Step 2: Open PR**
793
+
794
+ Title: `feat: FastMCP 3 migration + SSE transport removal`
795
+ Body:
796
+
797
+ ```markdown
798
+ ## Summary
799
+ - Bumps FastMCP to `>=3.2,<4`
800
+ - Removes `sse` from `--transport` CLI choices (breaking — users should migrate to `--transport http` for remote)
801
+ - Refactors `main()` to accept `argv` for testability
802
+ - Adds `tests/test_server_boot.py` with three regression tests
803
+ - Scrubs SSE references from README and MCP_CONFIG docs
804
+
805
+ ## Breaking changes
806
+ - `--transport sse` is no longer accepted. Use `--transport http` (Streamable HTTP) instead.
807
+ - Consumers importing FastMCP APIs transitively may need updates per the [FastMCP 3 upgrade guide](https://gofastmcp.com/getting-started/upgrading/from-fastmcp-2).
808
+
809
+ ## Test plan
810
+ - [x] `uv run pytest tests/` green on all four Python matrix rows
811
+ - [x] Manual stdio smoke test via `uv run csv-editor --transport stdio`
812
+ - [x] Manual http smoke test via `uv run csv-editor --transport http --port 8765`
813
+ - [ ] Claude Desktop smoke test — will verify post-merge on a release candidate build
814
+
815
+ Part of v2.0.0 preparation (spec: `specs/2026-04-19-fastmcp3-migration-design.md`).
816
+ ```
817
+
818
+ - [ ] **Step 3: Wait for CI green**
819
+
820
+ All four matrix rows must be green.
821
+
822
+ - [ ] **Step 4: Manual Claude Desktop smoke test**
823
+
824
+ Before merging, install the PR branch locally into Claude Desktop:
825
+
826
+ 1. Build: `uv build`
827
+ 2. Point Claude Desktop's `claude_desktop_config.json` at the built wheel or a local `uv tool install` of the branch.
828
+ 3. Restart Claude Desktop.
829
+ 4. Invoke `health_check` — expect `status: "healthy"`.
830
+ 5. Invoke `load_csv` on a small fixture file — expect success with shape/columns returned.
831
+
832
+ If either invocation fails, **do not merge**. Diagnose the FastMCP 3 API call, patch, push.
833
+
834
+ - [ ] **Step 5: Merge**
835
+
836
+ Squash merge via GitHub UI. Delete the branch.
837
+
838
+ ---
839
+
840
+ ## PR 4: v2.0.0 Release Cut
841
+
842
+ **Branch name:** `release/v2.0.0`
843
+ **Goal:** finalize CHANGELOG, bump version string in two places, merge, tag, push.
844
+
845
+ ### Task 14: Create branch and bump version
846
+
847
+ **Files:**
848
+ - Modify: `pyproject.toml`
849
+ - Modify: `src/csv_editor/server.py` (`health_check` response)
850
+
851
+ - [ ] **Step 1: Create branch**
852
+
853
+ ```bash
854
+ git checkout main
855
+ git pull --ff-only origin main
856
+ git checkout -b release/v2.0.0
857
+ ```
858
+
859
+ - [ ] **Step 2: Bump version in pyproject.toml**
860
+
861
+ In `pyproject.toml`, find:
862
+ ```toml
863
+ version = "1.0.1"
864
+ ```
865
+ Change to:
866
+ ```toml
867
+ version = "2.0.0"
868
+ ```
869
+
870
+ - [ ] **Step 3: Bump version in health_check**
871
+
872
+ In `src/csv_editor/server.py`, find the `health_check` function (around line 28–54) and locate the `"version": "1.0.0"` line. Change to:
873
+ ```python
874
+ "version": "2.0.0",
875
+ ```
876
+
877
+ - [ ] **Step 4: Run tests**
878
+
879
+ ```bash
880
+ uv run pytest tests/ -v
881
+ ```
882
+
883
+ Expected: all green (if any tests assert against the version string, they should already be using `csv_editor.__version__` or similar — if they assert the literal `"1.0.0"`, update them in this task).
884
+
885
+ - [ ] **Step 5: Commit**
886
+
887
+ ```bash
888
+ git add pyproject.toml src/csv_editor/server.py
889
+ git commit -m "chore: bump version to 2.0.0"
890
+ ```
891
+
892
+ ### Task 15: Write CHANGELOG entry
893
+
894
+ **Files:**
895
+ - Modify: `CHANGELOG.md`
896
+
897
+ - [ ] **Step 1: Add [2.0.0] section**
898
+
899
+ In `CHANGELOG.md`, insert after the header comment and before `## [1.0.1]`:
900
+
901
+ ```markdown
902
+ ## [2.0.0] - 2026-04-DD
903
+
904
+ ### BREAKING CHANGES
905
+ - **Python floor raised to 3.11.** Users on 3.8, 3.9, or 3.10 must upgrade. Users who pinned `csv-editor>=1,<2` are unaffected.
906
+ - **`--transport sse` CLI option removed.** Use `--transport http` (Streamable HTTP) for remote deployments. This aligns with the MCP 2025-11-25 spec and FastMCP 3 guidance.
907
+ - **FastMCP dependency bumped to `>=3.2,<4`.** Any code importing FastMCP APIs transitively may require updates per the [FastMCP 3 upgrade guide](https://gofastmcp.com/getting-started/upgrading/from-fastmcp-2).
908
+
909
+ ### Added
910
+ - GitHub Actions `test.yml` workflow: pytest matrix on Python 3.11–3.14.
911
+ - `tests/test_server_boot.py` regression tests for server import, tool registry, and CLI argument handling.
912
+ - Python 3.14 classifier and test coverage.
913
+ - Contributing guide: local virtualenv rebuild instructions.
914
+
915
+ ### Changed
916
+ - Python floor: `>=3.10` → `>=3.11`.
917
+ - `fastmcp`: `>=2.11.3` → `>=3.2,<4`.
918
+ - `pydantic`: `>=2.10.4` → `>=2.13`.
919
+ - `pydantic-settings`: `>=2.10.1` → `>=2.13`.
920
+ - `pyarrow`: `>=17.0.0` → `>=23`.
921
+ - `httpx`: `>=0.27.0` → `>=0.28`.
922
+ - `aiofiles`: `>=24.1.0` → `>=25`.
923
+ - `tabulate`: `>=0.9.0` → `>=0.10`.
924
+ - `black`/`ruff`/`mypy` target-version: Python 3.8 → 3.11.
925
+ - Dockerfile base image pinned to `python:3.11-slim-bookworm`.
926
+ - `main()` signature: now accepts optional `argv` for testability.
927
+
928
+ ### Removed
929
+ - `--transport sse` CLI option.
930
+ - Python 3.8, 3.9, 3.10 classifier entries.
931
+
932
+ ### Unchanged
933
+ - `pandas>=2.2.3` and `numpy>=2.1.3` — upgrading to 3.0/2.4 is deferred to a follow-up release (Sub-project 1b) due to pandas' Copy-on-Write behavioral change requiring focused testing.
934
+ ```
935
+
936
+ Replace `2026-04-DD` with the actual release date when you push the tag.
937
+
938
+ - [ ] **Step 2: Commit**
939
+
940
+ ```bash
941
+ git add CHANGELOG.md
942
+ git commit -m "docs: add v2.0.0 CHANGELOG entry"
943
+ ```
944
+
945
+ ### Task 16: Push, open PR, and merge
946
+
947
+ - [ ] **Step 1: Push**
948
+
949
+ ```bash
950
+ git push -u origin release/v2.0.0
951
+ ```
952
+
953
+ - [ ] **Step 2: Open PR**
954
+
955
+ Title: `release: v2.0.0`
956
+ Body: paste the CHANGELOG `[2.0.0]` section.
957
+
958
+ - [ ] **Step 3: Wait for CI green**
959
+
960
+ All four matrix rows must be green.
961
+
962
+ - [ ] **Step 4: Merge**
963
+
964
+ Squash merge via GitHub UI.
965
+
966
+ ### Task 17: Tag and verify release
967
+
968
+ **Files:** none (tagging and publishing)
969
+
970
+ - [ ] **Step 1: Update CHANGELOG date**
971
+
972
+ Checkout main, pull, and if the `2026-04-DD` placeholder is still in CHANGELOG.md, make a quick follow-up commit with the real date (e.g., `2026-04-22`):
973
+
974
+ ```bash
975
+ git checkout main
976
+ git pull --ff-only origin main
977
+ # If CHANGELOG still has 2026-04-DD:
978
+ sed -i.bak 's/2026-04-DD/2026-04-22/' CHANGELOG.md && rm CHANGELOG.md.bak
979
+ git add CHANGELOG.md
980
+ git commit -m "docs: set v2.0.0 release date"
981
+ git push origin main
982
+ ```
983
+
984
+ Skip this step if the date was already correct in PR 4.
985
+
986
+ - [ ] **Step 2: Create and push tag**
987
+
988
+ ```bash
989
+ git tag -a v2.0.0 -m "Release v2.0.0: FastMCP 3 + Python 3.11 floor"
990
+ git push origin v2.0.0
991
+ ```
992
+
993
+ - [ ] **Step 3: Wait for publish workflow**
994
+
995
+ `publish.yml` (existing) should trigger on the tag and publish to PyPI. Watch the Actions tab.
996
+
997
+ Expected: green publish run; `csv-editor 2.0.0` visible on PyPI.
998
+
999
+ - [ ] **Step 4: Verify pip install in a clean venv**
1000
+
1001
+ ```bash
1002
+ cd /tmp
1003
+ python3.11 -m venv verify-venv
1004
+ ./verify-venv/bin/pip install csv-editor==2.0.0
1005
+ ./verify-venv/bin/csv-editor --help
1006
+ ```
1007
+
1008
+ Expected: `--help` output lists `--transport {stdio,http}` (no `sse`).
1009
+
1010
+ - [ ] **Step 5: Verify Smithery listing**
1011
+
1012
+ Open https://smithery.ai/server/@santoshray02/csv-editor in a browser. Confirm the version badge shows `2.0.0` (may take minutes to refresh; manually redeploy via Smithery dashboard if stale).
1013
+
1014
+ - [ ] **Step 6: Announce**
1015
+
1016
+ Optional but recommended: post a GitHub Release note (copy the CHANGELOG `[2.0.0]` section) at https://github.com/santoshray02/csv-editor/releases/new with tag `v2.0.0`.
1017
+
1018
+ ---
1019
+
1020
+ ## Post-release: Follow-up Issues to Open
1021
+
1022
+ After v2.0.0 ships, open these GitHub issues to track the rest of the roadmap:
1023
+
1024
+ - [ ] **Issue: Sub-project 0 — Migrate docs from Docusaurus to MkDocs-Material**
1025
+ - [ ] **Issue: Sub-project 1b — pandas 3.0 + numpy 2.4 migration**
1026
+ - [ ] **Issue: Sub-project 2 — Add DuckDB engine (default for files >100 MB) + Polars engine**
1027
+ - [ ] **Issue: Sub-project 3 — Adopt MCP async Tasks for `load_csv`, `export_csv`, `profile_data`; Resource Links for large outputs**
1028
+ - [ ] **Issue: Sub-project 4 — Remote HTTP + OAuth (CIMD) deployment mode for ChatGPT Connectors reach**
1029
+ - [ ] **Issue: Sub-project 5 — Elicitation for ambiguous CSV dialect/encoding detection**
1030
+
1031
+ For each, paste the relevant sections from the April 2026 relevance audit as the issue body, link the design spec once written.
1032
+
1033
+ ---
1034
+
1035
+ ## Self-Review Checklist (meta)
1036
+
1037
+ Before starting implementation:
1038
+
1039
+ - [ ] Spec and plan locations match user preference (specs/, plans/ — not docs/ which is Docusaurus).
1040
+ - [ ] Every task has exact file paths and complete code in every code step.
1041
+ - [ ] No "TBD", "similar to Task N", or "implement appropriate error handling" placeholders.
1042
+ - [ ] Types and function signatures referenced late in the plan (e.g., `main(argv)`) are defined in an earlier task.
1043
+ - [ ] PR 1 → PR 2 → PR 3 → PR 4 ordering is enforced (each PR 2+ task assumes the previous PR merged).
1044
+ - [ ] CI matrix in `test.yml` matches the Python floor at each point (3.10–3.14 in PR 1, 3.11–3.14 after PR 2).
1045
+ - [ ] Manual Claude Desktop smoke test is called out explicitly as a gate before merging PR 3.