mcp-gee-sweet 0.9.1.dev182__tar.gz → 0.9.1.dev185__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.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/PKG-INFO +1 -1
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/design/module-history.md +1 -1
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/known-limitations.md +33 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/tests/drive_transfer.md +104 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/roadmap.md +1 -1
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/drive/transfer.py +477 -25
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/drive/test_transfer.py +569 -7
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/uv.lock +3 -3
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.claude/commands/cleanup-worktrees.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.claude/commands/merge-pr.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.claude/commands/notify-kai.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.claude/commands/notify-partner.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.claude/commands/orchestrator.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.claude/commands/prep-for-pr.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.claude/commands/qa-kickoff.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.claude/commands/release.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.claude/commands/retro.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.claude/commands/roadmap-status.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.claude/commands/start-worktree.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.claude/commands/team-member.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.claude/commands/verify-pr.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.claude/settings.json +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.claude/team-roles/amy.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.claude/team-roles/aziz.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.claude/team-roles/bob.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.claude/team-roles/dev.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.claude/team-roles/joy.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.claude/team-roles/kai.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.claude/team-roles/qa.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.dockerignore +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.github/dependabot.yml +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.github/workflows/ci.yml +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.github/workflows/docs.yml +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.github/workflows/publish-dev.yml +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.github/workflows/release.yml +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.gitignore +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.pre-commit-config.yaml +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/.python-version +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/CHANGELOG.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/CLAUDE.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/CODE_OF_CONDUCT.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/CONTRIBUTING.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/Dockerfile +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/LICENSE +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/Makefile +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/README.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/SECURITY.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docker-compose.yml +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/assets/logo.png +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/auth.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/client-setup.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/configuration.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-async-tool-execution.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-auth-status-limitation-categories.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-cache-invalidation.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-chart-theming.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-comments-first-class.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-composite-tools.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-condition-type-docstring-generation.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-converted-doc-change-detection.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-dev-team.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-docs-formatting.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-fork.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-gmail-domain.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-grid-data-size-cap.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-local-fs-test-harness.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-mcp-v2-resource-context-access.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-pillow-image-dependency.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-prompt-qa-role.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-publishing.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-release-docs-roles.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-repositioning.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-response-size-cap-generalization.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-response-size-cap-reevaluation-519.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-supports-all-drives-lint-check.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-sync-folder-recursion.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/decision-testing.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/decisions/index.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/design/blockquote-representation.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/design/borderless-table-columns.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/design/doc-to-markdown.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/design/docs-ast-pipeline.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/design/effectiveformat-spike.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/design/heading-anchor-resolution.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/design/image-conversion.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/design/index.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/design/markdown-support.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/design/phase3-theme-system.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/design.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/index.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/notes-read.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/README.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/fixtures/qa-fixture-pixel.png +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/fixtures/tc-d195-create-doc.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/fixtures/tc-d196-create-doc.html +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/fixtures/tc-d213-dollar-escape.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/fixtures/tc-d226-heading-table.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/fixtures/tc-doc102-image-conversion.html +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/fixtures/tc-doc103-image-conversion.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/fixtures/tc-doc104-nested-lists.html +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/fixtures/tc-doc105-nested-lists-gfm.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/fixtures/tc-doc106-nested-lists-4space.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/fixtures/tc-doc135-paragraph-boundary.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/fixtures/tc-doc136-interrupted-block-boundary.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/fixtures/tc-doc137-inline-hr-no-block-ancestor.html +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/fixtures/tc-doc138-nbsp-spacer-paragraph.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/fixtures/tc-doc141-mismatched-list-tags.html +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/fixtures/tc-doc142-heading-anchors.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/fixtures/tc-doc144-anchor-in-table-cell.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/fixtures/tc-doc145-anchor-on-first-element.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/fixtures.template.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/gmail-test-plan.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/operations.yaml +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/playwright_oauth.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/results/.gitkeep +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/results/2026-06-02.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/results/2026-06-14.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/results/2026-06-17.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/results/2026-06-18.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/results/2026-06-21-drive-discovery.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/results/2026-06-21-sheets-delete-clear.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/results/2026-06-28.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/results/2026-07-04-docs.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/results/2026-07-04-drive-smoke.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/results/2026-07-04-infra.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/results/2026-07-04-sheets_read.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/results/2026-07-04.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/results/2026-09-04.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/retro-v0.8.0.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/retro-v0.9.0.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/run.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/runs/README.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/runs/v0.8.0.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/runs/v0.8.1.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/runs/v0.9.0.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/setup.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/tests/calendar.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/tests/docs_content.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/tests/docs_layout.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/tests/docs_style.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/tests/docs_tables.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/tests/drive_activity.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/tests/drive_files.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/tests/drive_sharing.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/tests/gmail.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/tests/infra.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/tests/sheets_charts.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/tests/sheets_mgmt.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/tests/sheets_read.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/qa/tests/sheets_write.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/style-guide.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/docs/tools.md +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/mkdocs.yml +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/pyproject.toml +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/pyrightconfig.json +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/scripts/check_supports_all_drives.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/scripts/gen_tool_docs.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/scripts/oauth_setup.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/scripts/qa_gmail_fixtures.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/scripts/setup_team.sh +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/scripts/write_team_mcp_configs.sh +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/server.json +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/.env.template +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/__init__.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/auth.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/cache.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/http_transport.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/server.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/__init__.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/cache.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/calendar.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/concurrency.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/docs/__init__.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/docs/anchors.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/docs/ast.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/docs/ast_to_markdown.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/docs/comments.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/docs/content.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/docs/doc_to_ast.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/docs/editing.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/docs/emitter.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/docs/html_parser.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/docs/images.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/docs/indices.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/docs/layout.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/docs/markdown_export.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/docs/named_ranges.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/docs/style.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/docs/tables.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/drive/__init__.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/drive/activity.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/drive/files.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/drive/sharing.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/gmail.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/response_limits.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/sheets/__init__.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/sheets/data.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/sheets/helpers.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/src/mcp_gee_sweet/tools/sheets/structure.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/__init__.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/conftest.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/drive/__init__.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/drive/test_activity.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/drive/test_files.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/drive/test_sharing.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/integration/__init__.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/integration/conftest.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/integration/test_local_fs_harness.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/sheets/__init__.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/sheets/test_data.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/sheets/test_helpers.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/sheets/test_structure.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_auth.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_cache.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_calendar.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_check_supports_all_drives.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_concurrency.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_docs_anchors.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_docs_comments.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_docs_content.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_docs_core.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_docs_editing.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_docs_images.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_docs_indices.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_docs_layout.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_docs_markdown_export.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_docs_named_ranges.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_docs_style.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_docs_tables.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_gen_tool_docs.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_gmail.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_qa_gmail_fixtures.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_response_limits.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_server.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_server_json.py +0 -0
- {mcp_gee_sweet-0.9.1.dev182 → mcp_gee_sweet-0.9.1.dev185}/tests/test_team_mcp_configs.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: mcp-gee-sweet
|
|
3
|
-
Version: 0.9.1.
|
|
3
|
+
Version: 0.9.1.dev185
|
|
4
4
|
Summary: MCP server for Google Workspace — Sheets, Drive, Docs (HTML or Markdown), and Calendar.
|
|
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
|
|
@@ -12,7 +12,7 @@ Issue and PR numbers are as they were at the move (2026-09-29). Later history be
|
|
|
12
12
|
|
|
13
13
|
## `tools/drive/transfer.py`
|
|
14
14
|
|
|
15
|
-
`tools/drive/transfer.py` — upload/download/sync/export/revisions; `_upload_local_file` (the core behind the `upload_local_file` tool) is a module-level helper so `docs/images.py`'s `insert_local_images` can call it directly, imported cross-package the same way `docs/content.py` imports `_SA_QUOTA_ERROR` from `tools/drive/__init__.py`. `upload_local_file`'s `convert` param (#188) requests Drive's *native* import conversion — CSV/XLSX→Sheets, DOCX/MD/HTML→Docs, PPTX→Slides — by uploading with the source format's mimeType while setting the destination's `mimeType` metadata to the target Google Workspace type (`_CONVERT_MIME` maps extension → (source mime, target mime)). This is a different mechanism from `docs/content.py`'s `create_doc_from_file`, which parses the file locally and rebuilds it via Docs API requests instead of Drive's importer — don't conflate the two when adding new conversion paths. `sync_folder`'s `convert_markdown` param (#211) applies the same native-conversion mechanism to local `.md` files during sync uploads. Because the converted Doc keeps its `.md` name in Drive, `_sync_level`'s drive_map-building loop matches it back to the local `.md` file directly (bypassing the export_format-suffix scheme normal Workspace files need) so resyncs settle into "in sync" instead of re-uploading a duplicate every run — this also reopens matching for a drive-only converted Doc with no local counterpart and no `export_format` (previously unreachable, since such files were always excluded from `drive_map`) — the plan-building loop classifies that case as a `conflict` (a convert_markdown Doc has no reverse conversion) before it ever reaches the download branch, so the download branch's own `_EXPORT_MIME[None]` guard is defensive-only dead code in the current implementation, not a reachable path (confirmed live: TC-D221). There is no reverse conversion (Doc → markdown). A converted Doc is recognized on every later sync via a Drive `properties` marker (`_CONVERT_MARKDOWN_SOURCE_PROP`) stamped at creation time by both `_sync_level`'s own upload path and `_upload_local_file`'s `convert=True` path (#414 rounds 2-3) — recognition is deliberately independent of whether the *current* call passes `convert_markdown`/`convert`, since the Doc's own persisted property is what matters, not this call's flag. Both paths stamp through the shared `_convert_properties` helper, which keeps every value within Drive's 124-byte per-property cap: `_CONVERT_MARKDOWN_SOURCE_PROP` holds the raw name only when it fits, and the generic `_CONVERT_SOURCE_PROP` marker (#769) falls back to a `sha256:` digest. `_sync_level` used to stamp the raw name directly, so a `.md` name over 95 bytes failed `create()` with `403 propertyLengthLimitExceeded` after Drive had already made the Doc, leaving an orphan on every run (#805). Recognition goes through `_converted_md_source_name`, which accepts either key. The legacy markdown key is still honored, so Docs converted before #805 keep matching. The generic marker counts only for a `.md` source, and a digest is resolved against the Doc's own display name, which keeps its `.md` suffix. Because of that, a digest-marked Doc (a name over 103 UTF-8 bytes, `.md` included) stops matching if it's renamed in Drive; see `docs/known-limitations.md`. Three further edge cases from that round-3 review shipped as issue #422: the `drive_map`-building loop now detects (rather than silently resolving) a name collision between a plain file and a converted Doc, reporting it under `failed` instead of letting whichever is listed last win the slot and make the other invisible to the sync; `_upload_local_file` now stamps `modifiedTime` from the local file's mtime on a converted Doc (via `create()` then a metadata-only `update()`, mirroring `_sync_level`'s own create() branch, since Drive's native import-conversion overwrites the create()-time value once conversion finishes) so a subsequent `sync_folder` lands in `skipped` rather than `conflicts`; and the drive-only branch now checks `direction` before `_is_converted_md`, so `direction='upload'` reports a plain `skip` like any other drive-only file instead of always reporting `conflict`. PR #433's own review round found the collision guard needed to go further: it originally only fired when the two colliding entries' `_is_converted_md` differed (a plain file vs. a converted Doc, #422's own reported shape), silently overwriting a same-type collision (two plain files, or two converted Docs, sharing a name) the exact same way; it now fires on any duplicate name regardless of type. The collision case is also now routed through the normal `plan`/`action` machinery (a new `"collision"` action, mapped to `conflict` during `dry_run` and to a real `failed` entry — `kind: "collision_fail"` — once execution is actually attempted) rather than writing to `failed` directly from the `drive_map`-building loop: that direct write both leaked into `failed` during `dry_run` (breaking the "dry_run never reports failures, only previews" invariant the pre-existing folder-collision failure path already upholds) and silently dropped a *local* file sharing the colliding name from every result list, since the plan loop's bare `continue` skipped it with zero acknowledgment. Separately, `_upload_local_file`'s `create()`+`update()` pair now shares one broad `except Exception` returning `{"error": ...}` (matching `_sync_level._run_one`'s own create+update pattern) instead of only catching quota `HttpError`s and letting everything else — including a failure in the new metadata-only restamp `update()`, after `create()` already succeeded — propagate uncaught to `upload_local_file`, which has no try/except of its own. Issue #420 found that same broad `except` still lost information on the restamp-failure path specifically: a bare `{"error": ...}` (or, for `_sync_level._run_one`'s own mirrored pair, a bare `upload_fail`) gave no way to find the Doc that `create()` had already genuinely made in Drive before the restamp failed — an untracked orphan. Fixed in both places by giving the restamp `update()` its own dedicated `try`/`except` that reports the created file's own ID alongside the error — `_upload_local_file` as `{"error": ..., "fileId": ...}`, `_run_one` as `{"kind": "upload_fail", "name": ..., "error": ..., "fileId": ...}` — while a `create()` failure still reports no `fileId`, correctly, since nothing was created. The two functions structure this differently: `_run_one`'s restamp `try`/`except` sits nested inside its one broad outer `try` (which also covers `create()` and the `if name in drive_map` update branch above it), while `_upload_local_file`'s is a separate, sequential `try`/`except` reached only after the `create()` `try`/`except` has already returned successfully — not nested inside it (PR #645 QA round 1 caught this file's prose originally describing both as nested). PR #650 factored the two restamp-failure branches' shared shape — the `created Drive file {id!r} but failed to restamp its modifiedTime` message plus `storageQuotaExceeded`→`_SA_QUOTA_ERROR` rendering — into a module-level `_restamp_failure_result(file_id, exc)` returning `{"error": ..., "fileId": ...}`; `_run_one` spreads that and layers its own `kind`/`name` keys on top. Before this only the `create()` except recognized a quota error — the restamp except caught bare `Exception` and would have leaked a raw `str(e)` for a quota failure there, unlike every other quota-error site in the file. `sync_folder`'s and `upload_local_folder`'s own `failed`-list aggregation steps (which previously narrowed each per-item result down to just `{name, error}`) were updated to carry `fileId` through when present, or the fix would have been invisible by the time a caller saw the result — and, since a `fileId`-carrying failure means `create()` genuinely changed the folder's contents, all three call sites (`_sync_level`, the `upload_local_file` tool, `upload_local_folder`) also had to treat that case as cache-dirtying, the same as a real success: none of them originally did (same QA round), since a `fileId`-carrying result still fell into each site's own success-only cache-invalidation gate's "false" branch. `sync_folder`'s `use_checksum` param (#274) adds a content check ahead of `_sync_level`'s mtime comparison for names present on both sides: when the local file's md5 (`_local_md5`, streamed in 1 MiB chunks) matches Drive's own `md5Checksum` (now fetched by `_list_drive_children`), the pair is treated as in sync regardless of `modifiedTime` drift — this is what actually fixes `upload_local_file` not stamping `modifiedTime` (unlike `_sync_level`'s own upload path) causing a spurious re-download of identical content on the next sync, not just a same-mtime coincidence. Only applies where Drive returns a real `md5Checksum` — Workspace files and `convert_markdown` Docs have none and always fall back to mtime-only comparison, flag or no flag. A checksum *mismatch* doesn't force any decision by itself; it just falls through to the same mtime-based upload/download/conflict logic used when `use_checksum=False`, since content differing doesn't by itself say which side is newer. PR #472's own review round found four issues in this first version: the checksum comparison ran unconditionally, before the (cheap) mtime diff, including for pairs already within `_SYNC_MTIME_TOLERANCE` and even during `dry_run=True` — fixed by moving the mtime diff first and gating the hash read on `use_checksum and not dry_run and abs(diff) > _SYNC_MTIME_TOLERANCE`, so an already-in-sync pair or a dry_run preview never pays for a read it can't change the outcome of (#716 later dropped the `abs(diff)` part of that gate; see below); `_local_md5` had no try/except unlike every other per-item operation in this module, so a file that became unreadable (deleted, permission-denied) between the directory scan and the hash read raised uncaught out of the whole `sync_folder` call instead of degrading to one `failed` entry — fixed via a new plan action (now `"local_read_fail"`, which since PR #841 also covers the plan's own `stat()`) mirroring the existing `"collision"` pattern; and `_upload_local_file`'s plain (non-`convert`) branch was identified as the actual root cause worth fixing directly — it never stamped `modifiedTime` the way the `convert_mime` branch three lines above already did (and `#422` already established for that branch), so a file uploaded through it always read as "Drive newer" on the very next `sync_folder` regardless of content — now stamped unconditionally in the `create()` body for every upload (no follow-up `update()` needed for the plain case, since Drive only overwrites `modifiedTime` when import-conversion is involved). `use_checksum` remains valuable beyond that root-cause fix for causes of drift the mtime stamp doesn't cover (e.g. a local overwrite that happens to preserve mtime). One review finding was accepted as a known tradeoff rather than fixed: a checksum mismatch that resolves to `'upload'` reads the local file twice (once to hash, once via `MediaFileUpload` for the actual transfer) — avoiding it would mean buffering the whole file in memory to share across both reads, a worse tradeoff for large files than one extra (likely page-cache-warm) disk read. Issue #659 covered the inverse of `use_checksum`'s target failure mode — **false sameness**, where the two mtimes agree but the content has diverged (classic cause: a rename-in-place, since `mv` preserves mtime), which short-circuited to `"in sync"` before the hash block (then guarded on `abs(diff) > _SYNC_MTIME_TOLERANCE`) could run, so `use_checksum=True` couldn't reach it by construction (until #716, below). Fixed with a near-free discriminator in `_sync_level`'s both-sides branch: `size` is now also fetched by `_list_drive_children`, and a within-tolerance mtime pair whose local `st_size` disagrees with Drive's reported `size` is not skipped — it is reported as a `conflict` for **every** direction, never an auto-transfer (PR #712 QA round 1): the mtimes agree so recency is unknown, and the `diff`-based branches already report `conflict` rather than overwrite a target they *can* tell is newer, so this branch — which knows less — must be at least as cautious. The byte-size check is one `stat()` and no read, so unlike the hash block it runs during `dry_run` too. Non-Workspace files only (`size`, like `md5Checksum`, is absent for Workspace/`convert_markdown` files). A same-size content edit that also preserves mtime still reads as `"in sync"` under the default `use_checksum=False`. Issue #716 closed that gap for an explicit `use_checksum=True`: it now hashes every both-sides pair with a Drive `md5Checksum`, within tolerance or not (a size mismatch is still settled from the `stat()` alone, first). A within-tolerance hash mismatch is a `conflict` for every direction, the same as the size mismatch; both come from `_diverged_same_mtime_step`. The hashes run after the plan loop, concurrently, capped at `_SYNC_HASH_CONCURRENCY`, each filling in a placeholder step that holds the mtime-only fallback from `_mtime_step`. `dry_run` still never hashes, and an affected step's reason ends in `(checksum not verified in dry_run)`, since the real run can decide differently. `upload_local_folder` (#411) used to have its own independent inline upload loop with no `convert` param, unreachable from `_upload_local_file`'s conversion mechanism — bulk-importing a folder of CSV/DOCX/PPTX into native Workspace formats required falling back to per-file `upload_local_file` calls. Fixed by routing each candidate through `_upload_local_file` itself, gaining the same `convert` param and its semantics (an unsupported extension lands in `failed` rather than uploading as-is, matching `upload_local_file`'s own error-not-silent-fallback behavior). The existing bulk `skip_if_exists` pre-check (one `list()` call for the whole folder, not one per file — TC-D100) is preserved rather than replaced by `_upload_local_file`'s own per-call check: the folder loop now fetches each existing file's `mimeType` alongside its name in that same single call, decides skip-worthiness itself (name-only match when `convert=False`; name *and* target-mimeType match when `convert=True`, mirroring `_upload_local_file`'s own convert-aware duplicate check), and then calls `_upload_local_file` with `skip_if_exists=False` for whatever survives — so existence is never checked twice. `sync_folder(dry_run=True)`'s response used to duplicate every `skip`/`conflict` name into both a flat list (`skipped`/`conflicts`) and a full `{name, action, reason}` entry in `actions`, deterministically tripping the response-size cap on a moderately-sized, mostly-in-sync folder even though `actions` alone (name + action-type + reason for every item, including upload/download entries the flat lists never populated during `dry_run` in the first place) was already a complete, non-redundant picture (issue #512). Fixed by leaving `uploaded`/`downloaded`/`skipped`/`conflicts`/`failed` empty during `dry_run` rather than trimming or toggling `actions` itself. Separately, `sync_folder` previously had no response-size-cap bypass at all (`local_path_available=False`) unlike every other capped tool except `export_file`/`list_file_activity` (both deliberately, per the decision doc below) — its own `local_path` param already means the sync destination, so it can't double as a dump target the way `get_sheet_data`'s `local_path` does. Fixed by adding a separate `result_local_path` param following the same unconditional-if-passed `write_capped_result_to_disk` pattern as every other capped tool with a bypass; `enforce_response_size_cap` gained a `local_path_param` override so its error message names the right parameter instead of hardcoding `local_path`. PR #518's own QA review round live-caught a gap in that first version: `result_local_path` had no guard against pointing at or inside `local_path` — since `local_path` is scanned as sync input on every call (unlike every other capped tool's local_path/result_local_path, which are pure output destinations), a manifest written there showed up as a new local-only file on the very next sync and would have been uploaded to Drive on a real run. Fixed by rejecting the call up front (`ValueError`, before any Drive API call) when `Path(result_local_path).resolve()` equals or is a descendant of the resolved `local_path`. `upload_file`'s `convert_to_doc` param implements the identical native-import-conversion trick as `_upload_local_file`'s `convert`/`_CONVERT_MIME` above, independently — raw text/markdown/html content instead of a local file, always targeting a Doc since there's no file extension to dispatch Sheets/Slides on — with no cross-reference between the two (issue #412). Fixed by sharing `_GOOGLE_DOC_MIME` as the single source of truth for the target mimeType literal both mechanisms write, plus explicit docstring cross-references at both call sites; the two code paths themselves stayed separate rather than being merged, since their actual upload semantics (raw content vs. local file, single target vs. extension-dispatched) differ enough that unifying them would cost more complexity than it'd save. Issue #424 (folded #421's two extra findings in) was pure hardening on this same `convert_markdown` matching logic, no behavior change: the synthetic `"_is_converted_md"` key spread into every `drive_map` entry (previously read directly at 5 separate call sites — the pattern PR #433's own review round flagged as having already caused one missed site) was replaced with `_is_converted_md_entry(f)`, a helper that recomputes the classification on demand from the resource's own `mimeType`/`properties` rather than caching it — `drive_map` is now a plain passthrough of Drive's file resource shape again, and every call site (the build loop, both plan conflict branches, the upload-execution reimport check, the download-execution guard) shares one place to agree with when this classification's rules change. The three independently-hand-written "no reverse conversion" messages (drive-only plan conflict, drive-newer plan conflict, download-execution guard) now interpolate a shared `_NO_REVERSE_CONVERSION_CLAUSE` constant instead. The dead `assert convert_source is not None` fell out naturally once the build loop's `local_name` derivation was rewritten against the new helper. The upload-execution branch's `_CONVERT_MIME[".md"]` unpack moved inside the `convert_this` check so it's only looked up for an actual `.md` conversion, not unconditionally for every upload. The doubled create()+update() latency on every new converted-`.md` upload (both here and in `_upload_local_file`'s `convert=True` path) was reviewed and left as an accepted tradeoff, not fixed — Drive's import-conversion finishes asynchronously after `create()` already returns, so there's no single call that can request both the upload and the post-conversion `modifiedTime` restamp together, and polling for conversion completion before restamping would trade one predictable extra call for an unpredictable number of slower ones.
|
|
15
|
+
`tools/drive/transfer.py` — upload/download/sync/export/revisions; `_upload_local_file` (the core behind the `upload_local_file` tool) is a module-level helper so `docs/images.py`'s `insert_local_images` can call it directly, imported cross-package the same way `docs/content.py` imports `_SA_QUOTA_ERROR` from `tools/drive/__init__.py`. `upload_local_file`'s `convert` param (#188) requests Drive's *native* import conversion — CSV/XLSX→Sheets, DOCX/MD/HTML→Docs, PPTX→Slides — by uploading with the source format's mimeType while setting the destination's `mimeType` metadata to the target Google Workspace type (`_CONVERT_MIME` maps extension → (source mime, target mime)). This is a different mechanism from `docs/content.py`'s `create_doc_from_file`, which parses the file locally and rebuilds it via Docs API requests instead of Drive's importer — don't conflate the two when adding new conversion paths. `sync_folder`'s `convert_markdown` param (#211) applies the same native-conversion mechanism to local `.md` files during sync uploads. Because the converted Doc keeps its `.md` name in Drive, `_sync_level`'s drive_map-building loop matches it back to the local `.md` file directly (bypassing the export_format-suffix scheme normal Workspace files need) so resyncs settle into "in sync" instead of re-uploading a duplicate every run — this also reopens matching for a drive-only converted Doc with no local counterpart and no `export_format` (previously unreachable, since such files were always excluded from `drive_map`) — the plan-building loop classifies that case as a `conflict` (a convert_markdown Doc has no reverse conversion) before it ever reaches the download branch, so the download branch's own `_EXPORT_MIME[None]` guard is defensive-only dead code in the current implementation, not a reachable path (confirmed live: TC-D221). There is no reverse conversion (Doc → markdown). A converted Doc is recognized on every later sync via a Drive `properties` marker (`_CONVERT_MARKDOWN_SOURCE_PROP`) stamped at creation time by both `_sync_level`'s own upload path and `_upload_local_file`'s `convert=True` path (#414 rounds 2-3) — recognition is deliberately independent of whether the *current* call passes `convert_markdown`/`convert`, since the Doc's own persisted property is what matters, not this call's flag. Both paths stamp through the shared `_convert_properties` helper, which keeps every value within Drive's 124-byte per-property cap: `_CONVERT_MARKDOWN_SOURCE_PROP` holds the raw name only when it fits, and the generic `_CONVERT_SOURCE_PROP` marker (#769) falls back to a `sha256:` digest. `_sync_level` used to stamp the raw name directly, so a `.md` name over 95 bytes failed `create()` with `403 propertyLengthLimitExceeded` after Drive had already made the Doc, leaving an orphan on every run (#805). Recognition goes through `_converted_md_source_name`, which accepts either key. The legacy markdown key is still honored, so Docs converted before #805 keep matching. The generic marker counts only for a `.md` source, and a digest is resolved against the Doc's own display name, which keeps its `.md` suffix. Because of that, a digest-marked Doc (a name over 103 UTF-8 bytes, `.md` included) stops matching if it's renamed in Drive; see `docs/known-limitations.md`. Three further edge cases from that round-3 review shipped as issue #422: the `drive_map`-building loop now detects (rather than silently resolving) a name collision between a plain file and a converted Doc, reporting it under `failed` instead of letting whichever is listed last win the slot and make the other invisible to the sync; `_upload_local_file` now stamps `modifiedTime` from the local file's mtime on a converted Doc (via `create()` then a metadata-only `update()`, mirroring `_sync_level`'s own create() branch, since Drive's native import-conversion overwrites the create()-time value once conversion finishes) so a subsequent `sync_folder` lands in `skipped` rather than `conflicts`; and the drive-only branch now checks `direction` before `_is_converted_md`, so `direction='upload'` reports a plain `skip` like any other drive-only file instead of always reporting `conflict`. PR #433's own review round found the collision guard needed to go further: it originally only fired when the two colliding entries' `_is_converted_md` differed (a plain file vs. a converted Doc, #422's own reported shape), silently overwriting a same-type collision (two plain files, or two converted Docs, sharing a name) the exact same way; it now fires on any duplicate name regardless of type. The collision case is also now routed through the normal `plan`/`action` machinery (a new `"collision"` action, mapped to `conflict` during `dry_run` and to a real `failed` entry — `kind: "collision_fail"` — once execution is actually attempted) rather than writing to `failed` directly from the `drive_map`-building loop: that direct write both leaked into `failed` during `dry_run` (breaking the "dry_run never reports failures, only previews" invariant the pre-existing folder-collision failure path already upholds) and silently dropped a *local* file sharing the colliding name from every result list, since the plan loop's bare `continue` skipped it with zero acknowledgment. Separately, `_upload_local_file`'s `create()`+`update()` pair now shares one broad `except Exception` returning `{"error": ...}` (matching `_sync_level._run_one`'s own create+update pattern) instead of only catching quota `HttpError`s and letting everything else — including a failure in the new metadata-only restamp `update()`, after `create()` already succeeded — propagate uncaught to `upload_local_file`, which has no try/except of its own. Issue #420 found that same broad `except` still lost information on the restamp-failure path specifically: a bare `{"error": ...}` (or, for `_sync_level._run_one`'s own mirrored pair, a bare `upload_fail`) gave no way to find the Doc that `create()` had already genuinely made in Drive before the restamp failed — an untracked orphan. Fixed in both places by giving the restamp `update()` its own dedicated `try`/`except` that reports the created file's own ID alongside the error — `_upload_local_file` as `{"error": ..., "fileId": ...}`, `_run_one` as `{"kind": "upload_fail", "name": ..., "error": ..., "fileId": ...}` — while a `create()` failure still reports no `fileId`, correctly, since nothing was created. The two functions structure this differently: `_run_one`'s restamp `try`/`except` sits nested inside its one broad outer `try` (which also covers `create()` and the `if name in drive_map` update branch above it), while `_upload_local_file`'s is a separate, sequential `try`/`except` reached only after the `create()` `try`/`except` has already returned successfully — not nested inside it (PR #645 QA round 1 caught this file's prose originally describing both as nested). PR #650 factored the two restamp-failure branches' shared shape — the `created Drive file {id!r} but failed to restamp its modifiedTime` message plus `storageQuotaExceeded`→`_SA_QUOTA_ERROR` rendering — into a module-level `_restamp_failure_result(file_id, exc)` returning `{"error": ..., "fileId": ...}`; `_run_one` spreads that and layers its own `kind`/`name` keys on top. Before this only the `create()` except recognized a quota error — the restamp except caught bare `Exception` and would have leaked a raw `str(e)` for a quota failure there, unlike every other quota-error site in the file. `sync_folder`'s and `upload_local_folder`'s own `failed`-list aggregation steps (which previously narrowed each per-item result down to just `{name, error}`) were updated to carry `fileId` through when present, or the fix would have been invisible by the time a caller saw the result — and, since a `fileId`-carrying failure means `create()` genuinely changed the folder's contents, all three call sites (`_sync_level`, the `upload_local_file` tool, `upload_local_folder`) also had to treat that case as cache-dirtying, the same as a real success: none of them originally did (same QA round), since a `fileId`-carrying result still fell into each site's own success-only cache-invalidation gate's "false" branch. `sync_folder`'s `use_checksum` param (#274) adds a content check ahead of `_sync_level`'s mtime comparison for names present on both sides: when the local file's md5 (`_local_md5`, streamed in 1 MiB chunks) matches Drive's own `md5Checksum` (now fetched by `_list_drive_children`), the pair is treated as in sync regardless of `modifiedTime` drift — this is what actually fixes `upload_local_file` not stamping `modifiedTime` (unlike `_sync_level`'s own upload path) causing a spurious re-download of identical content on the next sync, not just a same-mtime coincidence. Only applies where Drive returns a real `md5Checksum` — Workspace files and `convert_markdown` Docs have none and always fall back to mtime-only comparison, flag or no flag. A checksum *mismatch* doesn't force any decision by itself; it just falls through to the same mtime-based upload/download/conflict logic used when `use_checksum=False`, since content differing doesn't by itself say which side is newer. PR #472's own review round found four issues in this first version: the checksum comparison ran unconditionally, before the (cheap) mtime diff, including for pairs already within `_SYNC_MTIME_TOLERANCE` and even during `dry_run=True` — fixed by moving the mtime diff first and gating the hash read on `use_checksum and not dry_run and abs(diff) > _SYNC_MTIME_TOLERANCE`, so an already-in-sync pair or a dry_run preview never pays for a read it can't change the outcome of (#716 later dropped the `abs(diff)` part of that gate; see below); `_local_md5` had no try/except unlike every other per-item operation in this module, so a file that became unreadable (deleted, permission-denied) between the directory scan and the hash read raised uncaught out of the whole `sync_folder` call instead of degrading to one `failed` entry — fixed via a new plan action (now `"local_read_fail"`, which since PR #841 also covers the plan's own `stat()`) mirroring the existing `"collision"` pattern; and `_upload_local_file`'s plain (non-`convert`) branch was identified as the actual root cause worth fixing directly — it never stamped `modifiedTime` the way the `convert_mime` branch three lines above already did (and `#422` already established for that branch), so a file uploaded through it always read as "Drive newer" on the very next `sync_folder` regardless of content — now stamped unconditionally in the `create()` body for every upload (no follow-up `update()` needed for the plain case, since Drive only overwrites `modifiedTime` when import-conversion is involved). `use_checksum` remains valuable beyond that root-cause fix for causes of drift the mtime stamp doesn't cover (e.g. a local overwrite that happens to preserve mtime). One review finding was accepted as a known tradeoff rather than fixed: a checksum mismatch that resolves to `'upload'` reads the local file twice (once to hash, once via `MediaFileUpload` for the actual transfer) — avoiding it would mean buffering the whole file in memory to share across both reads, a worse tradeoff for large files than one extra (likely page-cache-warm) disk read. Issue #659 covered the inverse of `use_checksum`'s target failure mode — **false sameness**, where the two mtimes agree but the content has diverged (classic cause: a rename-in-place, since `mv` preserves mtime), which short-circuited to `"in sync"` before the hash block (then guarded on `abs(diff) > _SYNC_MTIME_TOLERANCE`) could run, so `use_checksum=True` couldn't reach it by construction (until #716, below). Fixed with a near-free discriminator in `_sync_level`'s both-sides branch: `size` is now also fetched by `_list_drive_children`, and a within-tolerance mtime pair whose local `st_size` disagrees with Drive's reported `size` is not skipped — it is reported as a `conflict` for **every** direction, never an auto-transfer (PR #712 QA round 1): the mtimes agree so recency is unknown, and the `diff`-based branches already report `conflict` rather than overwrite a target they *can* tell is newer, so this branch — which knows less — must be at least as cautious. The byte-size check is one `stat()` and no read, so unlike the hash block it runs during `dry_run` too. Non-Workspace files only (`size`, like `md5Checksum`, is absent for Workspace/`convert_markdown` files). A same-size content edit that also preserves mtime still reads as `"in sync"` under the default `use_checksum=False`. Issue #716 closed that gap for an explicit `use_checksum=True`: it now hashes every both-sides pair with a Drive `md5Checksum`, within tolerance or not (a size mismatch is still settled from the `stat()` alone, first). A within-tolerance hash mismatch is a `conflict` for every direction, the same as the size mismatch; both come from `_diverged_same_mtime_step`. The hashes run after the plan loop, concurrently, capped at `_SYNC_HASH_CONCURRENCY`, each filling in a placeholder step that holds the mtime-only fallback from `_mtime_step`. `dry_run` still never hashes, and an affected step's reason ends in `(checksum not verified in dry_run)`, since the real run can decide differently. `upload_local_folder` (#411) used to have its own independent inline upload loop with no `convert` param, unreachable from `_upload_local_file`'s conversion mechanism — bulk-importing a folder of CSV/DOCX/PPTX into native Workspace formats required falling back to per-file `upload_local_file` calls. Fixed by routing each candidate through `_upload_local_file` itself, gaining the same `convert` param and its semantics (an unsupported extension lands in `failed` rather than uploading as-is, matching `upload_local_file`'s own error-not-silent-fallback behavior). The existing bulk `skip_if_exists` pre-check (one `list()` call for the whole folder, not one per file — TC-D100) is preserved rather than replaced by `_upload_local_file`'s own per-call check: the folder loop now fetches each existing file's `mimeType` alongside its name in that same single call, decides skip-worthiness itself (name-only match when `convert=False`; name *and* target-mimeType match when `convert=True`, mirroring `_upload_local_file`'s own convert-aware duplicate check), and then calls `_upload_local_file` with `skip_if_exists=False` for whatever survives — so existence is never checked twice. `sync_folder(dry_run=True)`'s response used to duplicate every `skip`/`conflict` name into both a flat list (`skipped`/`conflicts`) and a full `{name, action, reason}` entry in `actions`, deterministically tripping the response-size cap on a moderately-sized, mostly-in-sync folder even though `actions` alone (name + action-type + reason for every item, including upload/download entries the flat lists never populated during `dry_run` in the first place) was already a complete, non-redundant picture (issue #512). Fixed by leaving `uploaded`/`downloaded`/`skipped`/`conflicts`/`failed` empty during `dry_run` rather than trimming or toggling `actions` itself. Separately, `sync_folder` previously had no response-size-cap bypass at all (`local_path_available=False`) unlike every other capped tool except `export_file`/`list_file_activity` (both deliberately, per the decision doc below) — its own `local_path` param already means the sync destination, so it can't double as a dump target the way `get_sheet_data`'s `local_path` does. Fixed by adding a separate `result_local_path` param following the same unconditional-if-passed `write_capped_result_to_disk` pattern as every other capped tool with a bypass; `enforce_response_size_cap` gained a `local_path_param` override so its error message names the right parameter instead of hardcoding `local_path`. PR #518's own QA review round live-caught a gap in that first version: `result_local_path` had no guard against pointing at or inside `local_path` — since `local_path` is scanned as sync input on every call (unlike every other capped tool's local_path/result_local_path, which are pure output destinations), a manifest written there showed up as a new local-only file on the very next sync and would have been uploaded to Drive on a real run. Fixed by rejecting the call up front (`ValueError`, before any Drive API call) when `Path(result_local_path).resolve()` equals or is a descendant of the resolved `local_path`. `upload_file`'s `convert_to_doc` param implements the identical native-import-conversion trick as `_upload_local_file`'s `convert`/`_CONVERT_MIME` above, independently — raw text/markdown/html content instead of a local file, always targeting a Doc since there's no file extension to dispatch Sheets/Slides on — with no cross-reference between the two (issue #412). Fixed by sharing `_GOOGLE_DOC_MIME` as the single source of truth for the target mimeType literal both mechanisms write, plus explicit docstring cross-references at both call sites; the two code paths themselves stayed separate rather than being merged, since their actual upload semantics (raw content vs. local file, single target vs. extension-dispatched) differ enough that unifying them would cost more complexity than it'd save. Issue #424 (folded #421's two extra findings in) was pure hardening on this same `convert_markdown` matching logic, no behavior change: the synthetic `"_is_converted_md"` key spread into every `drive_map` entry (previously read directly at 5 separate call sites — the pattern PR #433's own review round flagged as having already caused one missed site) was replaced with `_is_converted_md_entry(f)`, a helper that recomputes the classification on demand from the resource's own `mimeType`/`properties` rather than caching it — `drive_map` is now a plain passthrough of Drive's file resource shape again, and every call site (the build loop, both plan conflict branches, the upload-execution reimport check, the download-execution guard) shares one place to agree with when this classification's rules change. The three independently-hand-written "no reverse conversion" messages (drive-only plan conflict, drive-newer plan conflict, download-execution guard) now interpolate a shared `_NO_REVERSE_CONVERSION_CLAUSE` constant instead. The dead `assert convert_source is not None` fell out naturally once the build loop's `local_name` derivation was rewritten against the new helper. The upload-execution branch's `_CONVERT_MIME[".md"]` unpack moved inside the `convert_this` check so it's only looked up for an actual `.md` conversion, not unconditionally for every upload. The doubled create()+update() latency on every new converted-`.md` upload (both here and in `_upload_local_file`'s `convert=True` path) was reviewed and left as an accepted tradeoff, not fixed — Drive's import-conversion finishes asynchronously after `create()` already returns, so there's no single call that can request both the upload and the post-conversion `modifiedTime` restamp together, and polling for conversion completion before restamping would trade one predictable extra call for an unpredictable number of slower ones. Issue #814 took converted `.md` Docs off the `modifiedTime` comparison entirely: Drive updates a Doc's `modifiedTime` from the Docs backend asynchronously, minutes behind, which both overwrote the restamp (an unedited Doc sat in `conflicts`) and hid real Drive edits for minutes. Every converted-`.md` upload now stamps `_CONVERTED_MD_SOURCE_MTIME_PROP` (the local mtime), a re-upload also stamps `_CONVERTED_MD_BASELINE_REV_PROP` (the Doc's latest revision, read just before the `update()`) and clears `_CONVERTED_MD_IMPORT_REV_PROP`, and the first sync afterward records the import's revision ID there. `_sync_level` then judges the local side against the stamped mtime and the Drive side from `revisions.list` (`_converted_md_drive_state`/`_converted_md_step`, fetched concurrently after the plan loop and capped at `_SYNC_REVISION_CONCURRENCY`, during `dry_run` too). A Drive edit is always a `conflict`, including when the local file changed too (previously an upload over the Drive edit). A revision-read failure is a `"drive_read_fail"` plan action. A Doc without the stamp (converted before #814) keeps the `modifiedTime` path until its next re-upload. The restamp stays, now cosmetic for these Docs, and the import-revision write re-sends the stamped `modifiedTime`, since a property-only update bumps it to "now" (seen live). PR #854 QA round 1 tightened it: the source-mtime stamp keeps microsecond precision and is compared exactly (`_CONVERTED_MD_STAMP_TOLERANCE`), since the 5s Drive clock-skew tolerance hid a local save made within 5s of the upload. The import-revision write always re-sends a `modifiedTime`, since a property write bumps it to "now": the stamped source mtime for an unedited Doc, and Drive's own listed value for a Drive-edited one (re-sending the source mtime there erased the edit's timestamp). It also marks the folder cache dirty. An empty revision history is a `drive_read_fail`, not an edit. A local change under `direction='download'` is a conflict even while the import is pending. The upload path's fresh revision read now aborts the upload as a `conflict` if the latest revision moved since planning. Every upload also stamps `_CONVERTED_MD_UPLOADED_AT_PROP`, so a pruned baseline falls back to finding the import by timestamp, and a candidate import more than `_CONVERTED_MD_IMPORT_WINDOW` after the upload is read as the import merged into a later edit (`changed`). Legacy Docs already stuck in the drift conflict are unstuck by touching the local file (`docs/known-limitations.md`). Design: `docs/decisions/decision-converted-doc-change-detection.md`; other conversion types are #818.
|
|
16
16
|
|
|
17
17
|
## `tools/docs/content.py`
|
|
18
18
|
|
|
@@ -157,3 +157,36 @@ longer agree (#805).
|
|
|
157
157
|
delete the old Doc in Drive; the next sync converts the file again under its new name. Renaming
|
|
158
158
|
both sides to the same new name doesn't restore the link, because the digest still records the
|
|
159
159
|
old name.
|
|
160
|
+
|
|
161
|
+
### `sync_folder` — a `convert_markdown` Doc edited in Drive stays a conflict until it's replaced
|
|
162
|
+
|
|
163
|
+
**What:** Once a converted Doc is edited in Drive, every `sync_folder` run reports it under
|
|
164
|
+
`conflicts`, including after the local `.md` is edited too. Editing or touching the local file
|
|
165
|
+
doesn't clear the conflict, and there's no parameter to accept the Drive version as the new
|
|
166
|
+
baseline or to force the upload over it.
|
|
167
|
+
|
|
168
|
+
**Why:** There's no reverse conversion (Doc → Markdown), so the Drive edit can't be downloaded.
|
|
169
|
+
The only way to pick up the local file would be to overwrite the Drive edit, which is what
|
|
170
|
+
`sync_folder` used to do silently and now refuses (#814). A Drive edit is detected from the
|
|
171
|
+
Doc's revision history, which only a new upload resets.
|
|
172
|
+
|
|
173
|
+
**Workaround:** Carry the Drive edits into the local `.md` by hand, then rename or remove the Doc
|
|
174
|
+
in Drive and sync again. The next sync converts the local file into a new Doc. That new Doc has
|
|
175
|
+
a new file ID, so re-share it and update any links that pointed to the old one. Tracked in #860.
|
|
176
|
+
|
|
177
|
+
### `sync_folder` — a `convert_markdown` Doc from before #814 can stay stuck in `conflicts`
|
|
178
|
+
|
|
179
|
+
**What:** A converted Doc created before #814 has none of the properties #814's change
|
|
180
|
+
detection uses, so it stays on the old `modifiedTime` comparison until it's next re-uploaded. If
|
|
181
|
+
Drive's late `modifiedTime` update overwrote its restamp (the bug #814 fixed), it reads as
|
|
182
|
+
"Drive newer" and is reported under `conflicts` on every sync. A conflict never uploads, so it
|
|
183
|
+
never picks up the properties that would fix it.
|
|
184
|
+
|
|
185
|
+
**Why:** Migrating such a Doc means choosing a baseline revision for it. While it reads as Drive
|
|
186
|
+
newer, `sync_folder` can't tell a lagging timestamp from a real Drive edit, so it doesn't guess.
|
|
187
|
+
|
|
188
|
+
**Workaround:** If the Doc wasn't edited in Drive, touch the local `.md` (for example,
|
|
189
|
+
`touch notes.md`), so it's newer than the Drive timestamp, and sync again. The upload replaces
|
|
190
|
+
the Doc's content in place, keeping its file ID, and stamps the properties. From then on, the
|
|
191
|
+
Doc uses the new detection. If it *was* edited in Drive, carry those edits into the local file
|
|
192
|
+
first, since the upload overwrites them.
|
|
@@ -754,6 +754,8 @@ download_folder mime_type_filter=application/vnd.google-apps.document, export_fo
|
|
|
754
754
|
|
|
755
755
|
**Result, round 2 (2026-07-25) ✅ PASS** After the fix's metadata-only `files().update()` re-stamp following `create()`: `notes.md` landed in `skipped`, not `failed`, on the immediate resync. `get_file_metadata` confirmed Drive's `modified_time` (`15:47:12.000Z`) now matches the local mtime exactly, byte-for-byte on the seconds field — no drift. Ran a second identical resync immediately after to confirm it's not a one-off race; stayed `skipped` both times.
|
|
756
756
|
|
|
757
|
+
**Result, PR #854 re-run** (2026-09-30, Sky, PR #854 round 1 at `665b870`, `mcp-gee-sweet-sky` reconnected after reset, OAuth, Shared Drive): ✅ **PASS**. Fresh scratch folder and `/tmp/qa-sync-211/notes.md`. The TC-D217 upload put `notes.md` in `uploaded`. The immediate `bidirectional` resync put it in `skipped`, with `conflicts`/`failed` empty. `list_files` showed exactly one `notes.md`, a Google Doc.
|
|
758
|
+
|
|
757
759
|
---
|
|
758
760
|
|
|
759
761
|
### TC-D219: convert_markdown — local edit re-converts in place, not a new file ⚠️ local-filesystem
|
|
@@ -1876,6 +1878,13 @@ Trash `{FOLDER_ID}` and its contents. Remove `/tmp/qa-266/`.
|
|
|
1876
1878
|
- Final listing: exactly 6 Google Docs, no `text/markdown` files, no orphans or duplicates.
|
|
1877
1879
|
- Unit suite at `2544c93`: 1718 passed, 3 skipped.
|
|
1878
1880
|
|
|
1881
|
+
**Result, PR #854 re-run** (2026-09-30, Sky, PR #854 round 1 at `665b870`, `mcp-gee-sweet-sky` reconnected after reset, OAuth, Shared Drive): ✅ **PASS**.
|
|
1882
|
+
- Setup: the legacy Doc's `modifiedTime` was restamped to `05:00:00.000Z` about 20s after `create()`, and the local mtime was set to match.
|
|
1883
|
+
- Call 1: `failed == []`. `uploaded` held the 4 new names, and `legacy.md` was in `skipped`. The markers were as specified. `short.md` had both keys. `<A100>.md` had only the raw generic key. `<B110>.md` and `<CJK>.md` had only a `sha256:` generic key. `legacy.md` had only its original key. All 4 new Docs also carried `geeSweetSourceMtime`, and the legacy Doc didn't.
|
|
1884
|
+
- Calls 2 and 3 (with and without `convert_markdown`): `uploaded`, `conflicts` and `failed` were empty, and all 5 names were in `skipped`.
|
|
1885
|
+
- Call 4: `upload_local_file(convert=True)` on `<C115>.md` returned no error, and the repeated call 2 listed it in `skipped`.
|
|
1886
|
+
- Final `list_files`: exactly 6 Google Docs, no `text/markdown` files, no orphans or duplicates.
|
|
1887
|
+
|
|
1879
1888
|
### TC-D267: `upload_local_file(convert=True)` on a CSV restamps the converted Sheet's `modifiedTime`, so a following `sync_folder(export_format='csv')` reads it as in sync (issue #435) ⚠️ local-filesystem
|
|
1880
1889
|
|
|
1881
1890
|
**Background:** Issue #435 proposed restamping `modifiedTime` only for `.md` → Doc conversions, on the premise that `sync_folder` never matches other converted types back to a local file. That premise is false. Drive strips `.csv` from a converted Sheet's display name, so `export_format='csv'` maps the Sheet straight back to the local `.csv`, and the two mtimes are compared. For a Sheet, `create()` ignores the requested `modifiedTime` outright. Without the follow-up metadata-only restamp, the pair reads as "Drive newer" on the very next sync. #435 declined the `.md`-only gate, and both call sites now share one `_restamp_modified_time` helper. This case guards the non-`.md` path.
|
|
@@ -1910,3 +1919,98 @@ Trash `{FOLDER_ID}` and its contents. Remove `/tmp/qa-267/`.
|
|
|
1910
1919
|
- `convert_markdown` regression check, run again at `b4e6439`: upload, then dry-run resync reports `skip` / "in sync".
|
|
1911
1920
|
- The round-1 findings are all addressed in `b4e6439`, confirmed from its diff: the docstring is now consistent, the mtime is read inside the `try` at both sites (each with a new unit test), a single `_local_mtime_dt`, the stale comment is dropped, and the restamp test is parametrized.
|
|
1912
1921
|
- Unit suite at `b4e6439`: 1739 passed, 3 skipped; ruff check and format are clean.
|
|
1922
|
+
|
|
1923
|
+
### TC-D271: `sync_folder(convert_markdown=True)` — a Drive-side edit made right after a sync is a `conflict` on an immediate re-sync, and an unedited Doc settles into `skipped` whatever its `modifiedTime` does (issue #814) ⚠️ destructive ⚠️ local-filesystem
|
|
1924
|
+
|
|
1925
|
+
**Background:** Drive updates a Google Doc's `modifiedTime` from the Docs backend asynchronously, minutes behind. That late update overwrote the post-create restamp (an unedited Doc then sat in `conflicts` on every sync), and it also hid real Drive edits for 1.5–5 minutes, so a sync in that window reported an edited Doc as in sync. A converted Doc now carries `geeSweetSourceMtime` (the local mtime at upload), and its Drive side is judged from revision history. The first sync after an upload records the import's revision ID in `geeSweetImportRevision`, and any later revision is a Drive edit. See `docs/decisions/decision-converted-doc-change-detection.md`.
|
|
1926
|
+
|
|
1927
|
+
**Setup**
|
|
1928
|
+
Create a scratch Drive folder `{FOLDER_ID}`. Locally, create `/tmp/qa-271/` containing `a.md`, `b.md`, `c.md`, `d.md`, `e.md`, and `f.md`, each holding `# Heading` and one paragraph of body text.
|
|
1929
|
+
|
|
1930
|
+
**Tool calls**
|
|
1931
|
+
1. `sync_folder(folder_id={FOLDER_ID}, local_path="/tmp/qa-271/", direction="upload", convert_markdown=True)`
|
|
1932
|
+
2. `sync_folder(folder_id={FOLDER_ID}, local_path="/tmp/qa-271/", direction="bidirectional", convert_markdown=True)`. Run it about 10 seconds after call 1.
|
|
1933
|
+
3. `insert_doc_text(doc_id={ID of a.md}, insertions=[{"index": 1, "text": "Drive edit\n"}])`
|
|
1934
|
+
4. Within 30 seconds of call 3: `sync_folder(folder_id={FOLDER_ID}, local_path="/tmp/qa-271/", direction="bidirectional", convert_markdown=True, dry_run=True)`
|
|
1935
|
+
5. The same call without `dry_run`.
|
|
1936
|
+
6. Wait 5 minutes, then repeat call 5.
|
|
1937
|
+
|
|
1938
|
+
**Checks**
|
|
1939
|
+
- Call 1: `uploaded` holds all 6 names, and `failed == []`.
|
|
1940
|
+
- A scratch-script `files().list(fields="files(name,properties)")` after call 1 shows on every Doc `geeSweetSourceMtime`, equal to that local file's mtime to the microsecond (`YYYY-MM-DDTHH:MM:SS.ffffffZ`); `geeSweetUploadedAt`, within a minute of call 1; and no `geeSweetBaselineRevision`.
|
|
1941
|
+
- Call 2: all 6 names are in `skipped`, and `conflicts == []` and `failed == []`, even for any Doc whose `get_file_metadata` `modified_time` has drifted from its local mtime. Afterwards each Doc carries `geeSweetImportRevision`, equal to the only revision ID `list_revisions` returns for it.
|
|
1942
|
+
- Call 4: the `a.md` action is `conflict`, with a reason starting "edited in Drive since the last upload". The other 5 are `skip`. `get_file_metadata` on `a.md` may still show the pre-edit `modified_time`, which is expected, and is exactly what used to hide the edit.
|
|
1943
|
+
- Call 5: `conflicts == ["a.md"]`, the other 5 are in `skipped`, and `uploaded == []`. `get_doc_content` on `a.md` still contains "Drive edit".
|
|
1944
|
+
- Call 6: the same as call 5. No drift-induced conflict appears on `b.md`–`f.md`.
|
|
1945
|
+
|
|
1946
|
+
**Teardown**
|
|
1947
|
+
Trash `{FOLDER_ID}` and its contents. Remove `/tmp/qa-271/`.
|
|
1948
|
+
|
|
1949
|
+
**Result** (2026-09-30, Sky, PR #854 round 1 at `665b870`, `mcp-gee-sweet-sky` reconnected after reset, OAuth, Shared Drive): ✅ **PASS**.
|
|
1950
|
+
- Call 1: `uploaded` held all 6 names, and `failed == []`. The scratch-script listing showed `geeSweetSourceMtime` = `2026-09-30T05:08:13.000Z` on every Doc, equal to each local mtime, and no `geeSweetBaselineRevision`. Each Doc had one revision, `1`.
|
|
1951
|
+
- Call 2 (about 10s later): all 6 were in `skipped`, with `conflicts` and `failed` empty. Every Doc then carried `geeSweetImportRevision: "1"`, its only revision. The race didn't drift any `modifiedTime` this run.
|
|
1952
|
+
- Call 4 (dry run, about 20s after the edit): `a.md` was a `conflict` ("edited in Drive since the last upload, …"), and the other 5 were `skip`. `get_file_metadata` on `a.md` still showed the pre-edit `modified_time` `05:08:13.000Z`: the lag that used to hide the edit.
|
|
1953
|
+
- Call 5: `conflicts == ["a.md"]`, the other 5 were in `skipped`, and `uploaded == []`. `get_doc_content` on `a.md` still began with "Drive edit".
|
|
1954
|
+
- Call 6 (about 5 minutes later): identical to call 5. By then `a.md`'s `modifiedTime` had caught up to the edit (`05:09:08.894Z`). `b.md`–`f.md` stayed at `05:08:13.000Z` with no drift-induced conflict.
|
|
1955
|
+
- Side probe (code-review finding 1, reproduced): a new Doc `g.md` was uploaded and edited in Drive *before* its first bidirectional sync. Once its `modifiedTime` caught up to the edit (`05:11:15.146Z`), the first sync correctly reported it as a `conflict`. But the import-revision write reset `modifiedTime` to the old source mtime `05:10:57.000Z`, hiding the Drive edit's timestamp.
|
|
1956
|
+
|
|
1957
|
+
### TC-D272: `sync_folder(convert_markdown=True)` — a local-only change re-uploads and settles; a change on both sides is a `conflict`, never an overwrite of the Drive edit (issue #814) ⚠️ destructive ⚠️ local-filesystem
|
|
1958
|
+
|
|
1959
|
+
**Background:** Before #814, a converted Doc edited in Drive *and* locally read as "local newer" and was re-uploaded, silently replacing the Drive edit. Now a re-upload stamps the Doc's latest revision from just before the upload as `geeSweetBaselineRevision`, and clears `geeSweetImportRevision`. The next sync takes the first revision after that baseline as the new import. A local change goes up only when the Drive side is unchanged.
|
|
1960
|
+
|
|
1961
|
+
**Setup**
|
|
1962
|
+
Create a scratch Drive folder `{FOLDER_ID}` and `/tmp/qa-272/` containing `x.md` and `y.md`, each holding `# Heading` and one paragraph. Run `sync_folder(folder_id={FOLDER_ID}, local_path="/tmp/qa-272/", direction="upload", convert_markdown=True)`, wait about 10 seconds, then run the same call with `direction="bidirectional"` so both Docs record their import revision.
|
|
1963
|
+
|
|
1964
|
+
**Tool calls**
|
|
1965
|
+
1. Append a line `local edit` to `/tmp/qa-272/x.md`. Then `sync_folder(folder_id={FOLDER_ID}, local_path="/tmp/qa-272/", direction="bidirectional", convert_markdown=True)`
|
|
1966
|
+
2. About 10 seconds later, repeat call 1 with no further edits.
|
|
1967
|
+
3. `insert_doc_text(doc_id={ID of y.md}, insertions=[{"index": 1, "text": "Drive edit\n"}])`, and append a line `local edit` to `/tmp/qa-272/y.md`. Then `sync_folder(folder_id={FOLDER_ID}, local_path="/tmp/qa-272/", direction="bidirectional", convert_markdown=True, dry_run=True)`
|
|
1968
|
+
4. The same call without `dry_run`.
|
|
1969
|
+
5. `sync_folder(folder_id={FOLDER_ID}, local_path="/tmp/qa-272/", direction="download", convert_markdown=True, dry_run=True)`, after appending another line to `/tmp/qa-272/x.md`.
|
|
1970
|
+
|
|
1971
|
+
**Checks**
|
|
1972
|
+
- Call 1: `uploaded == ["x.md"]` and `y.md` is in `skipped`. The same file ID is reused (not a new Doc). The scratch-script properties listing shows `x.md` with `geeSweetBaselineRevision` equal to its pre-upload latest revision ID (from `list_revisions` before call 1), a new `geeSweetSourceMtime`, and no `geeSweetImportRevision`.
|
|
1973
|
+
- Call 2: both names are in `skipped`, and `x.md` now carries `geeSweetImportRevision`, equal to the revision after the baseline. If call 2 runs before Drive lists the new revision, `x.md` is still `skipped` and the property is recorded on a later sync.
|
|
1974
|
+
- Call 3: the `y.md` action is `conflict`, with a reason starting "edited both locally and in Drive".
|
|
1975
|
+
- Call 4: `conflicts == ["y.md"]` and `uploaded == []`. `get_doc_content` on `y.md` still contains "Drive edit", and not "local edit".
|
|
1976
|
+
- Call 5: the `x.md` action is `conflict`, with reason "local .md changed since the last upload but direction is download".
|
|
1977
|
+
|
|
1978
|
+
**Teardown**
|
|
1979
|
+
Trash `{FOLDER_ID}` and its contents. Remove `/tmp/qa-272/`.
|
|
1980
|
+
|
|
1981
|
+
**Result** (2026-09-30, Sky, PR #854 round 1 at `665b870`, `mcp-gee-sweet-sky` reconnected after reset, OAuth, Shared Drive): ✅ **PASS**. The checks as written all pass. A side probe reproduced code-review finding 8 (see below).
|
|
1982
|
+
- Setup: both Docs recorded `geeSweetImportRevision: "1"` on the bidirectional setup sync.
|
|
1983
|
+
- Call 1: `uploaded == ["x.md"]`, and `y.md` was in `skipped`. The file ID was unchanged. `x.md` carried `geeSweetBaselineRevision: "1"` (its pre-upload latest), a new `geeSweetSourceMtime` (`05:09:29.000Z`), and no `geeSweetImportRevision`. Revision `2` appeared.
|
|
1984
|
+
- Call 2: both were in `skipped`, and `x.md` now carried `geeSweetImportRevision: "2"`.
|
|
1985
|
+
- Call 3 (dry run): `y.md` was a `conflict` ("edited both locally and in Drive since the last upload — …"). `x.md` was `skip`.
|
|
1986
|
+
- Call 4: `conflicts == ["y.md"]` and `uploaded == []`. `get_doc_content` on `y.md` contained "Drive edit" and not "local edit".
|
|
1987
|
+
- Call 5 (`direction="download"`, dry run, after another `x.md` edit): `x.md` was a `conflict` with reason "local .md changed since the last upload but direction is download".
|
|
1988
|
+
- Side probe (code-review finding 8, reproduced): `x.md` was re-uploaded (stamp `05:10:10`). Another line was then appended, with the mtime set to stamp+3s. After the import revision (`3`) was recorded, a bidirectional sync (real and dry run) still reported `x.md` as `in sync`. The new content never uploaded (`get_doc_content` lacked "second-save"), because the local-vs-stamp comparison uses the 5s clock-skew tolerance.
|
|
1989
|
+
|
|
1990
|
+
### TC-D273: `sync_folder(convert_markdown=True)` — recording the import revision leaves a Drive edit's `modifiedTime` alone, and a local save seconds after an upload still uploads (PR #854 QA round 1) ⚠️ destructive ⚠️ local-filesystem
|
|
1991
|
+
|
|
1992
|
+
**Background:** PR #854 round 1 reproduced two bugs live. The first sync after an upload records the import's revision ID. It used to re-send the stamped source mtime as `modifiedTime` even on a Doc already edited in Drive, which reset the edit's real timestamp. Also, the local side was compared against its stamp with the 5s Drive clock-skew tolerance, so a save within 5s of the uploaded version never uploaded. The stamp now keeps microsecond precision and is compared exactly, and `modifiedTime` is re-sent only for an unedited Doc.
|
|
1993
|
+
|
|
1994
|
+
**Setup**
|
|
1995
|
+
Create a scratch Drive folder `{FOLDER_ID}` and `/tmp/qa-273/` containing `g.md` and `x.md`, each holding `# Heading` and one paragraph. Run `sync_folder(folder_id={FOLDER_ID}, local_path="/tmp/qa-273/", direction="upload", convert_markdown=True)`.
|
|
1996
|
+
|
|
1997
|
+
**Tool calls**
|
|
1998
|
+
1. Right after setup, before any other sync: `insert_doc_text(doc_id={ID of g.md}, insertions=[{"index": 1, "text": "Drive edit\n"}])`. Wait until `get_file_metadata` on `g.md` shows a `modified_time` later than the upload (up to about 5 minutes), and note that value.
|
|
1999
|
+
2. `sync_folder(folder_id={FOLDER_ID}, local_path="/tmp/qa-273/", direction="bidirectional", convert_markdown=True)`
|
|
2000
|
+
3. `get_file_metadata(file_id={ID of g.md})`
|
|
2001
|
+
4. Append a line to `/tmp/qa-273/x.md`, then set its mtime to 3 seconds after its `geeSweetSourceMtime` value (scratch script: `os.utime`). Then repeat call 2.
|
|
2002
|
+
|
|
2003
|
+
**Checks**
|
|
2004
|
+
- Call 2: `conflicts` contains `g.md`. `x.md` is in `skipped`.
|
|
2005
|
+
- Call 3: `modified_time` is still the value noted in call 1, not reset to the upload's mtime. The scratch-script properties listing shows `geeSweetImportRevision` set on `g.md`.
|
|
2006
|
+
- Call 4: `uploaded == ["x.md"]`. `get_doc_content` on `x.md` contains the appended line.
|
|
2007
|
+
|
|
2008
|
+
**Teardown**
|
|
2009
|
+
Trash `{FOLDER_ID}` and its contents. Remove `/tmp/qa-273/`.
|
|
2010
|
+
|
|
2011
|
+
**Result** (2026-10-01, Sky, PR #854 round 2 re-verification of `749c7bb`, `mcp-gee-sweet-sky` reconnected after reset, OAuth, Shared Drive): ✅ **PASS**.
|
|
2012
|
+
- Setup: both Docs carried `geeSweetSourceMtime` to the microsecond (`…03:45:16.813224Z` / `…03:45:16.813441Z`, matching `os.stat`) and `geeSweetUploadedAt` (`03:45:19.000Z`), with no baseline. The #814 restamp race hit `g.md` this run (`modifiedTime` `03:45:23.614Z`, vs. a source mtime of `03:45:16`).
|
|
2013
|
+
- Call 1: the edit added revision `2`. `get_file_metadata` showed `modified_time` `03:45:36.140Z` about 3 minutes later.
|
|
2014
|
+
- Call 2: `conflicts == ["g.md"]`, and `x.md` was in `skipped`, even though `g.md` had also drifted.
|
|
2015
|
+
- Call 3: `modified_time` was still `03:45:36.140Z`, not reset. `g.md` carried `geeSweetImportRevision: "1"`.
|
|
2016
|
+
- Call 4: `uploaded == ["x.md"]`, and `get_doc_content` contained "appended within 5s". An immediate extra resync left `x.md` in `skipped` (no repeat upload), and `g.md` stayed in `conflicts`.
|
|
@@ -293,7 +293,7 @@ Same shape as v0.8.1 — stabilize on defects that surfaced since v0.9.0 shipped
|
|
|
293
293
|
- [ ] `_SA_LIMITATIONS` (`server.py`) hygiene: no registry tying restricted tools to entries, redundant per-call flatten, duplicated alternatives text — triaged out of `backlog` 2026-09-11 ([#517](https://github.com/khuisman/mcp-gee-sweet/issues/517))
|
|
294
294
|
- [ ] `server.json`/`test_server_json.py`: minor hardening findings from PR #603's review — triaged out of `backlog` 2026-09-11 ([#607](https://github.com/khuisman/mcp-gee-sweet/issues/607))
|
|
295
295
|
- [ ] Restore the explanatory comment for the `annotations=None` stub param in `gen_tool_docs.py` — triaged out of `backlog` 2026-09-11 ([#637](https://github.com/khuisman/mcp-gee-sweet/issues/637))
|
|
296
|
-
- [
|
|
296
|
+
- [x] Team setup: give each worktree only its own MCP server — other roles' tool names are an estimated ~15–20k of the ~80k lane bootstrap; measure first. Prioritized 2026-09-29, lane-b ahead of #789 ([#850](https://github.com/khuisman/mcp-gee-sweet/issues/850)) (PR #856)
|
|
297
297
|
- [ ] Context-size backstop hook for lane sessions, plus a warning before a cold `--resume` — code half of #843; lane-b after #850 ([#847](https://github.com/khuisman/mcp-gee-sweet/issues/847))
|
|
298
298
|
- [ ] Cut `CLAUDE.md` and the role files down to lasting rules + pointers, moving per-PR history to `docs/design/`/`docs/decisions/` — Amy writes it (user-assigned), Bob reviews each PR under the prompt-QA gate; can land as one PR per file ([#849](https://github.com/khuisman/mcp-gee-sweet/issues/849))
|
|
299
299
|
- [x] `merge-pr.md` step 6: guard the team-slot reset against in-flight work on another ticket, mirroring #837's receiving-side guard in `dev.md` — Bob's track ([#838](https://github.com/khuisman/mcp-gee-sweet/issues/838)) (PR #853)
|