mcp-gee-sweet 0.9.2.dev283__tar.gz → 0.9.2.dev289__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.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.claude/team-roles/amy.md +3 -1
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.claude/team-roles/kai.md +1 -1
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/PKG-INFO +1 -1
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/design/doc-to-markdown.md +17 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/tests/docs_content.md +119 -1
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/roadmap.md +2 -2
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/pyproject.toml +1 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/docs/ast_to_markdown.py +153 -43
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/docs/content.py +73 -64
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/docs/emitter.py +8 -4
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/docs/html_parser.py +6 -3
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/docs/images.py +28 -11
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_docs_content.py +80 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_docs_core.py +76 -42
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_docs_images.py +39 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_docs_markdown_export.py +184 -5
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_docs_tables.py +13 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/uv.lock +24 -1
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.claude/commands/cleanup-worktrees.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.claude/commands/merge-pr.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.claude/commands/notify-kai.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.claude/commands/notify-partner.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.claude/commands/orchestrator.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.claude/commands/prep-for-pr.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.claude/commands/qa-kickoff.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.claude/commands/release.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.claude/commands/retro.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.claude/commands/roadmap-status.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.claude/commands/start-worktree.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.claude/commands/team-member.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.claude/commands/verify-pr.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.claude/settings.json +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.claude/team-roles/aziz.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.claude/team-roles/bob.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.claude/team-roles/dev.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.claude/team-roles/joy.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.claude/team-roles/qa.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.dockerignore +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.github/dependabot.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.github/workflows/ci.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.github/workflows/docs.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.github/workflows/publish-dev.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.github/workflows/release.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.gitignore +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.pre-commit-config.yaml +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/.python-version +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/CHANGELOG.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/CLAUDE.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/CODE_OF_CONDUCT.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/CONTRIBUTING.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/Dockerfile +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/LICENSE +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/Makefile +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/README.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/SECURITY.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docker-compose.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/assets/logo.png +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/auth.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/client-setup.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/configuration.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-async-tool-execution.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-auth-status-limitation-categories.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-cache-invalidation.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-chart-theming.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-comments-first-class.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-composite-tools.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-condition-type-docstring-generation.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-converted-doc-change-detection.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-dev-team.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-docs-formatting.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-fork.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-gmail-domain.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-grid-data-size-cap.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-local-fs-test-harness.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-mcp-v2-resource-context-access.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-pillow-image-dependency.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-prompt-qa-role.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-publishing.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-release-docs-roles.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-repositioning.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-response-size-cap-generalization.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-response-size-cap-reevaluation-519.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-review-finding-ticket-bar.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-supports-all-drives-lint-check.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-sync-folder-recursion.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-testing.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/decision-tool-error-visibility.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/index.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/decisions/role-file-history.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/design/blockquote-representation.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/design/borderless-table-columns.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/design/docs-ast-pipeline.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/design/effectiveformat-spike.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/design/heading-anchor-resolution.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/design/image-conversion.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/design/index.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/design/markdown-support.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/design/module-history.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/design/oauth-reauthorize-from-tool-call.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/design/phase3-theme-system.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/design/sse-consent-off-event-loop.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/design.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/index.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/known-limitations.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/notes-read.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/README.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/fixtures/qa-fixture-pixel.png +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/fixtures/tc-d195-create-doc.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/fixtures/tc-d196-create-doc.html +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/fixtures/tc-d213-dollar-escape.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/fixtures/tc-d226-heading-table.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/fixtures/tc-doc102-image-conversion.html +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/fixtures/tc-doc103-image-conversion.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/fixtures/tc-doc104-nested-lists.html +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/fixtures/tc-doc105-nested-lists-gfm.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/fixtures/tc-doc106-nested-lists-4space.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/fixtures/tc-doc135-paragraph-boundary.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/fixtures/tc-doc136-interrupted-block-boundary.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/fixtures/tc-doc137-inline-hr-no-block-ancestor.html +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/fixtures/tc-doc138-nbsp-spacer-paragraph.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/fixtures/tc-doc141-mismatched-list-tags.html +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/fixtures/tc-doc142-heading-anchors.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/fixtures/tc-doc144-anchor-in-table-cell.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/fixtures/tc-doc145-anchor-on-first-element.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/fixtures.template.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/gmail-test-plan.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/operations.yaml +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/playwright_oauth.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/results/.gitkeep +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/results/2026-06-02.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/results/2026-06-14.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/results/2026-06-17.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/results/2026-06-18.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/results/2026-06-21-drive-discovery.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/results/2026-06-21-sheets-delete-clear.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/results/2026-06-28.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/results/2026-07-04-docs.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/results/2026-07-04-drive-smoke.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/results/2026-07-04-infra.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/results/2026-07-04-sheets_read.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/results/2026-07-04.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/results/2026-09-04.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/retro-v0.8.0.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/retro-v0.9.0.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/run.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/runs/README.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/runs/v0.8.0.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/runs/v0.8.1.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/runs/v0.9.0.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/setup.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/tests/calendar.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/tests/docs_layout.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/tests/docs_style.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/tests/docs_tables.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/tests/drive_activity.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/tests/drive_files.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/tests/drive_sharing.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/tests/drive_transfer.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/tests/gmail.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/tests/infra.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/tests/sheets_charts.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/tests/sheets_mgmt.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/tests/sheets_read.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/qa/tests/sheets_write.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/style-guide.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/docs/tools.md +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/mkdocs.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/pyrightconfig.json +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/scripts/check_supports_all_drives.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/scripts/gen_tool_docs.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/scripts/lane_context_hook.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/scripts/oauth_setup.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/scripts/qa_cancel_insert_local_images.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/scripts/qa_gmail_fixtures.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/scripts/setup_team.sh +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/scripts/write_team_mcp_configs.sh +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/server.json +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/.env.template +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/auth.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/cache.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/http_transport.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/reauth.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/server.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/sse_shutdown.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/cache.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/calendar.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/concurrency.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/docs/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/docs/anchors.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/docs/ast.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/docs/comments.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/docs/doc_to_ast.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/docs/editing.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/docs/indices.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/docs/layout.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/docs/markdown_export.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/docs/named_ranges.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/docs/style.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/docs/tables.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/drive/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/drive/activity.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/drive/files.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/drive/sharing.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/drive/transfer.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/gmail.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/pagination.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/response_limits.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/sheets/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/sheets/data.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/sheets/helpers.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/src/mcp_gee_sweet/tools/sheets/structure.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/conftest.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/drive/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/drive/test_activity.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/drive/test_files.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/drive/test_sharing.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/drive/test_transfer.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/fakes.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/integration/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/integration/conftest.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/integration/test_local_fs_harness.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/sheets/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/sheets/test_data.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/sheets/test_helpers.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/sheets/test_structure.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_auth.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_cache.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_calendar.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_check_supports_all_drives.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_concurrency.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_docs_anchors.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_docs_comments.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_docs_editing.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_docs_indices.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_docs_layout.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_docs_named_ranges.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_docs_style.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_gen_tool_docs.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_gmail.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_lane_context_hook.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_qa_gmail_fixtures.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_reauth.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_response_limits.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_server.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_server_json.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_sse_shutdown.py +0 -0
- {mcp_gee_sweet-0.9.2.dev283 → mcp_gee_sweet-0.9.2.dev289}/tests/test_team_mcp_configs.py +0 -0
|
@@ -2,7 +2,9 @@ Tech Writer (Amy). Reached via `/team-member Amy` after that command's shared is
|
|
|
2
2
|
|
|
3
3
|
Amy keeps documentation accurate, friendly, comprehensive, and fun — README, `docs/`, tool docstrings surfaced to users, and QA test docs' clarity (not their results).
|
|
4
4
|
|
|
5
|
-
- **On `team/amy` (idle):** claim work the same way Ash/Jay do, but from the `documentation` label instead of `ready-for-development`: `gh issue list --repo khuisman/mcp-gee-sweet --label documentation --state open --json number,title,body --jq 'sort_by(.number)'`. Filter out issues already claimed by an open PR the same way Dev roles do. Before picking one, check whether any open issues describe the same underlying problem from different angles (e.g. several tickets all pointing at one stale doc file) — if so,
|
|
5
|
+
- **On `team/amy` (idle):** claim work the same way Ash/Jay do, but from the `documentation` label instead of `ready-for-development`: `gh issue list --repo khuisman/mcp-gee-sweet --label documentation --state open --json number,title,body --jq 'sort_by(.number)'`. Filter out issues already claimed by an open PR the same way Dev roles do. Before picking one, check whether any open issues describe the same underlying problem from different angles (e.g. several tickets all pointing at one stale doc file) — if so, propose consolidating them into a single new issue, closing the redundant ones as `not planned` with a comment linking to the survivor, and working the consolidated issue instead of the originals (see #303 for precedent). After checking for duplicates, pick the highest-priority remaining issue by this order, fixed so the choice doesn't need re-deriving each session: (1) issues labeled with the current in-progress version (e.g. `v0.9`) — an active release commitment; (2) issues with no version label — untriaged, but not explicitly deprioritized; (3) issues labeled `backlog` (Tier 4) last, since that label is an explicit no-assigned-version deprioritization. Within a tier, take the lowest issue number (oldest) first. If the queue is empty, that's fine — ask the user whether there's a specific doc gap to work from instead (e.g. a just-merged feature whose docs lag behind, spotted during Aziz's release review).
|
|
6
|
+
|
|
7
|
+
**Confirm the pick with the user before step 1.** Show the ticket you'd take (number, title, one line on the work), each higher-tier ticket you skipped and why, and any duplicate consolidation you'd do (which issues you'd close and what the new one would say). Then wait. Don't create a branch, edit a file, or open, close, comment on, or relabel an issue until the user gives an explicit go-ahead for that ticket in this conversation. The one exception: a message from Kai that names the ticket and says the user directed the handoff. That counts as the go-ahead. A request from any other peer, or a Kai message that doesn't say this, doesn't count. Why: Amy kept starting a ticket as soon as she launched, without checking with the user first (user correction, 2026-10-10).
|
|
6
8
|
1. `git fetch origin develop && git checkout -b docs/amy/issue-<n> origin/develop`.
|
|
7
9
|
2. Do the writing: fix inaccuracies, tighten voice, fill gaps, add examples. Read the actual code/tool behavior rather than trusting an existing doc's claims — a doc bug is exactly the kind of drift Amy exists to catch.
|
|
8
10
|
3. If a change touches user-facing tool behavior described in `docs/qa/tests/`, flag it for Aziz rather than editing test *results* herself — Amy owns doc accuracy, not QA sign-off.
|
|
@@ -25,7 +25,7 @@ The incidents behind the rules in this file are in [`docs/decisions/role-file-hi
|
|
|
25
25
|
- Team-process PRs (edits to `CLAUDE.md`, `.claude/team-roles/*.md`, `.claude/commands/*.md`) are Bob's own merge track, not Kai's — Bob reviews, labels `prompt-qa-approved`, and merges those himself (see `.claude/team-roles/bob.md` step 4). This used to be Kai's job too; per direct user instruction, 2026-07-21, it isn't anymore.
|
|
26
26
|
- Cutting a release once Aziz has signed off (`/release`)
|
|
27
27
|
- Worktree cleanup after merge (`cleanup-worktrees`) — this must never remove the persistent dev-team slot worktrees (the full `WORKTREE_ROLES` list in `scripts/setup_team.sh`, currently eight), only ad hoc worker worktrees
|
|
28
|
-
- Release readiness and documentation quality are Aziz's and Amy's territory, not Kai's — when cutting a release, hand off to Aziz (`/team-member Aziz`) for the review/QA-tier/sign-off pass rather than doing it here; route doc gaps found along the way to Amy (`documentation` label) rather than fixing them inline.
|
|
28
|
+
- Release readiness and documentation quality are Aziz's and Amy's territory, not Kai's — when cutting a release, hand off to Aziz (`/team-member Aziz`) for the review/QA-tier/sign-off pass rather than doing it here; route doc gaps found along the way to Amy (`documentation` label) rather than fixing them inline. Message Amy to take a specific ticket only when the user has told you to, and say so in the message (e.g. "the user asked me to hand you #N"): `amy.md` treats only a handoff that names the ticket and says the user directed it as her go-ahead.
|
|
29
29
|
|
|
30
30
|
Do NOT pick up ticket implementation directly in this session — hand it to a worker session via `start-worktree` (or `next-issue`, which does this automatically), or to a dev-team lane via `/team-member` if the team is active.
|
|
31
31
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: mcp-gee-sweet
|
|
3
|
-
Version: 0.9.2.
|
|
3
|
+
Version: 0.9.2.dev289
|
|
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
|
|
@@ -120,6 +120,23 @@ Three gaps were kept **as documented limitations, not implementation shortcuts**
|
|
|
120
120
|
next to ordinary text in the same paragraph — the one case it can't resolve (a paragraph
|
|
121
121
|
containing *only* a single inline code span, nothing else) is the genuinely ambiguous case
|
|
122
122
|
documented above, not a bug in the heuristic.
|
|
123
|
+
- **Code delimiters** (#923, #940, #938): a code span's content is literal, so the serializer
|
|
124
|
+
can only pick a delimiter and padding. A fenced block's fence and an inline span's delimiter
|
|
125
|
+
are one backtick longer than the longest backtick run in the content (fences at least 3).
|
|
126
|
+
An inline span is padded with one space a side when its content starts or ends with a
|
|
127
|
+
backtick, or starts *and* ends with a space (CommonMark strips one space from each end in that
|
|
128
|
+
case). Adjacent code runs sharing a link merge into one span, since two spans side by side
|
|
129
|
+
would read as one. An inline code run's line breaks become `<br>` between separate spans: a
|
|
130
|
+
span can't hold a line break, and a raw newline could start a new block. In a table cell, a
|
|
131
|
+
code line containing `|` renders as raw HTML `<code>`, because the GFM `\|` escape shows its
|
|
132
|
+
backslash in renderers that don't implement it. Text inside raw HTML is still parsed as
|
|
133
|
+
Markdown, so every ASCII punctuation character in that line becomes a numeric entity
|
|
134
|
+
(`|`, `*`, `\`, ...). Line breaks at either end of a linked code run go outside the
|
|
135
|
+
link. Because the exporter writes its own `<br>`/`<code>` HTML, plain text escapes `~`, a `<`
|
|
136
|
+
that could open a tag, and an `&` that starts an entity, so typed markup can't pass for
|
|
137
|
+
exporter markup. The export tests
|
|
138
|
+
check these against a strict CommonMark parser (`markdown-it-py`), since Python-Markdown
|
|
139
|
+
strips whitespace in code spans and closes a fence only on an exact-length match.
|
|
123
140
|
- **Tight vs. loose lists**: consecutive `BulletItem` nodes are joined with a single `"\n"`
|
|
124
141
|
rather than the `"\n\n"` used between other block types — CommonMark still parses a
|
|
125
142
|
blank-line-separated run of list items as one list either way (just "loose", wrapped in `<p>`
|
|
@@ -1016,6 +1016,9 @@ Markdown pipe table. 3 rows, 2 columns: Name/Value, Alpha/1, Beta/2 — all corr
|
|
|
1016
1016
|
**Result (2026-09-04) ✅ PASS**
|
|
1017
1017
|
create_doc_from_file(tc-d195-create-doc.md): docId+web_link, no error. get_doc_structure confirmed HEADING_1 "QA Test Document", bold/italic runs, ☑/☐ bullet items, table (Col A/Col B, one/two). Trashed.
|
|
1018
1018
|
|
|
1019
|
+
**Result (2026-10-10) ✅ PASS — PR #961 (Sky)**
|
|
1020
|
+
create_doc_from_file(tc-d195-create-doc.md): docId+web_link, no error. get_doc_structure: HEADING_1 "QA Test Document", bold/italic runs, ☑/☐/Plain item bullets, Col A/Col B table. Playwright (account check passed): heading, bold/italic, checkbox glyphs, and table render correctly. Trashed.
|
|
1021
|
+
|
|
1019
1022
|
---
|
|
1020
1023
|
|
|
1021
1024
|
### TC-DOC45: `create_doc_from_file` with a local .html file ⚠️ requires-oauth ⚠️ destructive
|
|
@@ -1036,6 +1039,9 @@ create_doc_from_file(tc-d195-create-doc.md): docId+web_link, no error. get_doc_s
|
|
|
1036
1039
|
**Result (2026-09-04) ✅ PASS**
|
|
1037
1040
|
create_doc_from_file(tc-d196-create-doc.html): docId+web_link, no error. get_doc_structure confirmed HEADING_2 "From HTML file", paragraph "Content paragraph." Trashed.
|
|
1038
1041
|
|
|
1042
|
+
**Result (2026-10-10) ✅ PASS — PR #961 (Sky)**
|
|
1043
|
+
create_doc_from_file(tc-d196-create-doc.html): docId+web_link, no error. get_doc_structure: HEADING_2 "From HTML file", paragraph "Content paragraph." Trashed.
|
|
1044
|
+
|
|
1039
1045
|
---
|
|
1040
1046
|
|
|
1041
1047
|
### TC-DOC46: `create_doc_from_file` file not found
|
|
@@ -1051,6 +1057,9 @@ create_doc_from_file(tc-d196-create-doc.html): docId+web_link, no error. get_doc
|
|
|
1051
1057
|
**Result (2026-09-04) ✅ PASS**
|
|
1052
1058
|
create_doc_from_file('~/does-not-exist.md') returned {"error": "File not found: ~/does-not-exist.md"}, no exception.
|
|
1053
1059
|
|
|
1060
|
+
**Result (2026-10-10) ✅ PASS — PR #961 (Sky)**
|
|
1061
|
+
create_doc_from_file('~/does-not-exist.md') returned {"error": "File not found: ~/does-not-exist.md"}, no exception. Unsupported .txt also still returns the no-hint message "Use .md or .html/.htm" (shared helper, format_overridable=False).
|
|
1062
|
+
|
|
1054
1063
|
---
|
|
1055
1064
|
|
|
1056
1065
|
### TC-DOC47: `write_doc_content` inline code monospace ⚠️ destructive
|
|
@@ -2804,6 +2813,9 @@ insert_local_images (default revoke_sharing) on scratch doc with "Marker: IMGMAR
|
|
|
2804
2813
|
**Result (2026-09-04) ✅ PASS**
|
|
2805
2814
|
create_doc HTML table with image in a cell: no error. get_doc_structure: cell 0 "Before", cell 2 "After", cell 1 (image) empty (""). No images key in response (image dropped at parse time, table-cell images still out of scope). No timeout/hang. Trashed.
|
|
2806
2815
|
|
|
2816
|
+
**Result (2026-10-10) ✅ PASS — PR #961 (Sky)**
|
|
2817
|
+
create_doc HTML table with an image in a cell: returned at once, no error, no `images` key. get_doc_structure: cell 0 "Before", cell 1 "", cell 2 "After". No hang. Trashed.
|
|
2818
|
+
|
|
2807
2819
|
---
|
|
2808
2820
|
|
|
2809
2821
|
### TC-DOC154: A `<pre>` block resumed after a nested table doesn't leave a spurious trailing whitespace-only paragraph ⚠️ requires-oauth ⚠️ destructive
|
|
@@ -3107,6 +3119,9 @@ Generated real ~76MB 4800x5000 (24.0MP, under 25MP pre-check) noise PNG. Main ca
|
|
|
3107
3119
|
**Result (2026-09-04) ✅ PASS**
|
|
3108
3120
|
update_doc_from_file(tc-d195-create-doc.md) on DOC_ID: returned docId matching DOC_ID, no error. get_doc_structure matches TC-DOC44's create_doc_from_file output exactly (HEADING_1, bold/italic runs, ☑/☐ items, Col A/Col B table). get_file_metadata before/after identical name/parents/webViewLink — doc identity/location preserved.
|
|
3109
3121
|
|
|
3122
|
+
**Result (2026-10-10) ✅ PASS — PR #961 (Sky)**
|
|
3123
|
+
update_doc_from_file(tc-d195-create-doc.md) on DOC_ID: docId matched, web_link present, no error. get_doc_structure matches TC-DOC44's output (HEADING_1, bold/italic, ☑/☐ items, Col A/Col B table). get_file_metadata before/after: same name/parents/webViewLink.
|
|
3124
|
+
|
|
3110
3125
|
### TC-DOC169: `update_doc_from_file` with a local .html file ⚠️ destructive
|
|
3111
3126
|
**Prompt**
|
|
3112
3127
|
> "Update Google Doc {DOC_ID} from the file <repo-root>/docs/qa/fixtures/tc-d196-create-doc.html"
|
|
@@ -3123,6 +3138,9 @@ update_doc_from_file(tc-d195-create-doc.md) on DOC_ID: returned docId matching D
|
|
|
3123
3138
|
**Result (2026-09-04) ✅ PASS**
|
|
3124
3139
|
update_doc_from_file(tc-d196-create-doc.html): docId matched, no error. get_doc_structure: HEADING_2 "From HTML file", paragraph "Content paragraph." — matches TC-DOC45.
|
|
3125
3140
|
|
|
3141
|
+
**Result (2026-10-10) ✅ PASS — PR #961 (Sky)**
|
|
3142
|
+
update_doc_from_file(tc-d196-create-doc.html): docId matched, no error. get_doc_structure: HEADING_2 "From HTML file", paragraph "Content paragraph."
|
|
3143
|
+
|
|
3126
3144
|
### TC-DOC170: `update_doc_from_file` file not found
|
|
3127
3145
|
**Prompt**
|
|
3128
3146
|
> "Update Google Doc {DOC_ID} from the file ~/does-not-exist.md"
|
|
@@ -3137,6 +3155,9 @@ update_doc_from_file(tc-d196-create-doc.html): docId matched, no error. get_doc_
|
|
|
3137
3155
|
**Result (2026-09-04) ✅ PASS**
|
|
3138
3156
|
update_doc_from_file('~/does-not-exist.md') returned {"error":"File not found: ~/does-not-exist.md"}, no exception. get_doc_content confirmed doc unchanged (still HEADING_2/paragraph from DOC169).
|
|
3139
3157
|
|
|
3158
|
+
**Result (2026-10-10) ✅ PASS — PR #961 (Sky)**
|
|
3159
|
+
update_doc_from_file('~/does-not-exist.md') returned {"error": "File not found: ~/does-not-exist.md"}, no exception. get_doc_content unchanged.
|
|
3160
|
+
|
|
3140
3161
|
### TC-DOC171: `update_doc_from_file` unsupported extension with no override returns an error, doc untouched
|
|
3141
3162
|
**Setup:** create a local file `qa-update.txt` with arbitrary plain text
|
|
3142
3163
|
|
|
@@ -3155,6 +3176,9 @@ update_doc_from_file('~/does-not-exist.md') returned {"error":"File not found: ~
|
|
|
3155
3176
|
**Result (2026-09-04) ✅ PASS**
|
|
3156
3177
|
update_doc_from_file(qa-update.txt, no override) returned {"error":"Unsupported file extension '.txt'. Use .md or .html/.htm, or pass content_format explicitly."}. get_doc_content confirmed doc untouched — no Docs API call made.
|
|
3157
3178
|
|
|
3179
|
+
**Result (2026-10-10) ✅ PASS — PR #961 (Sky)**
|
|
3180
|
+
update_doc_from_file(qa-update.txt) returned {"error": "Unsupported file extension '.txt'. Use .md or .html/.htm, or pass content_format explicitly."}. get_doc_content unchanged.
|
|
3181
|
+
|
|
3158
3182
|
### TC-DOC172: `update_doc_from_file` `content_format` explicitly overrides an unrecognized extension ⚠️ destructive
|
|
3159
3183
|
**Setup:** create a local file `qa-update-override.txt` containing `# Overridden Heading\n\nParagraph text.\n`
|
|
3160
3184
|
|
|
@@ -3171,6 +3195,9 @@ update_doc_from_file(qa-update.txt, no override) returned {"error":"Unsupported
|
|
|
3171
3195
|
**Result (2026-09-04) ✅ PASS**
|
|
3172
3196
|
update_doc_from_file(qa-update-override.txt, content_format='markdown') succeeded, no error. get_doc_structure: HEADING_1 "Overridden Heading", paragraph "Paragraph text." Fixture restored after.
|
|
3173
3197
|
|
|
3198
|
+
**Result (2026-10-10) ✅ PASS — PR #961 (Sky)**
|
|
3199
|
+
update_doc_from_file(qa-update-override.txt, content_format='markdown'): no error. get_doc_structure: HEADING_1 "Overridden Heading", paragraph "Paragraph text." Fixture restored to setup.md's seed HTML via write_doc_content afterward.
|
|
3200
|
+
|
|
3174
3201
|
---
|
|
3175
3202
|
|
|
3176
3203
|
## `get_doc_as_markdown`
|
|
@@ -3514,6 +3541,69 @@ Edge spacers (single <br> each): markdown = "| Trailing test<br> |\n| --- |\n| <
|
|
|
3514
3541
|
|
|
3515
3542
|
---
|
|
3516
3543
|
|
|
3544
|
+
### TC-DOC206: Inline code keeps a space at both ends, and adjacent code runs export as one span (#938)
|
|
3545
|
+
**Setup:** raw HTML via `write_doc_content(doc_id={DOC_ID}, content_format='html', content="<p>pad <code> both ends </code> end</p><p>adj <code>x`</code><b><code>`y</code></b> end</p>")`. The bold change splits the second paragraph's code into two Courier New runs.
|
|
3546
|
+
|
|
3547
|
+
**Prompt**
|
|
3548
|
+
> "Export doc {DOC_ID} as Markdown"
|
|
3549
|
+
|
|
3550
|
+
**Checks**
|
|
3551
|
+
- `markdown` contains ``pad ` both ends ` end``: one extra space a side, since CommonMark strips one space from each end when both ends have one
|
|
3552
|
+
- `markdown` contains ```` adj ```x``y``` end ````: the two runs merge into one span, with a three-backtick delimiter around the two-backtick run
|
|
3553
|
+
- Rendering `markdown` with a strict CommonMark renderer (e.g. `markdown-it-py`'s `commonmark` preset, not Python `markdown`, which strips code-span whitespace) gives `<code> both ends </code>` and `<code>x``y</code>`
|
|
3554
|
+
|
|
3555
|
+
**Cleanup:** write fixture content back over `{DOC_ID}`
|
|
3556
|
+
|
|
3557
|
+
**Result (2026-10-10) ✅ PASS — Kit, PR #968 round 1 (`d2449f7`).** `get_doc_as_markdown` returned ``pad ` both ends ` end\n\nadj ```x``y``` end``. markdown-it-py 4.2.0's `commonmark` preset rendered `<code> both ends </code>` and `<code>x``y</code>`. Fixture content was written back.
|
|
3558
|
+
|
|
3559
|
+
**Result (2026-10-10) ✅ PASS — Kit, PR #968 round 2 (`759362a`).** Re-run because the fix changed `_render_run`. Output was identical to round 1. Fixture content was written back.
|
|
3560
|
+
|
|
3561
|
+
---
|
|
3562
|
+
|
|
3563
|
+
### TC-DOC207: Inline code in a table cell keeps a literal `|` and puts line breaks outside the code (#938)
|
|
3564
|
+
**Setup:** raw HTML via `write_doc_content(doc_id={DOC_ID}, content_format='html', content="<table><tr><td>Head</td></tr><tr><td>pipe <code>a|b</code> and a|b</td></tr><tr><td><code>x</code><br><code>y|z</code></td></tr></table>")`. The last cell has two code-styled paragraphs.
|
|
3565
|
+
|
|
3566
|
+
**Prompt**
|
|
3567
|
+
> "Export doc {DOC_ID} as Markdown"
|
|
3568
|
+
|
|
3569
|
+
**Checks**
|
|
3570
|
+
- `markdown` contains the row `| pipe <code>a|b</code> and a\|b |`: the code's pipe is an HTML entity inside raw `<code>`, with no backslash, and the plain-text pipe is still escaped
|
|
3571
|
+
- `markdown` contains the row `` | `x`<br><code>y|z</code> | ``: the cell's paragraph break is a `<br>` between two code elements, not inside one
|
|
3572
|
+
- Rendering `markdown` with a GFM-table renderer (e.g. `markdown-it-py`'s `commonmark` preset with `table` enabled) gives a one-column table whose cells contain `<code>a|b</code>` and `<code>x</code><br><code>y|z</code>`. No cell shows a backslash
|
|
3573
|
+
|
|
3574
|
+
Not covered live: a soft break (Shift+Enter) inside an inline code run in a paragraph that also has plain text. No tool can build one (`style_doc_range` doesn't set a font), so unit tests cover it (`TestInlineCodeCommonMark::test_newline_in_inline_code_does_not_start_a_new_block`).
|
|
3575
|
+
|
|
3576
|
+
**Cleanup:** write fixture content back over `{DOC_ID}`
|
|
3577
|
+
|
|
3578
|
+
**Result (2026-10-10) ✅ PASS (checks as written); ❌ probe beyond the case — Kit, PR #968 round 1 (`d2449f7`).** `get_doc_as_markdown` returned both expected rows exactly: `| pipe <code>a|b</code> and a\|b |` and `` | `x`<br><code>y|z</code> | ``. markdown-it-py's `commonmark` preset with `table` enabled rendered a one-column table with `<code>a|b</code>` and `<code>x</code><br><code>y|z</code>`, and no backslash. Probe: a cell with code `a|*b* _c_` exported as `<code>a|*b* _c_</code>`, which renders `<em>` inside the `<code>`. A cell with code `C:\|x` exported as `<code>C:\|x</code>`, which renders as `C:&#124;x`. Both are sent back on the PR. Fixture content was written back.
|
|
3579
|
+
|
|
3580
|
+
**Result (2026-10-10) ✅ PASS — Kit, PR #968 round 2 (`759362a`).** Re-run because the fix changed the table-cell `<code>` path. Both expected rows came back exactly, and the render was the same as round 1. The round 1 probe is now TC-DOC208, which passes. Fixture content was written back.
|
|
3581
|
+
|
|
3582
|
+
---
|
|
3583
|
+
|
|
3584
|
+
### TC-DOC208: Markdown punctuation inside table-cell `<code>`, linked code after a cell break, and HTML-like plain text all export literally (#938)
|
|
3585
|
+
**Setup:** raw HTML via `write_doc_content(doc_id={DOC_ID}, content_format='html', content=...)` with this content. The `\` in `C:\|x` is one literal backslash character.
|
|
3586
|
+
|
|
3587
|
+
```html
|
|
3588
|
+
<table><tr><td>Head</td></tr><tr><td><code>a|*b* _c_</code></td></tr><tr><td><code>C:\|x</code></td></tr><tr><td><code>x</code><br><a href="https://example.com/b"><code>b</code></a></td></tr></table><p>Use <br> or &#124; and ~~old~~, AT&T a < b</p>
|
|
3589
|
+
```
|
|
3590
|
+
|
|
3591
|
+
**Prompt**
|
|
3592
|
+
> "Export doc {DOC_ID} as Markdown"
|
|
3593
|
+
|
|
3594
|
+
**Checks**
|
|
3595
|
+
- `markdown` contains the row `| <code>a|*b* _c_</code> |`: every punctuation character inside raw `<code>` is a numeric entity
|
|
3596
|
+
- `markdown` contains the row `| <code>C:\|x</code> |`: the backslash is an entity too, so it can't escape the pipe's `&`
|
|
3597
|
+
- `markdown` contains the row `` | `x`<br>[`b`](https://example.com/b) | ``: the `<br>` from the cell's paragraph break is outside the link
|
|
3598
|
+
- `markdown` contains the paragraph `Use \<br> or \| and \~\~old\~\~, AT&T a < b`: `<` before a tag, `&` starting an entity, and `~` are escaped; `AT&T` and `a < b` are not
|
|
3599
|
+
- Rendering `markdown` with `markdown-it-py`'s `commonmark` preset with `table` enabled gives cells `<code>a|*b* _c_</code>`, `<code>C:\|x</code>`, and `<code>x</code><br><a href="https://example.com/b"><code>b</code></a>`, with no `<em>` inside any `<code>`, and the paragraph renders as literal text (no line break, no `|`, no strikethrough)
|
|
3600
|
+
|
|
3601
|
+
**Cleanup:** write fixture content back over `{DOC_ID}`
|
|
3602
|
+
|
|
3603
|
+
**Result (2026-10-10) ✅ PASS — Kit, PR #968 round 2 (`759362a`).** `get_doc_as_markdown` returned all four expected lines exactly. markdown-it-py 4.2.0 (`commonmark` + `table` + `strikethrough`) rendered the cells `<code>a|*b* _c_</code>`, `<code>C:\|x</code>`, and `<code>x</code><br><a href="https://example.com/b"><code>b</code></a>`, and the paragraph `Use <br> or &#124; and ~~old~~, AT&T a < b`, with no `<em>`, `<del>`, or line break. Fixture content was written back.
|
|
3604
|
+
|
|
3605
|
+
---
|
|
3606
|
+
|
|
3517
3607
|
## Orphan fileId on inline-image sharing failure (#649)
|
|
3518
3608
|
|
|
3519
3609
|
Mirrors #420's fix in `drive/transfer.py` (see TC-D249/TC-D250 in `docs/qa/tests/drive_transfer.md`) applied to the same create()-succeeds-but-follow-up-fails shape in `docs/images.py`'s `upload_and_share_image` and `docs/content.py`'s `_resolve_image_source`: a transient failure in the `permissions().create()`/`files().get()` sharing step, after `files().create()` (or an already-uploaded `_upload_local_file`) had already succeeded, previously returned a bare `{"error": ...}` with no way to find the Drive file that now genuinely exists. Not reliably reproducible live (would require forcing a transient API failure in the exact window between create and share); verified by unit test instead, matching TC-D249/TC-D250's own convention.
|
|
@@ -3747,12 +3837,15 @@ Tool call: `insert_local_images(doc_id={DOC_ID}, images=[{"marker": "IMGMARKERBO
|
|
|
3747
3837
|
**Checks**
|
|
3748
3838
|
- `results[0]` has no `fileId` and an `error` containing "14000x13000 (182.0 megapixels)" and "25 megapixels"
|
|
3749
3839
|
- No `qa-bomb.png` appears in `{FOLDER_ID}`, and the marker text is unchanged
|
|
3750
|
-
- Re-run with `auto_downscale=True`: `results[0]` still has no `fileId`, and its `error` ends with "Could not auto-downscale it (unreadable or corrupt image, animated,
|
|
3840
|
+
- Re-run with `auto_downscale=True`: `results[0]` still has no `fileId`, and its `error` ends with "Could not auto-downscale it (unreadable or corrupt image, animated, too large to decode safely, or still over the file-size limit after shrinking)." (the last reason was added by #894/#581)
|
|
3751
3841
|
|
|
3752
3842
|
**Cleanup:** trash the doc; `rm /tmp/qa-bomb.png`
|
|
3753
3843
|
|
|
3754
3844
|
**Result (2026-09-24) ✅ PASS — Kit, PR #801 round 2 (fix `4d5f842`, issue #560).** Both checks matched exactly, and nothing was uploaded. (Round 1 reproduced the pre-fix behavior locally: `check_image_file` returned `None` for this file.) Doc trashed.
|
|
3755
3845
|
|
|
3846
|
+
**Result (2026-10-10) ✅ PASS — PR #961 (Sky)**
|
|
3847
|
+
insert_local_images with the 14000x13000 PNG: results[0] no fileId, error names "14000x13000 (182.0 megapixels)" and "25 megapixels". With auto_downscale=True: error ends with "Could not auto-downscale it (unreadable or corrupt image, animated, too large to decode safely, or still over the file-size limit after shrinking)." No qa-bomb.png in FOLDER_ID; marker text unchanged. Doc trashed.
|
|
3848
|
+
|
|
3756
3849
|
---
|
|
3757
3850
|
|
|
3758
3851
|
### TC-DOC198: `insert_local_images` — an unreadable local file fails with "failed to read local file", not a raw upload error (PR #801)
|
|
@@ -3935,3 +4028,28 @@ Two extra probes ran from a scratch copy of the script (not committed):
|
|
|
3935
4028
|
All files from both probes and the three steps, and the doc, were trashed.
|
|
3936
4029
|
|
|
3937
4030
|
**Result (2026-10-04) ✅ PASS — Kit, PR #903 round 2 (`4e392c3`).** All six steps ran from the kit worktree with its OAuth token, on a fresh scratch doc in `{FOLDER_ID}`. Each step printed `cancelled at '<point>'; the tool returned nothing` and the WARNING `Doc edit for {DOC_ID} failed or was cancelled after its images were resolved; files created by this call: <one file ID>; pre-existing files: none; still link-shared by this call: ...`. That last field listed `{KEPT_FILE_ID}` for step 4 and was `none` for every other step. Steps 2 (`share-fail`, the orphan) and 6 (`revoke`), which logged nothing in round 1's probe, now log. `list_permissions` showed no `anyone` grant on `{SHARE_FILE_ID}`, `{ORPHAN_FILE_ID}`, `{EDIT_FILE_ID}`, `{INFLIGHT_FILE_ID}`, or `{REVOKE_FILE_ID}`, and showed `anyoneWithLink`/`reader` on `{KEPT_FILE_ID}`. After `refresh_cache`, `get_doc_as_markdown` read `Before`, `IMGMARKER883`, an image, an image, `After`. So steps 1–4 left `IMGMARKER883` untouched, step 6's edit landed before its cancel, and step 5's in-flight edit also landed (an image replaced `INFLIGHT883`). All six files and the doc were trashed.
|
|
4031
|
+
|
|
4032
|
+
## Unreadable source file returns an error — `create_doc_from_file` / `update_doc_from_file` (PR #961)
|
|
4033
|
+
|
|
4034
|
+
Both tools read the local file through the shared `_read_doc_source_file` helper. Before PR #961's round 2, a path that existed but wasn't a readable UTF-8 regular file raised out of the tool (`[Errno 21] Is a directory`, `'utf-8' codec can't decode byte 0xe9 ...`) instead of returning `{"error": ...}`.
|
|
4035
|
+
|
|
4036
|
+
### TC-DOC208: A directory or non-UTF-8 source file returns `{"error": ...}`, and nothing is created or edited
|
|
4037
|
+
**Setup:**
|
|
4038
|
+
- `mkdir -p /tmp/tc-doc208/dir.md`
|
|
4039
|
+
- `python3 -c "open('/tmp/tc-doc208/latin1.html','wb').write('<p>caf\xe9</p>'.encode('latin-1'))"`
|
|
4040
|
+
- `get_doc_content(file_id={DOC_ID})`, note the content for comparison afterward
|
|
4041
|
+
|
|
4042
|
+
**Steps**
|
|
4043
|
+
1. `create_doc_from_file(local_path="/tmp/tc-doc208/dir.md", folder_id={FOLDER_ID})`
|
|
4044
|
+
2. `create_doc_from_file(local_path="/tmp/tc-doc208/latin1.html", folder_id={FOLDER_ID})`
|
|
4045
|
+
3. `update_doc_from_file(doc_id={DOC_ID}, local_path="/tmp/tc-doc208/dir.md")`
|
|
4046
|
+
4. `update_doc_from_file(doc_id={DOC_ID}, local_path="/tmp/tc-doc208/latin1.html")`
|
|
4047
|
+
|
|
4048
|
+
**Checks**
|
|
4049
|
+
- Each call returns a dict with an `error` key naming the path (any wording). None comes back as `Error executing tool ...`
|
|
4050
|
+
- `list_files(folder_id={FOLDER_ID})` shows no new Doc named `dir` or `latin1` (the filename stem `create_doc_from_file` uses as the default title; steps 1–2 fail before the Drive `create`)
|
|
4051
|
+
- `get_doc_content(file_id={DOC_ID})` matches the Setup snapshot (steps 3–4 fail before any Docs API call)
|
|
4052
|
+
|
|
4053
|
+
**Cleanup:** `rm -rf /tmp/tc-doc208`
|
|
4054
|
+
|
|
4055
|
+
**Result (2026-10-10) ✅ PASS — Sky, PR #961 round 2 (`849f0bb`).** Ran with the fixtures under this job's scratch dir instead of `/tmp/tc-doc208`. Steps 1 and 3 returned `{"error": "Not a regular file: <path>/dir.md"}`; steps 2 and 4 returned `{"error": "File is not valid UTF-8: <path>/latin1.html (invalid continuation byte at byte 6)"}`. No call surfaced as `Error executing tool`. `list_files({FOLDER_ID})` showed only the three standing fixtures. After `refresh_cache`, `get_doc_content({DOC_ID})` matched the Setup snapshot. Round 1 reproduced both raises on the pre-fix code (`075ce99`).
|
|
@@ -192,7 +192,7 @@ Same shape as v0.8.1 — stabilize on defects that surfaced since v0.9.0 shipped
|
|
|
192
192
|
- [x] `create_paragraph_bullets` (`style.py`) has the same isolated-run collapse-to-`nestingLevel 0` bug #439 fixed in `emitter.py` only — port PR #711's anchor-paragraph approach to the sibling path (PR #723) ([#713](https://github.com/khuisman/mcp-gee-sweet/issues/713))
|
|
193
193
|
- [x] Lock in the isolated-run rendering rule — an isolated bullet run renders at its true depth (`depth N` → `nestingLevel N`) at *every* depth, never clamped (decided 2026-09-09: consistency over avoiding orphaned indent; the markup generator owns the depth it emits). Extend TC-DOC190 with a `min_depth >= 2` case; state the rule in `emitter.py`'s bullet-pass comment and the `docs/emitter.py` paragraph of `CLAUDE.md` (PR #731) ([#714](https://github.com/khuisman/mcp-gee-sweet/issues/714))
|
|
194
194
|
- [x] `create_doc` markdown: a fenced code block whose body is raw HTML tags renders as loose plain paragraphs (fence ignored, indentation flattened) instead of a monospaced code block — python-markdown's raw-HTML-block handling appears to pre-empt `fenced_code`. Surfaced during #714 triage (PR #730) ([#719](https://github.com/khuisman/mcp-gee-sweet/issues/719))
|
|
195
|
-
- [
|
|
195
|
+
- [x] **Bundle:** Docs write side (emitter, html_parser, images, content), one PR closing [#509](https://github.com/khuisman/mcp-gee-sweet/issues/509), [#567](https://github.com/khuisman/mcp-gee-sweet/issues/567), [#581](https://github.com/khuisman/mcp-gee-sweet/issues/581), [#638](https://github.com/khuisman/mcp-gee-sweet/issues/638), [#715](https://github.com/khuisman/mcp-gee-sweet/issues/715) — lane A (PR #961) ([#894](https://github.com/khuisman/mcp-gee-sweet/issues/894))
|
|
196
196
|
- [x] **Bundle:** Calendar hardening, one PR closing [#459](https://github.com/khuisman/mcp-gee-sweet/issues/459), [#615](https://github.com/khuisman/mcp-gee-sweet/issues/615), [#626](https://github.com/khuisman/mcp-gee-sweet/issues/626), [#627](https://github.com/khuisman/mcp-gee-sweet/issues/627), [#726](https://github.com/khuisman/mcp-gee-sweet/issues/726) — lane B (PR #912) ([#890](https://github.com/khuisman/mcp-gee-sweet/issues/890))
|
|
197
197
|
- [x] `delete_file(permanent=True)` surfaces a misleading 404 when the caller can trash but not permanently delete (Shared Drive Content manager) — an agent can read it as "already gone" and skip cleanup; check `capabilities.canDelete` first — lane B ([#876](https://github.com/khuisman/mcp-gee-sweet/issues/876)) (PR #888)
|
|
198
198
|
- [x] SSE: SIGTERM with an SSE stream open logs an "Exception in ASGI application" traceback; the server still exits cleanly, pre-existing before #833 — lane A (PR #911) ([#868](https://github.com/khuisman/mcp-gee-sweet/issues/868))
|
|
@@ -351,7 +351,7 @@ Requires `gmail/v1` client and `https://www.googleapis.com/auth/gmail.modify` sc
|
|
|
351
351
|
- [ ] Define the correctness rubric and own execution for the composite-vs-primitive efficiency benchmark — Aziz ([#584](https://github.com/khuisman/mcp-gee-sweet/issues/584))
|
|
352
352
|
- [ ] QA `.env` has no single source of truth — `setup_team.sh` only copies `.env` into a worktree when absent, so fixture-ID changes never propagate (had to hand-sync 9 copies for #305); also `run.md` vs `setup.md` disagree on `docs/qa/.env` vs repo-root `.env`. Surfaced by PR #677, relates to [#648](https://github.com/khuisman/mcp-gee-sweet/issues/648) ([#679](https://github.com/khuisman/mcp-gee-sweet/issues/679))
|
|
353
353
|
- [x] `list_files`/`get_file_metadata` docstrings cite a stale `upload_local_file` `modifiedTime` caveat (fixed at the root by PR #472) and carry a broken self-referential contrast — from Bob's `docs/tools.md` docstring sweep; Dev/QA track, batches with #682/#683 ([#681](https://github.com/khuisman/mcp-gee-sweet/issues/681)) (PR 898)
|
|
354
|
-
- [
|
|
354
|
+
- [x] `create_doc` family docstrings name only the ~25MP inline-image limit; the ~50MB byte-size limit (#562) is equally enforced, and 3 deferring docstrings inherit the gap — from Bob's `docs/tools.md` docstring sweep. Routed to Amy (`documentation`); closed community PR #932 has a diff ([#682](https://github.com/khuisman/mcp-gee-sweet/issues/682)) (PR 970)
|
|
355
355
|
- [ ] `qa(docs)`: no comprehensive fixture exercising every Docs formatting construct in one pass — Aziz, triaged out of `backlog` 2026-09-11 ([#477](https://github.com/khuisman/mcp-gee-sweet/issues/477))
|
|
356
356
|
- [ ] Fact-check and word competitive/benchmark claims once real benchmark numbers exist — routed to Amy, triaged out of `backlog` 2026-09-11 ([#585](https://github.com/khuisman/mcp-gee-sweet/issues/585))
|
|
357
357
|
- [x] QA test-doc cross-reference cleanup after the `drive.md`/`docs.md` split (PR #582) — triaged out of `backlog` 2026-09-11 ([#590](https://github.com/khuisman/mcp-gee-sweet/issues/590)) (PR #851)
|
|
@@ -21,11 +21,21 @@ Known, deliberate gaps (documented rather than silently dropped):
|
|
|
21
21
|
from __future__ import annotations
|
|
22
22
|
|
|
23
23
|
import re
|
|
24
|
+
import string
|
|
24
25
|
|
|
25
26
|
from .ast import BulletItem, Cell, DocNode, Heading, Image, NamedBlock, Paragraph, Run, Table
|
|
26
27
|
from .emitter import _build_phantom_set
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
# Characters in plain text that Markdown would otherwise read as syntax. "~" is
|
|
30
|
+
# GFM strikethrough. "<" only when it could open an HTML tag or autolink, and
|
|
31
|
+
# "&" only when it starts an entity reference, so ordinary prose ("a < b",
|
|
32
|
+
# "AT&T") stays readable. Exported text must not be mistaken for the <br> and
|
|
33
|
+
# <code> HTML the exporter writes itself (#938).
|
|
34
|
+
_MD_ESCAPE = re.compile(
|
|
35
|
+
r"[\\`*_\[\]~]"
|
|
36
|
+
r"|<(?=[A-Za-z/!?])"
|
|
37
|
+
r"|&(?=#[0-9]{1,7};|#[xX][0-9a-fA-F]{1,6};|[A-Za-z][A-Za-z0-9]*;)"
|
|
38
|
+
)
|
|
29
39
|
_BACKTICK_RUN = re.compile(r"`+")
|
|
30
40
|
|
|
31
41
|
# A paragraph whose rendered text starts with one of these (unescaped) would be
|
|
@@ -93,23 +103,16 @@ def _render_code_block(node: Paragraph) -> str:
|
|
|
93
103
|
Run text did."""
|
|
94
104
|
segments: list[str] = []
|
|
95
105
|
buffer = ""
|
|
96
|
-
|
|
97
|
-
def fenced(text: str) -> str:
|
|
98
|
-
# Literal fences in the content must never close the surrounding block.
|
|
99
|
-
ticks = max((len(match.group()) for match in _BACKTICK_RUN.finditer(text)), default=0)
|
|
100
|
-
fence = "`" * max(3, ticks + 1)
|
|
101
|
-
return f"{fence}\n{text}\n{fence}"
|
|
102
|
-
|
|
103
106
|
for item in node.runs:
|
|
104
107
|
if isinstance(item, Run):
|
|
105
108
|
buffer += item.text
|
|
106
109
|
else:
|
|
107
110
|
if buffer:
|
|
108
|
-
segments.append(
|
|
111
|
+
segments.append(_fenced_code_block(buffer))
|
|
109
112
|
buffer = ""
|
|
110
113
|
segments.append(f"})")
|
|
111
114
|
if buffer:
|
|
112
|
-
segments.append(
|
|
115
|
+
segments.append(_fenced_code_block(buffer))
|
|
113
116
|
return "\n\n".join(segments)
|
|
114
117
|
|
|
115
118
|
|
|
@@ -171,7 +174,7 @@ def _md_link_dest(url: str) -> str:
|
|
|
171
174
|
|
|
172
175
|
def _render_inline(items: list[Run | Image]) -> str:
|
|
173
176
|
parts: list[str] = []
|
|
174
|
-
for item in items:
|
|
177
|
+
for item in _coalesce_code_runs(items):
|
|
175
178
|
if isinstance(item, Image):
|
|
176
179
|
parts.append(f"})")
|
|
177
180
|
else:
|
|
@@ -179,22 +182,63 @@ def _render_inline(items: list[Run | Image]) -> str:
|
|
|
179
182
|
return "".join(parts)
|
|
180
183
|
|
|
181
184
|
|
|
182
|
-
def
|
|
185
|
+
def _is_code_run(item: Run | Image | Table) -> bool:
|
|
186
|
+
return isinstance(item, Run) and item.font_family == "Courier New"
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _coalesce_code_runs(items: list) -> list:
|
|
190
|
+
"""Merge adjacent code runs that share a link. Docs splits a run at any
|
|
191
|
+
style change (bold, color), but rendered side by side two code spans
|
|
192
|
+
would read as one: `` `a``b` `` parses as the single span "a``b", since
|
|
193
|
+
the first span's closing delimiter and the second's opening one form one
|
|
194
|
+
backtick run. A code span can't carry those styles anyway, so the merge
|
|
195
|
+
loses nothing. Runs with different links stay apart; the link brackets
|
|
196
|
+
already separate them."""
|
|
197
|
+
merged: list = []
|
|
198
|
+
for item in items:
|
|
199
|
+
prev = merged[-1] if merged else None
|
|
200
|
+
if (
|
|
201
|
+
_is_code_run(item)
|
|
202
|
+
and prev is not None
|
|
203
|
+
and _is_code_run(prev)
|
|
204
|
+
and prev.link_url == item.link_url
|
|
205
|
+
):
|
|
206
|
+
merged[-1] = Run(
|
|
207
|
+
text=prev.text + item.text, font_family="Courier New", link_url=prev.link_url
|
|
208
|
+
)
|
|
209
|
+
else:
|
|
210
|
+
merged.append(item)
|
|
211
|
+
return merged
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _render_run(run: Run, in_table: bool = False) -> str:
|
|
215
|
+
"""Markdown for one run. `in_table` makes the result safe inside a
|
|
216
|
+
pipe-table cell (see `_table_safe`)."""
|
|
183
217
|
if run.text == "":
|
|
184
218
|
return ""
|
|
185
219
|
if run.font_family == "Courier New":
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
220
|
+
return _render_code_run(run, in_table)
|
|
221
|
+
body = _render_plain_run(run)
|
|
222
|
+
return _table_safe(body) if in_table else body
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _render_code_run(run: Run, in_table: bool) -> str:
|
|
226
|
+
# Line breaks at either end go outside the link, so a cell's paragraph
|
|
227
|
+
# break (a leading "\n" from doc_to_ast) or a trailing soft break doesn't
|
|
228
|
+
# render as a <br> inside the <a>.
|
|
229
|
+
inner = run.text.strip("\n")
|
|
230
|
+
if not inner:
|
|
231
|
+
return "<br>" * len(run.text)
|
|
232
|
+
lead = "<br>" * (len(run.text) - len(run.text.lstrip("\n")))
|
|
233
|
+
trail = "<br>" * (len(run.text) - len(run.text.rstrip("\n")))
|
|
234
|
+
body = _render_code_run_text(inner, in_table)
|
|
235
|
+
if run.link_url:
|
|
236
|
+
dest = _md_link_dest(run.link_url)
|
|
237
|
+
body = f"[{body}]({_table_safe(dest) if in_table else dest})"
|
|
238
|
+
return f"{lead}{body}{trail}"
|
|
239
|
+
|
|
197
240
|
|
|
241
|
+
def _render_plain_run(run: Run) -> str:
|
|
198
242
|
# CommonMark emphasis delimiters can't have whitespace on the inner side
|
|
199
243
|
# (e.g. "**Bold **" doesn't close — the space right before "**" means it
|
|
200
244
|
# renders as literal asterisks, not bold). Keep leading/trailing
|
|
@@ -220,7 +264,64 @@ def _render_run(run: Run) -> str:
|
|
|
220
264
|
|
|
221
265
|
|
|
222
266
|
def _escape(text: str) -> str:
|
|
223
|
-
return _MD_ESCAPE.sub(
|
|
267
|
+
return _MD_ESCAPE.sub(lambda m: "\\" + m.group(), text)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _longest_backtick_run(text: str) -> int:
|
|
271
|
+
return max((len(match.group()) for match in _BACKTICK_RUN.finditer(text)), default=0)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _fenced_code_block(text: str) -> str:
|
|
275
|
+
# A fence must be longer than any backtick run in the content, or a
|
|
276
|
+
# literal ``` line inside the block would close it early.
|
|
277
|
+
fence = "`" * max(3, _longest_backtick_run(text) + 1)
|
|
278
|
+
return f"{fence}\n{text}\n{fence}"
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _code_span(text: str) -> str:
|
|
282
|
+
"""One CommonMark code span for `text` (which has no newline).
|
|
283
|
+
|
|
284
|
+
A code span's content is literal: backslash escapes don't work inside
|
|
285
|
+
it, so the only tools are the delimiter length and padding. The
|
|
286
|
+
delimiter is one backtick longer than the longest run in the content, so
|
|
287
|
+
no literal run can close the span. Padding is exactly one space a side
|
|
288
|
+
because CommonMark strips exactly one space from each end when both ends
|
|
289
|
+
have one (and the content isn't all spaces). That stripping is what
|
|
290
|
+
separates an edge backtick from the delimiter, and it would also eat a
|
|
291
|
+
real leading and trailing space, so both cases get padded."""
|
|
292
|
+
fence = "`" * (_longest_backtick_run(text) + 1)
|
|
293
|
+
pad = text.startswith("`") or text.endswith("`")
|
|
294
|
+
pad = pad or (text.startswith(" ") and text.endswith(" ") and text.strip(" ") != "")
|
|
295
|
+
if pad:
|
|
296
|
+
text = f" {text} "
|
|
297
|
+
return f"{fence}{text}{fence}"
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _render_code_run_text(text: str, in_table: bool) -> str:
|
|
301
|
+
"""Inline code for `text`, which may span lines.
|
|
302
|
+
|
|
303
|
+
A code span can't hold a line break: CommonMark turns one into a space,
|
|
304
|
+
and in a paragraph a raw newline can start a new block (a "# " heading,
|
|
305
|
+
a "- " item) before the span is parsed. So each line becomes its own
|
|
306
|
+
span, joined by <br> the same way a table cell joins its lines.
|
|
307
|
+
|
|
308
|
+
In a table cell, a "|" splits the cell before inline parsing, and the
|
|
309
|
+
GFM `\\|` escape inside a code span shows its backslash in renderers
|
|
310
|
+
that don't implement that rule. A line holding a "|" is written as raw
|
|
311
|
+
HTML <code> instead, which no table parser splits on. Text inside raw
|
|
312
|
+
HTML is still parsed as Markdown, so every ASCII punctuation character
|
|
313
|
+
becomes a numeric entity: an entity is never Markdown syntax, and a
|
|
314
|
+
backslash can't escape the "&" that starts one."""
|
|
315
|
+
spans: list[str] = []
|
|
316
|
+
for line in text.split("\n"):
|
|
317
|
+
if not line:
|
|
318
|
+
spans.append("")
|
|
319
|
+
elif in_table and "|" in line:
|
|
320
|
+
escaped = "".join(f"&#{ord(c)};" if c in string.punctuation else c for c in line)
|
|
321
|
+
spans.append(f"<code>{escaped}</code>")
|
|
322
|
+
else:
|
|
323
|
+
spans.append(_code_span(line))
|
|
324
|
+
return "<br>".join(spans)
|
|
224
325
|
|
|
225
326
|
|
|
226
327
|
def _render_table(table: Table) -> str:
|
|
@@ -249,29 +350,38 @@ def _render_table(table: Table) -> str:
|
|
|
249
350
|
|
|
250
351
|
def _render_cell(cell: Cell) -> str:
|
|
251
352
|
parts: list[str] = []
|
|
252
|
-
for child in cell.children:
|
|
353
|
+
for child in _coalesce_code_runs(cell.children):
|
|
253
354
|
if isinstance(child, Table):
|
|
254
355
|
parts.append(_NESTED_TABLE_PLACEHOLDER)
|
|
255
356
|
elif isinstance(child, Image):
|
|
256
|
-
parts.append(f"})")
|
|
357
|
+
parts.append(_table_safe(f"})"))
|
|
257
358
|
else:
|
|
258
|
-
parts.append(_render_run(child))
|
|
259
|
-
#
|
|
260
|
-
#
|
|
261
|
-
#
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
359
|
+
parts.append(_render_run(child, in_table=True))
|
|
360
|
+
# Every part is already table-safe, with its newlines turned into <br>,
|
|
361
|
+
# so strip() here only removes incidental whitespace, never an edge
|
|
362
|
+
# spacer paragraph (#594 QA round 2; see _table_safe).
|
|
363
|
+
return "".join(parts).strip()
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _table_safe(text: str) -> str:
|
|
367
|
+
"""Make rendered non-code Markdown safe inside a pipe-table cell.
|
|
368
|
+
|
|
369
|
+
Pipe-table cells are single-line: a literal newline (from a multi-
|
|
370
|
+
paragraph cell, see doc_to_ast.py's _cell_content_to_children) would
|
|
371
|
+
break the table's row structure, so it becomes a <br> instead. "|" is
|
|
372
|
+
escaped for the same structural reason.
|
|
373
|
+
|
|
374
|
+
This runs per part, before _render_cell's strip(), not on the joined
|
|
375
|
+
cell text after it (#594 QA round 2): a spacer paragraph at the very
|
|
376
|
+
start or end of a cell shows up as a leading/trailing "\\n", and
|
|
377
|
+
stripping first silently ate exactly that newline. "<br>" is not
|
|
378
|
+
whitespace, so converting first keeps the spacer.
|
|
379
|
+
|
|
380
|
+
Code runs never go through this: _render_code_run_text writes its own
|
|
381
|
+
<br> and table-safe <code>, and escaping its output again would put a
|
|
382
|
+
literal backslash inside the code (#938). Only a code link's
|
|
383
|
+
destination is passed through here."""
|
|
384
|
+
return text.replace("\n", "<br>").replace("|", "\\|")
|
|
275
385
|
|
|
276
386
|
|
|
277
387
|
def _render_comment(comment: dict) -> str:
|