mcp-gee-sweet 0.7.0.dev82__tar.gz → 0.7.0.dev84__tar.gz
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.
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/.claude/commands/merge-pr.md +2 -1
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/PKG-INFO +1 -1
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/design/docs-ast-pipeline.md +14 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/tests/docs.md +19 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/roadmap.md +8 -7
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/docs/emitter.py +57 -23
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/tests/test_docs_tables.py +120 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/.claude/commands/cleanup-worktrees.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/.claude/commands/orchestrator.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/.claude/commands/prep-for-pr.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/.claude/commands/start-worktree.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/.claude/commands/verify-pr.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/.dockerignore +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/.github/dependabot.yml +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/.github/workflows/ci.yml +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/.github/workflows/docs.yml +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/.github/workflows/publish-dev.yml +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/.github/workflows/release.yml +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/.gitignore +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/.pre-commit-config.yaml +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/.python-version +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/CHANGELOG.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/CLAUDE.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/CODE_OF_CONDUCT.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/CONTRIBUTING.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/Dockerfile +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/LICENSE +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/Makefile +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/README.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/SECURITY.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docker-compose.yml +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/auth.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/client-setup.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/configuration.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/decisions/decision-cache-invalidation.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/decisions/decision-chart-theming.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/decisions/decision-composite-tools.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/decisions/decision-docs-formatting.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/decisions/decision-fork.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/decisions/decision-grid-data-size-cap.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/decisions/decision-publishing.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/decisions/decision-response-size-cap-generalization.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/decisions/decision-testing.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/decisions/index.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/design/effectiveformat-spike.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/design/index.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/design/markdown-support.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/design/phase3-theme-system.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/design.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/index.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/known-limitations.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/notes-read.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/README.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/fixtures/tc-d195-create-doc.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/fixtures/tc-d196-create-doc.html +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/fixtures/tc-d213-dollar-escape.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/fixtures/tc-d226-heading-table.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/fixtures.template.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/operations.yaml +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/playwright_oauth.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/results/.gitkeep +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/results/2026-06-02.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/results/2026-06-14.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/results/2026-06-17.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/results/2026-06-18.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/results/2026-06-21-drive-discovery.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/results/2026-06-21-sheets-delete-clear.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/results/2026-06-28.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/results/2026-07-04-docs.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/results/2026-07-04-drive-smoke.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/results/2026-07-04-infra.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/results/2026-07-04-sheets_read.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/results/2026-07-04.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/retro-v0.8.0.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/run.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/runs/README.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/runs/v0.8.0.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/runs/v0.8.1.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/setup.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/tests/calendar.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/tests/drive.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/tests/infra.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/tests/sheets_charts.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/tests/sheets_mgmt.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/tests/sheets_read.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/tests/sheets_write.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa-checklist.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/tools.md +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/mkdocs.yml +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/pyproject.toml +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/pyrightconfig.json +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/scripts/gen_tool_docs.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/scripts/oauth_setup.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/.env.template +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/auth.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/cache.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/server.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/cache.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/calendar.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/docs/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/docs/ast.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/docs/content.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/docs/html_parser.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/docs/layout.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/docs/style.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/docs/tables.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/drive/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/drive/activity.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/drive/files.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/drive/sharing.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/drive/transfer.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/response_limits.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/sheets/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/sheets/data.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/sheets/helpers.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/sheets/structure.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/tests/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/tests/drive/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/tests/drive/test_activity.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/tests/drive/test_files.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/tests/drive/test_sharing.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/tests/drive/test_transfer.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/tests/sheets/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/tests/sheets/test_data.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/tests/sheets/test_helpers.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/tests/sheets/test_structure.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/tests/test_auth.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/tests/test_cache.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/tests/test_calendar.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/tests/test_docs_content.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/tests/test_docs_core.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/tests/test_docs_layout.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/tests/test_docs_style.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/tests/test_gen_tool_docs.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/tests/test_response_limits.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/tests/test_server.py +0 -0
- {mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/uv.lock +0 -0
|
@@ -10,4 +10,5 @@ Merge the current branch's PR and clean up its worktree. Uses `--admin` only to
|
|
|
10
10
|
5. Clean up the local worktree:
|
|
11
11
|
- If this session created the current worktree (i.e. it got here via `/start-worktree` or `/next-issue` earlier in this same session), use `ExitWorktree` with `action: "remove"`.
|
|
12
12
|
- Otherwise (worktree predates this session), fall back to manual cleanup from the main checkout: `git worktree remove <path>`, then `git branch -d <branch>`, then `git worktree prune`.
|
|
13
|
-
6.
|
|
13
|
+
6. Update the roadmap: from the main checkout on `develop`, `git pull` then check `docs/roadmap.md` for a checkbox line referencing the issue(s) this PR closes (search by issue number). If found, check it off (`- [ ]` → `- [x]`) and append `(PR <number>)` in the existing style (e.g. `(PR #280)`) — see recent `- [x]` entries for the exact convention. If the roadmap has no line item for this issue (not every ticket is roadmap-tracked — chores, follow-up defects, etc.), skip this step silently. Commit directly to `develop` (`docs(roadmap): check off #<issue> (PR <number>)`) and push — `develop`'s branch protection has `enforce_admins: false`, so a direct push by the repo owner is allowed for this kind of small, reversible, docs-only change; don't open a separate PR for it.
|
|
14
|
+
7. Report a summary: the merge commit, the now-merged PR URL, confirmation the worktree was removed, and whether the roadmap was updated.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-gee-sweet
|
|
3
|
-
Version: 0.7.0.
|
|
3
|
+
Version: 0.7.0.dev84
|
|
4
4
|
Summary: MCP server for Google Workspace — Sheets, Drive, Docs, and Calendar. 60+ tools for AI clients.
|
|
5
5
|
Project-URL: Homepage, https://github.com/khuisman/mcp-gee-sweet
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/khuisman/mcp-gee-sweet/issues
|
|
@@ -338,3 +338,17 @@ Cells with no `Table` in `children` are still filled in one bulk batch by `_buil
|
|
|
338
338
|
- Text before, between, and after any number of nested tables in one cell renders in true source order, at any nesting depth.
|
|
339
339
|
- The "one level of nesting only" and "no colspan/rowspan/col_widths inside nested tables" limitations from the original #92 design are otherwise unchanged — this rewrite only touches content ordering, not those structural features.
|
|
340
340
|
- Tested with `tests/test_docs_tables.py`'s `TestFillNestedCellContent`, which drives the real algorithm against a small in-memory Docs-API simulator (`FakeDoc`/`Para`/`TableNode` in that file) that actually applies `insertText`/`insertTable` mutations and re-derives indices — chosen over hand-computed index fixtures because this much re-fetch/recursion logic is easy to get subtly wrong in a way a fixture can't catch (and did, twice, during development).
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Nested table colspan/rowspan (#109)
|
|
345
|
+
|
|
346
|
+
**Date:** 2026-07-08
|
|
347
|
+
|
|
348
|
+
Nested tables produced a correctly-sized shell but never merged cells with `colspan`/`rowspan` > 1 — `_fill_table_fully` (the per-nested-table fill helper introduced in the rewrite above) only ran the plain-cell bulk fill, with no equivalent of the outer-table merge phase (`fill_tables` phase 2).
|
|
349
|
+
|
|
350
|
+
Fix: `_fill_table_fully` now checks the nested `ast_table` for any cell with `colspan > 1 or rowspan > 1` and, if found, builds and executes `mergeTableCells` requests via the existing `_build_merge_requests` (unchanged — it already operated generically on any `doc_table`/`ast_table` pair, nested or not) before re-fetching and running the bulk fill. This exactly mirrors `fill_tables`'s own phase 1→2→3 order, just scoped to one nested table.
|
|
351
|
+
|
|
352
|
+
`col_widths` on nested tables remains unsupported — out of scope for #109, not addressed here.
|
|
353
|
+
|
|
354
|
+
Tested in `tests/test_docs_tables.py`'s `TestNestedTableMerges`, extending the same `FakeDoc` simulator with a no-op `mergeTableCells` handler (the real Docs API doesn't shift indices or delete content on merge — covered cells stay physical, just skipped by the existing phantom-cell mapping — so the simulator has nothing to mutate; the tests instead assert the merge request's shape and its position in batch order relative to the fill).
|
|
@@ -1038,6 +1038,25 @@ These test the HTML→AST→Docs API pipeline introduced in Phase 2 (#87). All u
|
|
|
1038
1038
|
|
|
1039
1039
|
---
|
|
1040
1040
|
|
|
1041
|
+
### TC-DOC90: `colspan`/`rowspan` inside a nested table now merges correctly (issue #109) ⚠️ destructive
|
|
1042
|
+
|
|
1043
|
+
**Background:** Nested tables produced a correctly-sized shell but silently ignored `colspan`/`rowspan` on their own cells — no `mergeTableCells` request was ever emitted for them, unlike outer-table cells (TC-DOC34/36/37). Fixed by having `_fill_table_fully` run the same merge phase for a nested table's own cells that `fill_tables` already runs for the outer table.
|
|
1044
|
+
|
|
1045
|
+
**Prompt**
|
|
1046
|
+
> "Write this HTML to doc {DOC_ID}: `<table><tr><td><table><tr><td colspan=\"2\">Header</td></tr><tr><td>A</td><td>B</td></tr></table></td></tr></table>`"
|
|
1047
|
+
|
|
1048
|
+
**Checks**
|
|
1049
|
+
- Call succeeds with no API error
|
|
1050
|
+
- `get_doc_structure` shows the outer table (1 row × 1 col) with cell [0,0] text empty (holds the nested table, not text)
|
|
1051
|
+
- 🔍 Visual check: nested table renders with "Header" spanning both columns of the top row, and "A"/"B" as two separate cells in the second row — not four ungrouped cells
|
|
1052
|
+
|
|
1053
|
+
**Cleanup:** write fixture content back
|
|
1054
|
+
|
|
1055
|
+
**Result (2026-07-09) ✅ PASS**
|
|
1056
|
+
`write_doc_content` succeeded. `get_doc_structure` showed the outer table (1 row × 1 col) with cell [0,0] text empty. Playwright screenshot confirmed the nested table rendered with "Header" spanning both columns of the top row and "A"/"B" as two separate cells below — the merge applied correctly. (Unrelated observation: the fixture doc had leftover header/footer text visible in the render and in `get_doc_content`'s plain-text export but not in `get_doc_structure` — headers/footers aren't part of the body map that tool returns; pre-existing fixture-doc state from an earlier header/footer test, untouched by `write_doc_content`, not a regression from this PR.) Fixture content restored per cleanup step.
|
|
1057
|
+
|
|
1058
|
+
---
|
|
1059
|
+
|
|
1041
1060
|
### TC-DOC52: `get_doc_theme` scans body paragraph styles
|
|
1042
1061
|
**Note:** `get_doc_theme` reads explicit per-paragraph and per-run styles from the document body. It returns data for AI-generated docs (where styles are set explicitly on runs); for standard docs whose styles are fully inherited from named style defaults it returns an empty dict.
|
|
1043
1062
|
|
|
@@ -95,8 +95,8 @@ No new tools. Stabilize on what Tier 1 shipped before starting Tier 2 feature wo
|
|
|
95
95
|
### Tier 2 — Useful for structured work, plus defects surfaced since v0.8.1 _(target: [v0.9.0](https://github.com/khuisman/mcp-gee-sweet/issues?q=is%3Aissue+label%3Av0.9))_
|
|
96
96
|
|
|
97
97
|
**Defects** _(found after v0.8.1 shipped; too late for that release, don't warrant a standalone patch)_
|
|
98
|
-
- [
|
|
99
|
-
- [
|
|
98
|
+
- [x] Bare URLs in markdown content aren't autolinked — Python-Markdown's built-in autolink only fires on `<https://...>` or `[text](url)`, not a bare URL (PR #265) ([#248](https://github.com/khuisman/mcp-gee-sweet/issues/248))
|
|
99
|
+
- [x] `zip(doc_tables, ast_tables)` in `emitter.py` silently cross-pairs tables when one is skipped (zero-row/zero-col table), misapplying fill/merge/style requests to the wrong table — surfaced during #276's review (PR #282) ([#277](https://github.com/khuisman/mcp-gee-sweet/issues/277))
|
|
100
100
|
|
|
101
101
|
**Sheets**
|
|
102
102
|
- [ ] `update_borders` — border style, width, color on a range ([#122](https://github.com/khuisman/mcp-gee-sweet/issues/122)) _(freema/mcp-gsheets)_
|
|
@@ -106,11 +106,11 @@ No new tools. Stabilize on what Tier 1 shipped before starting Tier 2 feature wo
|
|
|
106
106
|
- [ ] `update_sheet_properties` — tab color, hide/show gridlines ([#126](https://github.com/khuisman/mcp-gee-sweet/issues/126)) _(freema/mcp-gsheets)_
|
|
107
107
|
- [ ] `duplicate_sheet` — copy a sheet within the same spreadsheet ([#127](https://github.com/khuisman/mcp-gee-sweet/issues/127))
|
|
108
108
|
- [ ] Partial (rich-text) hyperlinks in `update_cells` ([#89](https://github.com/khuisman/mcp-gee-sweet/issues/89))
|
|
109
|
-
- [
|
|
109
|
+
- [x] `import_csv_to_sheet` — populate a spreadsheet from a local CSV file (PR #272) ([#187](https://github.com/khuisman/mcp-gee-sweet/issues/187))
|
|
110
110
|
|
|
111
111
|
**Calendar**
|
|
112
|
-
- [
|
|
113
|
-
- [
|
|
112
|
+
- [x] `create_calendar` / `update_calendar` / `delete_calendar` — calendar lifecycle (PR #266) ([#156](https://github.com/khuisman/mcp-gee-sweet/issues/156))
|
|
113
|
+
- [x] `add_calendar_to_list` / `remove_calendar_from_list` — subscribe/unsubscribe (PR #269) ([#157](https://github.com/khuisman/mcp-gee-sweet/issues/157))
|
|
114
114
|
- [ ] Calendar ACL — share a calendar with users or groups ([#158](https://github.com/khuisman/mcp-gee-sweet/issues/158))
|
|
115
115
|
- [ ] `list_all_events` — query all subscribed calendars in parallel _(decision needed — also tracked in Decisions Needed)_ ([#194](https://github.com/khuisman/mcp-gee-sweet/issues/194))
|
|
116
116
|
|
|
@@ -120,7 +120,7 @@ No new tools. Stabilize on what Tier 1 shipped before starting Tier 2 feature wo
|
|
|
120
120
|
- [ ] Comments API — list, add, resolve doc comments ([#151](https://github.com/khuisman/mcp-gee-sweet/issues/151))
|
|
121
121
|
- [ ] `create_named_range` / `create_bookmark` — anchor points for internal links ([#152](https://github.com/khuisman/mcp-gee-sweet/issues/152))
|
|
122
122
|
- [ ] Rowspan emitter — AST `Cell.rowspan` field exists but emitter does not yet merge rows ([#195](https://github.com/khuisman/mcp-gee-sweet/issues/195))
|
|
123
|
-
- [
|
|
123
|
+
- [x] Warn/error on mixed text + nested table in same cell — shipped as the fuller `Cell.children: list[Run | Table]` ordered model, also fixing trailing-text-after-nested-table ordering ([#275](https://github.com/khuisman/mcp-gee-sweet/issues/275)) in the same PR (PR #276) ([#108](https://github.com/khuisman/mcp-gee-sweet/issues/108))
|
|
124
124
|
- [ ] colspan/rowspan inside nested tables ([#109](https://github.com/khuisman/mcp-gee-sweet/issues/109))
|
|
125
125
|
- [ ] `find_in_doc` — search doc text and return match locations, parallel to `find_in_spreadsheet`; removes the manual index math currently needed to retrofit links (or any style) onto text already in a doc, raised while fixing #248 ([#262](https://github.com/khuisman/mcp-gee-sweet/issues/262))
|
|
126
126
|
|
|
@@ -135,7 +135,8 @@ No new tools. Stabilize on what Tier 1 shipped before starting Tier 2 feature wo
|
|
|
135
135
|
- [x] Drive Activity API — file change history ([#72](https://github.com/khuisman/mcp-gee-sweet/issues/72))
|
|
136
136
|
|
|
137
137
|
**Infrastructure**
|
|
138
|
-
- [
|
|
138
|
+
- [x] Harden concurrent-session access — fail-open on cache read/write errors, `busy_timeout` (PR #280) ([#234](https://github.com/khuisman/mcp-gee-sweet/issues/234))
|
|
139
|
+
- [x] Cache reliability & configurability — runtime-configurable TTL, smarter invalidation for shared files (PR #284) ([#99](https://github.com/khuisman/mcp-gee-sweet/issues/99))
|
|
139
140
|
- [ ] Async tool execution — `asyncio.gather()` for parallel Google API calls; establishes the project's parallel-call pattern, which [#194](https://github.com/khuisman/mcp-gee-sweet/issues/194) should reuse rather than introducing its own `ThreadPoolExecutor` ([#183](https://github.com/khuisman/mcp-gee-sweet/issues/183))
|
|
140
141
|
|
|
141
142
|
**Documentation**
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/docs/emitter.py
RENAMED
|
@@ -199,24 +199,20 @@ def fill_tables(docs_service, doc_id: str, tables: list[Table]) -> None:
|
|
|
199
199
|
3. Fill plain cells (no nested table) — insertText + updateTextStyle, high→low
|
|
200
200
|
4. Fill cells with a nested table, segment by segment (see
|
|
201
201
|
_fill_nested_cell_content) — text and tables in true source order,
|
|
202
|
-
recursing into any depth of further nesting
|
|
202
|
+
recursing into any depth of further nesting. Each nested table's own
|
|
203
|
+
merges are handled by _fill_table_fully, mirroring phase 2 above (#109).
|
|
203
204
|
5. Emit updateTableColumnProperties for tables with col_widths
|
|
204
205
|
6. If any cell has Phase 3 style fields: emit updateTableCellStyle
|
|
205
206
|
|
|
206
|
-
Nested table limitations: no
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
207
|
+
Nested table limitations: no col_widths inside nested tables. colspan/
|
|
208
|
+
rowspan (#109) and text sharing a cell with one or more nested tables
|
|
209
|
+
(#108, #275) are both fully supported, at any nesting depth — each run of
|
|
210
|
+
text, merge, and nested table renders in the same order and shape it had
|
|
211
|
+
in the source.
|
|
210
212
|
"""
|
|
211
213
|
if not tables:
|
|
212
214
|
return
|
|
213
215
|
|
|
214
|
-
has_merges = any(
|
|
215
|
-
cell.colspan > 1 or cell.rowspan > 1
|
|
216
|
-
for table in tables
|
|
217
|
-
for row in table.rows
|
|
218
|
-
for cell in row.cells
|
|
219
|
-
)
|
|
220
216
|
has_nested = any(
|
|
221
217
|
any(isinstance(child, Table) for child in cell.children)
|
|
222
218
|
for table in tables
|
|
@@ -238,14 +234,7 @@ def fill_tables(docs_service, doc_id: str, tables: list[Table]) -> None:
|
|
|
238
234
|
doc_tables = _top_level_tables(live_doc)
|
|
239
235
|
|
|
240
236
|
# Step 2: outer merges
|
|
241
|
-
|
|
242
|
-
merge_requests = _build_merge_requests(doc_tables, tables)
|
|
243
|
-
if merge_requests:
|
|
244
|
-
docs_service.documents().batchUpdate(
|
|
245
|
-
documentId=doc_id, body={"requests": merge_requests}
|
|
246
|
-
).execute()
|
|
247
|
-
live_doc = docs_service.documents().get(documentId=doc_id).execute()
|
|
248
|
-
doc_tables = _top_level_tables(live_doc)
|
|
237
|
+
doc_tables = _apply_merges(docs_service, doc_id, doc_tables, tables, _top_level_tables)
|
|
249
238
|
|
|
250
239
|
# Step 3: fill plain cells (no nested table) in one bulk batch
|
|
251
240
|
fill_requests = _build_fill_requests(doc_tables, tables)
|
|
@@ -552,12 +541,13 @@ def _fill_children_recursive(
|
|
|
552
541
|
|
|
553
542
|
|
|
554
543
|
def _fill_table_fully(docs_service, doc_id: str, resolve, ast_table: Table) -> None:
|
|
555
|
-
"""Fill one (typically just-inserted) table's cells:
|
|
556
|
-
cells with a further nested table via the
|
|
544
|
+
"""Fill one (typically just-inserted) table's cells: merges first, then
|
|
545
|
+
plain cells in bulk, then cells with a further nested table via the
|
|
546
|
+
recursive round algorithm.
|
|
557
547
|
|
|
558
548
|
Every nested table needs this — not just ones with further nesting — since
|
|
559
|
-
a nested table's own plain-text cells are never touched by the
|
|
560
|
-
|
|
549
|
+
a nested table's own plain-text cells (and merges) are never touched by the
|
|
550
|
+
top-level fill_tables passes (those only see the outer tables list).
|
|
561
551
|
"""
|
|
562
552
|
live_doc = docs_service.documents().get(documentId=doc_id).execute()
|
|
563
553
|
doc_tables = resolve(live_doc)
|
|
@@ -569,6 +559,8 @@ def _fill_table_fully(docs_service, doc_id: str, resolve, ast_table: Table) -> N
|
|
|
569
559
|
)
|
|
570
560
|
return
|
|
571
561
|
|
|
562
|
+
doc_tables = _apply_merges(docs_service, doc_id, doc_tables, [ast_table], resolve)
|
|
563
|
+
|
|
572
564
|
fill_requests = _build_fill_requests(doc_tables, [ast_table])
|
|
573
565
|
if fill_requests:
|
|
574
566
|
docs_service.documents().batchUpdate(
|
|
@@ -582,6 +574,48 @@ def _fill_table_fully(docs_service, doc_id: str, resolve, ast_table: Table) -> N
|
|
|
582
574
|
_fill_children_recursive(docs_service, doc_id, resolve, [ast_table], _doc_tables=doc_tables)
|
|
583
575
|
|
|
584
576
|
|
|
577
|
+
def _apply_merges(
|
|
578
|
+
docs_service, doc_id: str, doc_tables: list[dict], ast_tables: list[Table], resolve
|
|
579
|
+
) -> list[dict]:
|
|
580
|
+
"""Emit mergeTableCells for any ast_tables cell with colspan/rowspan > 1,
|
|
581
|
+
re-fetch, and return the updated doc_tables — or doc_tables unchanged if
|
|
582
|
+
nothing needed merging.
|
|
583
|
+
|
|
584
|
+
mergeTableCells doesn't shift character indices or delete content (covered
|
|
585
|
+
cells remain physical entries in the doc tree), so doc_tables — already
|
|
586
|
+
successfully resolved by the caller — is expected to resolve again right
|
|
587
|
+
after. If it doesn't, the live doc no longer matches the model used to
|
|
588
|
+
build the merge requests just executed; silently returning here would mean
|
|
589
|
+
every already-merged table renders with zero content and no operator-
|
|
590
|
+
visible signal, so raise instead of skipping the fill.
|
|
591
|
+
"""
|
|
592
|
+
has_merges = any(
|
|
593
|
+
cell.colspan > 1 or cell.rowspan > 1
|
|
594
|
+
for table in ast_tables
|
|
595
|
+
for row in table.rows
|
|
596
|
+
for cell in row.cells
|
|
597
|
+
)
|
|
598
|
+
if not has_merges:
|
|
599
|
+
return doc_tables
|
|
600
|
+
|
|
601
|
+
merge_requests = _build_merge_requests(doc_tables, ast_tables)
|
|
602
|
+
if not merge_requests:
|
|
603
|
+
return doc_tables
|
|
604
|
+
|
|
605
|
+
docs_service.documents().batchUpdate(
|
|
606
|
+
documentId=doc_id, body={"requests": merge_requests}
|
|
607
|
+
).execute()
|
|
608
|
+
live_doc = docs_service.documents().get(documentId=doc_id).execute()
|
|
609
|
+
updated = resolve(live_doc)
|
|
610
|
+
if len(updated) < len(ast_tables) or any(t is None for t in updated):
|
|
611
|
+
raise RuntimeError(
|
|
612
|
+
f"_apply_merges: table(s) vanished from doc {doc_id} immediately after "
|
|
613
|
+
"mergeTableCells, which does not itself shift indices or delete content — "
|
|
614
|
+
"refusing to silently skip the fill and leave the merged table empty."
|
|
615
|
+
)
|
|
616
|
+
return updated
|
|
617
|
+
|
|
618
|
+
|
|
585
619
|
def _build_phantom_set(ast_table: Table) -> set[tuple[int, int]]:
|
|
586
620
|
"""Return (row, col) logical positions covered by rowspan from a cell in an earlier row."""
|
|
587
621
|
phantom: set[tuple[int, int]] = set()
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from unittest.mock import MagicMock
|
|
4
4
|
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
5
7
|
from mcp_gee_sweet.tools import docs as docs_module
|
|
6
8
|
from mcp_gee_sweet.tools.docs.ast import (
|
|
7
9
|
Cell,
|
|
@@ -11,6 +13,7 @@ from mcp_gee_sweet.tools.docs.ast import (
|
|
|
11
13
|
)
|
|
12
14
|
from mcp_gee_sweet.tools.docs.content import _html_to_doc_requests
|
|
13
15
|
from mcp_gee_sweet.tools.docs.emitter import (
|
|
16
|
+
_apply_merges,
|
|
14
17
|
_ast_cell_to_doc_cell,
|
|
15
18
|
_build_fill_requests,
|
|
16
19
|
_cell_para_start_for_cursor,
|
|
@@ -590,6 +593,12 @@ class FakeDoc:
|
|
|
590
593
|
elif "insertTable" in req:
|
|
591
594
|
it = req["insertTable"]
|
|
592
595
|
self.insert_table(it["location"]["index"], it["rows"], it["columns"])
|
|
596
|
+
elif "mergeTableCells" in req:
|
|
597
|
+
# Real mergeTableCells doesn't delete content or shift indices —
|
|
598
|
+
# covered cells remain physical entries (see _physical_to_ast_indices) —
|
|
599
|
+
# so this simulator has nothing to mutate; the fill step already knows
|
|
600
|
+
# to skip covered cells based on the AST's own colspan/rowspan.
|
|
601
|
+
pass
|
|
593
602
|
|
|
594
603
|
def get(self) -> dict:
|
|
595
604
|
idx = 0
|
|
@@ -723,6 +732,117 @@ class TestFillNestedCellContent:
|
|
|
723
732
|
assert doc.describe_cell(0, 0, 0) == ["Para:After"]
|
|
724
733
|
|
|
725
734
|
|
|
735
|
+
# ---------------------------------------------------------------------------
|
|
736
|
+
# #109 — colspan/rowspan on cells inside a nested table
|
|
737
|
+
# ---------------------------------------------------------------------------
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
class TestNestedTableMerges:
|
|
741
|
+
"""A nested table's own cells may have colspan/rowspan > 1 — _fill_table_fully
|
|
742
|
+
must emit mergeTableCells for them, mirroring the outer-table merge phase in
|
|
743
|
+
fill_tables(), instead of silently leaving an unmerged shell (#109)."""
|
|
744
|
+
|
|
745
|
+
def _run(self, outer_cell: Cell) -> tuple[FakeDoc, list[list[dict]]]:
|
|
746
|
+
doc = FakeDoc()
|
|
747
|
+
doc.content = [TableNode(1, 1)]
|
|
748
|
+
ast_table = Table(rows=[Row(cells=[outer_cell])])
|
|
749
|
+
|
|
750
|
+
svc = MagicMock()
|
|
751
|
+
svc.documents.return_value.get.return_value.execute.side_effect = lambda: doc.get()
|
|
752
|
+
|
|
753
|
+
batches: list[list[dict]] = []
|
|
754
|
+
|
|
755
|
+
def do_batch(documentId, body):
|
|
756
|
+
batches.append(body["requests"])
|
|
757
|
+
doc.batch_update(body["requests"])
|
|
758
|
+
m = MagicMock()
|
|
759
|
+
m.execute.return_value = {}
|
|
760
|
+
return m
|
|
761
|
+
|
|
762
|
+
svc.documents.return_value.batchUpdate.side_effect = do_batch
|
|
763
|
+
_fill_nested_cell_content(svc, "doc1", [ast_table])
|
|
764
|
+
return doc, batches
|
|
765
|
+
|
|
766
|
+
def test_colspan_merge_emitted_and_only_anchor_cell_filled(self):
|
|
767
|
+
inner = _table(_row(_cell("Header", colspan=2)))
|
|
768
|
+
doc, batches = self._run(Cell(children=[inner]))
|
|
769
|
+
|
|
770
|
+
merge_reqs = [r for batch in batches for r in batch if "mergeTableCells" in r]
|
|
771
|
+
assert len(merge_reqs) == 1
|
|
772
|
+
tr = merge_reqs[0]["mergeTableCells"]["tableRange"]
|
|
773
|
+
assert tr["rowSpan"] == 1
|
|
774
|
+
assert tr["columnSpan"] == 2
|
|
775
|
+
|
|
776
|
+
nested_table = next(
|
|
777
|
+
c for c in doc.content[0].rows[0][0].content if isinstance(c, TableNode)
|
|
778
|
+
)
|
|
779
|
+
assert nested_table.rows[0][0].content[0].text == "Header"
|
|
780
|
+
assert nested_table.rows[0][1].content[0].text == "" # colspan phantom — untouched
|
|
781
|
+
|
|
782
|
+
def test_rowspan_merge_emitted_and_only_anchor_cell_filled(self):
|
|
783
|
+
inner = _table(_row(_cell("A", rowspan=2), _cell("B")), _row(_cell("C")))
|
|
784
|
+
doc, batches = self._run(Cell(children=[inner]))
|
|
785
|
+
|
|
786
|
+
merge_reqs = [r for batch in batches for r in batch if "mergeTableCells" in r]
|
|
787
|
+
assert len(merge_reqs) == 1
|
|
788
|
+
tr = merge_reqs[0]["mergeTableCells"]["tableRange"]
|
|
789
|
+
assert tr["rowSpan"] == 2
|
|
790
|
+
assert tr["columnSpan"] == 1
|
|
791
|
+
|
|
792
|
+
nested_table = next(
|
|
793
|
+
c for c in doc.content[0].rows[0][0].content if isinstance(c, TableNode)
|
|
794
|
+
)
|
|
795
|
+
assert nested_table.rows[0][0].content[0].text == "A"
|
|
796
|
+
assert nested_table.rows[0][1].content[0].text == "B"
|
|
797
|
+
assert nested_table.rows[1][0].content[0].text == "" # rowspan phantom — untouched
|
|
798
|
+
assert nested_table.rows[1][1].content[0].text == "C"
|
|
799
|
+
|
|
800
|
+
def test_no_merge_call_when_nested_table_has_no_spans(self):
|
|
801
|
+
inner = _table(_row(_cell("A"), _cell("B")))
|
|
802
|
+
_doc, batches = self._run(Cell(children=[inner]))
|
|
803
|
+
assert not any("mergeTableCells" in r for batch in batches for r in batch)
|
|
804
|
+
|
|
805
|
+
def test_merge_precedes_fill_in_batch_order(self):
|
|
806
|
+
# Pin the documented phase order in _fill_table_fully — merge runs (and
|
|
807
|
+
# is re-fetched) before the bulk text fill, mirroring fill_tables()'s own
|
|
808
|
+
# outer-table phase order. (The batch preceding both is the round
|
|
809
|
+
# algorithm's own insertTable for the nested table's shell.)
|
|
810
|
+
inner = _table(_row(_cell("Header", colspan=2)))
|
|
811
|
+
_doc, batches = self._run(Cell(children=[inner]))
|
|
812
|
+
|
|
813
|
+
merge_idx = next(
|
|
814
|
+
i for i, batch in enumerate(batches) if any("mergeTableCells" in r for r in batch)
|
|
815
|
+
)
|
|
816
|
+
fill_idx = next(
|
|
817
|
+
i for i, batch in enumerate(batches) if any("insertText" in r for r in batch)
|
|
818
|
+
)
|
|
819
|
+
assert merge_idx < fill_idx
|
|
820
|
+
|
|
821
|
+
def test_raises_when_table_vanishes_from_resolve_after_merge(self):
|
|
822
|
+
# mergeTableCells doesn't shift indices or delete content, so a resolve
|
|
823
|
+
# failure right after one is an anomaly, not a normal "not there yet"
|
|
824
|
+
# case — it must surface loudly rather than silently drop the fill and
|
|
825
|
+
# leave the just-merged table empty with no operator-visible signal.
|
|
826
|
+
ast_table = _table(_row(_cell("Header", colspan=2)))
|
|
827
|
+
doc_table = {
|
|
828
|
+
"tableRows": [
|
|
829
|
+
{
|
|
830
|
+
"tableCells": [
|
|
831
|
+
{"startIndex": 3, "content": [{"startIndex": 4}]},
|
|
832
|
+
{"startIndex": 6, "content": [{"startIndex": 7}]},
|
|
833
|
+
]
|
|
834
|
+
}
|
|
835
|
+
]
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
svc = MagicMock()
|
|
839
|
+
svc.documents.return_value.batchUpdate.return_value.execute.return_value = {}
|
|
840
|
+
svc.documents.return_value.get.return_value.execute.return_value = {"body": {"content": []}}
|
|
841
|
+
|
|
842
|
+
with pytest.raises(RuntimeError, match="vanished"):
|
|
843
|
+
_apply_merges(svc, "doc1", [doc_table], [ast_table], lambda _live_doc: [])
|
|
844
|
+
|
|
845
|
+
|
|
726
846
|
# ---------------------------------------------------------------------------
|
|
727
847
|
# #277 — a skipped degenerate table must not desync doc_tables/ast_tables
|
|
728
848
|
# ---------------------------------------------------------------------------
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/.claude/commands/cleanup-worktrees.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/.github/ISSUE_TEMPLATE/bug_report.md
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/.github/ISSUE_TEMPLATE/feature_request.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/decisions/decision-chart-theming.md
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/decisions/decision-composite-tools.md
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/decisions/decision-docs-formatting.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/decisions/decision-publishing.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/design/effectiveformat-spike.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/fixtures/tc-d195-create-doc.md
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/fixtures/tc-d196-create-doc.html
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/fixtures/tc-d213-dollar-escape.md
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/fixtures/tc-d226-heading-table.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/results/2026-07-04-drive-smoke.md
RENAMED
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/docs/qa/results/2026-07-04-sheets_read.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/docs/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/docs/content.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/docs/html_parser.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/docs/layout.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/docs/style.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/docs/tables.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/drive/__init__.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/drive/activity.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/drive/files.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/drive/sharing.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/drive/transfer.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/response_limits.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/sheets/__init__.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/sheets/data.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/sheets/helpers.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev82 → mcp_gee_sweet-0.7.0.dev84}/src/mcp_gee_sweet/tools/sheets/structure.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|