mcp-gee-sweet 0.9.6.dev134__tar.gz → 0.9.6.dev136__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.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/CLAUDE.md +4 -3
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/PKG-INFO +1 -1
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/known-limitations.md +29 -4
- mcp_gee_sweet-0.9.6.dev136/docs/qa/fixtures/md/tc-doc246/native-lines.html +4 -0
- mcp_gee_sweet-0.9.6.dev136/docs/qa/fixtures/md/tc-doc246/rules.md +11 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/tests/docs_content.md +41 -3
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/roadmap.md +4 -4
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/ast.py +5 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/ast_to_markdown.py +65 -25
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/doc_to_ast.py +93 -4
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/emitter.py +87 -6
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/html_parser.py +25 -28
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_docs_core.py +16 -4
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_docs_markdown_export.py +57 -0
- mcp_gee_sweet-0.9.6.dev136/tests/test_docs_rule.py +403 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/commands/cleanup-worktrees.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/commands/merge-pr.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/commands/notify-kai.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/commands/notify-partner.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/commands/orchestrator.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/commands/prep-for-pr.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/commands/qa-kickoff.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/commands/release.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/commands/retro.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/commands/roadmap-status.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/commands/security-advisory.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/commands/start-worktree.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/commands/team-member.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/commands/verify-pr.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/settings.json +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/team-roles/amy.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/team-roles/aziz.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/team-roles/bob.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/team-roles/dev.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/team-roles/joy.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/team-roles/kai.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/team-roles/qa.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.claude/team-roles/rio.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.dockerignore +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.gitattributes +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.github/dependabot.yml +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.github/workflows/ci.yml +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.github/workflows/docs.yml +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.github/workflows/mcpb.yml +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.github/workflows/publish.yml +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.gitignore +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.mcpbignore +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.pre-commit-config.yaml +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/.python-version +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/CHANGELOG.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/CODE_OF_CONDUCT.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/CONTRIBUTING.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/Dockerfile +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/LICENSE +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/Makefile +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/README.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/SECURITY.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docker-compose.yml +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/assets/logo.png +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/auth.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/client-setup.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/configuration.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-async-tool-execution.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-auth-status-limitation-categories.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-cache-invalidation.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-chart-theming.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-comments-first-class.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-composite-tools.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-condition-type-docstring-generation.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-converted-doc-change-detection.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-dev-team.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-docs-formatting.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-fork.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-gmail-domain.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-grid-data-size-cap.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-http-socket-timeout.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-impact-reach-triage.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-local-fs-test-harness.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-mcp-v2-resource-context-access.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-pillow-image-dependency.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-prompt-qa-role.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-publishing.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-release-docs-roles.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-repositioning.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-response-size-cap-generalization.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-response-size-cap-reevaluation-519.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-review-finding-ticket-bar.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-supports-all-drives-lint-check.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-sync-folder-recursion.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-testing.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/decision-tool-error-visibility.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/index.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/decisions/role-file-history.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design/blockquote-representation.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design/borderless-table-columns.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design/cancelled-write-invalidation.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design/doc-replace-backup.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design/doc-revision-export.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design/doc-to-markdown.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design/docs-ast-pipeline.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design/effectiveformat-spike.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design/gmail-send-confirmation.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design/heading-anchor-resolution.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design/image-conversion.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design/image-share-lifecycle.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design/index.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design/local-file-access-policy.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design/markdown-parser.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design/markdown-support.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design/mcp-bundle.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design/module-history.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design/oauth-reauthorize-from-tool-call.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design/phase3-theme-system.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design/sse-consent-off-event-loop.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/design.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/index.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/notes-read.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/README.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/realistic/r01-meeting-notes.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/realistic/r02-project-plan.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/realistic/r03-spec-prd.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/realistic/r04-readme.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/realistic/r05-status-report.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/realistic/r06-howto.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/realistic/r07-comparison.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/realistic/r08-research-summary.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/realistic/r09-changelog.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/realistic/r10-task-plan.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/realistic/r11-incident-postmortem.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/realistic/r12-interview-guide.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/realistic/r13-api-reference.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/realistic/r14-study-notes.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/realistic/r15-travel-itinerary.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/realistic/r16-planning-notes.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/state/dropped-links.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/state/theme.json +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/state/www-scheme.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/docstart/ds-01-starts-bullets.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/docstart/ds-02-starts-numbered.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/docstart/ds-03-starts-table.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/docstart/ds-04-starts-code.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/docstart/ds-05-starts-quote.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/docstart/ds-06-starts-image.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/docstart/ds-07-starts-rule.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/docstart/ds-08-ends-table.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/docstart/ds-09-ends-list.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/docstart/ds-10-empty.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/docstart/ds-11-whitespace-only.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/docstart/ds-12-no-trailing-newline.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/docstart/ds-13-crlf-line-endings.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/docstart/ds-14-front-matter.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/docstart/ds-15-rule-then-setext-heading.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/docstart/ds-16-front-matter-lists.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-adjacency-blank.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-adjacency-noblank.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-blockquotes.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-breaks-rules.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-code-blocks.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-containment.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-emphasis.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-escapes.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-headings.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-images.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-inline-code.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-inline-html.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-inline-matrix.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-links.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-lists-blocks.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-lists-boundaries.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-lists-markers.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-lists-nesting.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-long-document.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-tables.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-tasks.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/syntax/md-unicode.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/md/tc-doc245/relative-image.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/qa fixture pixel /303/251.png" +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/qa-fixture-pixel.png +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-d195-create-doc.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-d196-create-doc.html +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-d213-dollar-escape.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-d226-heading-table.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc102-image-conversion.html +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc103-image-conversion.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc104-nested-lists.html +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc105-nested-lists-gfm.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc106-nested-lists-4space.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc135-paragraph-boundary.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc136-interrupted-block-boundary.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc137-inline-hr-no-block-ancestor.html +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc138-nbsp-spacer-paragraph.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc141-mismatched-list-tags.html +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc142-heading-anchors.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc144-anchor-in-table-cell.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc145-anchor-on-first-element.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc240-bullet.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc240-heading.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc240-normal.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc240-quote.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc240-seed.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc241-links.html +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc241-links.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc241-write.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc242-loose.html +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures/tc-doc242-loose.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/fixtures.template.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/gmail-test-plan.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/operations.yaml +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/playwright_oauth.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/.gitkeep +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-06-02.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-06-14.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-06-17.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-06-18.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-06-21-drive-discovery.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-06-21-sheets-delete-clear.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-06-28.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-07-04-docs.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-07-04-drive-smoke.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-07-04-infra.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-07-04-sheets_read.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-07-04.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-09-04.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-10-17-pass-audit.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-10-17-spec.txt +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-10-17.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-10-18-markdown-phase2.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-10-18-markdown.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-10-22-markdown-boundaries-state.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-10-25-markdown.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-10-28-markdown-pr1286.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-10-29-markdown-pr1295.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-10-30-markdown-pr1301.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/results/2026-10-30-markdown-pr1303.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/retro-v0.8.0.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/retro-v0.9.0.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/retro-v0.9.2.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/run.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/runs/README.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/runs/v0.8.0.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/runs/v0.8.1.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/runs/v0.9.0.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/runs/v0.9.1.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/runs/v0.9.2.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/runs/v0.9.3.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/setup.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/tests/calendar.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/tests/docs_layout.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/tests/docs_markdown.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/tests/docs_style.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/tests/docs_tables.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/tests/drive_activity.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/tests/drive_files.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/tests/drive_sharing.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/tests/drive_transfer.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/tests/gmail.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/tests/infra.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/tests/sheets_charts.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/tests/sheets_mgmt.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/tests/sheets_read.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/qa/tests/sheets_write.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/style-guide.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/docs/tools.md +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/manifest.json +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/mkdocs.yml +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/pyproject.toml +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/pyrightconfig.json +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/scripts/build_mcpb.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/scripts/check_supports_all_drives.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/scripts/gen_tool_docs.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/scripts/kai_status.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/scripts/lane_context_hook.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/scripts/oauth_setup.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/scripts/qa_cancel_inflight_write.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/scripts/qa_cancel_insert_local_images.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/scripts/qa_compile_results.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/scripts/qa_gen_md_fixtures.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/scripts/qa_gmail_fixtures.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/scripts/qa_launch_mcpb.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/scripts/qa_revision_temp_doc_parent.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/scripts/setup_team.sh +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/scripts/sync_slot_config.sh +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/scripts/write_team_mcp_configs.sh +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/server.json +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/.env.template +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/__init__.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/__main__.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/auth.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/cache.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/http_auth.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/http_transport.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/local_files.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/reauth.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/server.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/sse_shutdown.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/__init__.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/atomic_write.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/cache.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/calendar.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/concurrency.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/__init__.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/anchors.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/batch_retry.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/comments.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/content.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/editing.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/images.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/indices.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/invalidation.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/layout.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/markdown_export.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/md_to_html.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/named_ranges.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/style.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/tables.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/drive/__init__.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/drive/activity.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/drive/files.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/drive/sharing.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/drive/transfer.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/front_matter.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/gmail.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/http_errors.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/mime_types.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/pagination.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/response_limits.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/sheets/__init__.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/sheets/data.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/sheets/helpers.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/sheets/structure.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/__init__.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/conftest.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/drive/__init__.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/drive/test_activity.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/drive/test_files.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/drive/test_sharing.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/drive/test_transfer.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/drive/test_transfer_front_matter.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/drive/test_transfer_pagination.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/drive/test_transfer_query_escaping.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/fakes.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/integration/__init__.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/integration/conftest.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/integration/test_local_fs_harness.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/sheets/__init__.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/sheets/test_data.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/sheets/test_helpers.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/sheets/test_structure.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_atomic_write.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_auth.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_cache.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_cache_isolation.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_calendar.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_cancelled_write_invalidation.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_check_supports_all_drives.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_concurrency.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_dependency_floors.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_docs_anchors.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_docs_batch_retry.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_docs_comments.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_docs_content.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_docs_dropped_links.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_docs_editing.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_docs_images.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_docs_indices.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_docs_layout.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_docs_md_to_html.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_docs_named_ranges.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_docs_relative_link.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_docs_revision_export.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_docs_style.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_docs_tables.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_front_matter.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_gen_tool_docs.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_get_doc_structure_inline_objects.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_gmail.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_gmail_send_confirm.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_http_auth.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_http_errors.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_http_transport.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_kai_status.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_lane_context_hook.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_local_file_sites.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_local_files.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_mcpb_manifest.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_mime_types.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_python_versions.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_qa_gen_md_fixtures.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_qa_gmail_fixtures.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_reauth.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_response_limits.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_server.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_server_json.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_sse_shutdown.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_sync_slot_config.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/tests/test_team_mcp_configs.py +0 -0
- {mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/uv.lock +0 -0
|
@@ -76,17 +76,17 @@ Each entry below says what a module owns and the invariants a future edit could
|
|
|
76
76
|
- `docs/layout.py` — tools (`create_header`, `create_footer`)
|
|
77
77
|
- `docs/comments.py` — tools (`list_doc_comments`, `add_doc_comment`, `resolve_doc_comment`) via the Drive `comments`/`replies` resource, not the Docs API
|
|
78
78
|
- `docs/named_ranges.py` — helper (`_create_named_range`) and tools (`create_named_range`, `create_bookmark`), extracted out of `content.py` (#371) since they're fully self-contained. The Docs API has no bookmark-creation endpoint — `create_bookmark` is a thin wrapper over `createNamedRange` spanning a single character; neither tool's output is a link target (only UI-created bookmarks/headings are).
|
|
79
|
-
- `docs/doc_to_ast.py` / `docs/ast_to_markdown.py` / `docs/markdown_export.py` — the read side (`get_doc_as_markdown`, opt-in `include_comments` for non-resolved comments via the Drive `comments` resource). It walks `documents().get()` into the same AST the write side uses and serializes that to Markdown, instead of cleaning up Google's styled HTML export (`docs/design/doc-to-markdown.md`). A paragraph is a fenced code block only when *every* run is code-styled (Courier New), so a paragraph holding only one inline code span renders as a block (documented in `docs/known-limitations.md`). Exception (#951): a fence can't hold a link, so a code paragraph with a linked run renders as linked inline code spans instead. A linked run that is only line breaks doesn't count, and neither does a linked image. Every image renders through `_render_image`, which keeps its link, and one inside a code paragraph renders outside the fence (`_render_code_block`). Links come from `_link_url`, shared by text runs and images, which rebuilds a heading or bookmark link (reported by ID, with no `url`) as a same-document URL. Empty-runs paragraphs in table cells are kept as spacers, and `_render_cell` converts `"\n"` to `<br>` *before* stripping. Emphasis markers go inside surrounding whitespace (CommonMark flanking). `_text_run_to_run` turns every `"\v"` (soft break) into `"\n"`, and a plain run's `"\n"` renders as `<br>` with edge breaks outside its link (#1057); `get_doc_structure`/`find_in_doc` still decode only Courier New runs (`decode_code_run_text`). A nested table renders a placeholder note. An ordered item renders its ordinal from `BulletItem.number`, which `document_to_ast` counts per `listId` and nesting level over body paragraphs, never per contiguous run, since a table or paragraph between two items doesn't restart a Docs list (#1208). `startNumber` 0 means 1 only for lettered and Roman levels. A sublist whose first number isn't 1 writes `1.` for that item, since CommonMark won't let it interrupt the parent's text. A block inside a list item (#1302, the inverse of #1094) is found by indent in `doc_to_ast._ListItemContext`: a non-bullet paragraph after an item with `indentStart == indentFirstLine` at that item's level's text indent (or a shallower level's), plus 36pt per quote level, gets that level as `list_depth`, with its quote depth counted from the text indent, not the margin. It tracks the `listId` open at each depth, since a sublist of the other type has its own (#1072). A table (no indent in the API) and a quote whose indent also reads as a deeper margin quote wait: they're inside an item only when the list goes on after them (an item of a list open at that depth, a sublist nested under the last item, or a plainly in-item block), else outside as read. `ast_to_markdown._OpenItems` indents such a block to its item's content column (`_item_prefix`'s width), never a fixed width, with the item's quote levels outside the indent whatever depth the block reads with. A bulleted `HEADING_N` paragraph that isn't a checkbox item is a `BulletItem` with `heading_level`, rendered `- # Title`; every other item's text goes through `_escape_leading_block_marker`. [History](docs/design/module-history.md#toolsdocsdoc_to_astpy--docsast_to_markdownpy--docsmarkdown_exportpy).
|
|
79
|
+
- `docs/doc_to_ast.py` / `docs/ast_to_markdown.py` / `docs/markdown_export.py` — the read side (`get_doc_as_markdown`, opt-in `include_comments` for non-resolved comments via the Drive `comments` resource). It walks `documents().get()` into the same AST the write side uses and serializes that to Markdown, instead of cleaning up Google's styled HTML export (`docs/design/doc-to-markdown.md`). A paragraph is a fenced code block only when *every* run is code-styled (Courier New), so a paragraph holding only one inline code span renders as a block (documented in `docs/known-limitations.md`). Exception (#951): a fence can't hold a link, so a code paragraph with a linked run renders as linked inline code spans instead. A linked run that is only line breaks doesn't count, and neither does a linked image. Every image renders through `_render_image`, which keeps its link, and one inside a code paragraph renders outside the fence (`_render_code_block`). Links come from `_link_url`, shared by text runs and images, which rebuilds a heading or bookmark link (reported by ID, with no `url`) as a same-document URL. Empty-runs paragraphs in table cells are kept as spacers, and `_render_cell` converts `"\n"` to `<br>` *before* stripping. Emphasis markers go inside surrounding whitespace (CommonMark flanking). Two adjacent quoted blocks are one Docs quote (one bar; separate quotes have the emitter's unquoted separator between them), so `_OpenItems.separator` makes the blank line between two nodes carry the quote levels both write at the margin (a block in a quoted list's item writes its item's), or the export reads as two quotes. An empty list item and its first block are joined by a newline, not a blank line: CommonMark ends an item that starts with a blank line (`_starts_empty_item`). `_text_run_to_run` turns every `"\v"` (soft break) into `"\n"`, and a plain run's `"\n"` renders as `<br>` with edge breaks outside its link (#1057); `get_doc_structure`/`find_in_doc` still decode only Courier New runs (`decode_code_run_text`). A nested table renders a placeholder note. An ordered item renders its ordinal from `BulletItem.number`, which `document_to_ast` counts per `listId` and nesting level over body paragraphs, never per contiguous run, since a table or paragraph between two items doesn't restart a Docs list (#1208). `startNumber` 0 means 1 only for lettered and Roman levels. A sublist whose first number isn't 1 writes `1.` for that item, since CommonMark won't let it interrupt the parent's text. A block inside a list item (#1302, the inverse of #1094) is found by indent in `doc_to_ast._ListItemContext`: a non-bullet paragraph after an item with `indentStart == indentFirstLine` at that item's level's text indent (or a shallower level's), plus 36pt per quote level, gets that level as `list_depth`, with its quote depth counted from the text indent, not the margin. It tracks the `listId` open at each depth, since a sublist of the other type has its own (#1072). A table (no indent in the API) and a quote whose indent also reads as a deeper margin quote wait: they're inside an item only when the list goes on after them (an item of a list open at that depth, a sublist nested under the last item, or a plainly in-item block), else outside as read. `ast_to_markdown._OpenItems` indents such a block to its item's content column (`_item_prefix`'s width), never a fixed width, with the item's quote levels outside the indent whatever depth the block reads with. A bulleted `HEADING_N` paragraph that isn't a checkbox item is a `BulletItem` with `heading_level`, rendered `- # Title`; every other item's text goes through `_escape_leading_block_marker`. [History](docs/design/module-history.md#toolsdocsdoc_to_astpy--docsast_to_markdownpy--docsmarkdown_exportpy).
|
|
80
80
|
- `docs/ast.py` — dataclass schema
|
|
81
81
|
- `docs/html_parser.py` — HTML→AST. [History](docs/design/module-history.md#toolsdocshtml_parserpy). Invariants:
|
|
82
82
|
- Bare top-level text gets an implicit paragraph. `_tag_depth` counts open non-block tags, excluding `_VOID_TAGS` (stdlib `HTMLParser` never closes a void tag written without a slash), so text inside only an inline tag stays a no-op. `_finalize()` flushes the block still open at end of input.
|
|
83
|
-
- `_emit_block_node(preserve_if_empty)`: a block's own first close keeps an empty node (`runs=[]`) as a paragraph boundary; a resumed frame's re-close (`_block_resumed`) still drops an empty result. A fresh `<p>` (only `<p>`, not `<li>` or headings, and not resumed) keeps whitespace-only runs, since ` ` lines are deliberate spacers. A fresh `<li>` always emits its `BulletItem`, with `runs=[]` when it has no text (#1075): a Markdown bare `-`, `<li> </li>`, and an `<li>` wrapping only a nested list or block are empty bullets, as a browser shows their markers. A bare `<hr>` appends an empty `Paragraph
|
|
83
|
+
- `_emit_block_node(preserve_if_empty)`: a block's own first close keeps an empty node (`runs=[]`) as a paragraph boundary; a resumed frame's re-close (`_block_resumed`) still drops an empty result. A fresh `<p>` (only `<p>`, not `<li>` or headings, and not resumed) keeps whitespace-only runs, since ` ` lines are deliberate spacers. A fresh `<li>` always emits its `BulletItem`, with `runs=[]` when it has no text (#1075): a Markdown bare `-`, `<li> </li>`, and an `<li>` wrapping only a nested list or block are empty bullets, as a browser shows their markers. A bare `<hr>` appends a rule, an empty `Paragraph` with `rule=True` (#1096). `<pre>` builds its node directly and applies the same fresh-vs-resumed rule itself, keeping a fresh `<pre>`'s whitespace.
|
|
84
84
|
- Any `</ol>` or `</ul>` pops `_list_ordered` by count, whatever type opened it, and must run *before* `_resume_interrupted_block`. List frames match by `list_depth`, not tag: the frame is popped once its depth is reached, but the outer block is resumed only on an exact tag match.
|
|
85
85
|
- `_resume_interrupted_block` flushes whatever block is open, unconditionally, before anything else.
|
|
86
86
|
- Every leaf-node construction (`Heading`, `Paragraph`, `BulletItem`, `NamedBlock`) must pass `**self._blockquote_fields()`, which sets `blockquote_depth` and `blockquote_path` from the open-`<blockquote>` id stack (`docs/design/blockquote-representation.md`). Without the path, two adjacent quotes are indistinguishable (#1098). A top-level `Table` gets `blockquote_depth` only, at `</table>`, for its indent (#1097). `_make_bullet_item` clamps depth to `>= 0` for an orphan `<li>`.
|
|
87
87
|
- A block inside a list item (#1094) is that block's own node with `list_depth`/`list_path` naming the item, from `_list_item_fields`, which every non-item leaf construction (and a top-level `Table`, at `</table>`) must pass. The item is the one whose later segment is open, else the innermost `<li>` frame on `_block_stack` whose list ids are exactly the ones open now (each `_BlockFrame` records the interrupted block's `_block_lists`), so a block loose in a list nested in the item isn't the item's. "Open now" is the open block's own `_block_lists`, not the live `_list_ids`, since a block left open until its list's `</ul>` is flushed after the pop (PR #1301 QA round 1). The walk skips a non-matching `<li>` frame instead of stopping at it: an unclosed item in a nested list leaves its frame behind after the `</ul>` (PR #1301 QA round 2). Match by ids, never by depth: a stale frame from a closed list has the depth of a later list (PR #1301 QA round 3). A resumed `<li>` segment (text after a nested list, quote, code block, paragraph or rule) is therefore a `Paragraph` inside the item, never a new `BulletItem`. A heading that opens an `<li>` (`- # Title`) is the item's own text: `BulletItem.heading_level`, the item emitted at the heading's close and its later segment resumed. Its tag stays on `_li_heading_tags` until that `</hN>`, so the end tag is consumed even when a nested block interrupted the item first; never let it reach the inline end-tag path, which would decrement `_tag_depth` for a start tag that never incremented it.
|
|
88
88
|
- In a table cell, a whitespace run holding a line break is dropped at a segment's start (the cell's start, or just after a nested table). At the end of the text so far it's held as runs with their own style and link (`_cell_held_runs`), and written only if content follows in the same segment, which ends at a nested `<table>` or `</td>` (#1073). Nothing inside a cell's `<pre>` is trimmed (`_cell_pre_depth`). Everything else in a cell parses as it did before; collapsing every source line break merged a cell's `<p>`s and `<pre>` lines (PR #1215 QA round 2), and whether a newline in cell text should collapse is #1221 (`docs/design/image-conversion.md`).
|
|
89
|
-
- `_interrupt_open_block` drops trailing whitespace holding a line break from the outgoing segment's unflushed source text: markdown-it writes `<li>a\n<ul>` for every nested block, and the `\n` would become an empty paragraph (#1116). A `<br>` goes into the run buffer as `_LineBreak`, never a bare `"\n"`, so that trim leaves it. A resumed segment likewise drops the line break at its start (`handle_data`), keeping an authored ` `. Neither trim runs in `<pre>` (`_in_pre`), where the break and indentation are the code's. An `<hr>` in
|
|
89
|
+
- `_interrupt_open_block` drops trailing whitespace holding a line break from the outgoing segment's unflushed source text: markdown-it writes `<li>a\n<ul>` for every nested block, and the `\n` would become an empty paragraph (#1116). A `<br>` goes into the run buffer as `_LineBreak`, never a bare `"\n"`, so that trim leaves it. A resumed segment likewise drops the line break at its start (`handle_data`), keeping an authored ` `. Neither trim runs in `<pre>` (`_in_pre`), where the break and indentation are the code's. An `<hr>` in any open block, outside `<pre>` and inline tags, interrupts and resumes it: after text, in a resumed segment, in a fresh `<li>` (an empty bullet, #1075), and in a fresh block with no text, whose empty first segment emits nothing, so `<p><hr></p>` is the rule alone (#1096).
|
|
90
90
|
- Known gap: a resumed block's genuine trailing ` ` is dropped (#444).
|
|
91
91
|
- `docs/emitter.py` — AST→Docs API requests, plus the multi-phase table fill (nested tables, cell styling). [History](docs/design/module-history.md#toolsdocsemitterpy). Invariants:
|
|
92
92
|
- Bullet depth is encoded as leading `"\t"` on each paragraph's text, and `createParagraphBullets` computes levels only within one call. So each top-level list gets exactly one call over its whole range, nested lists of either type included; one call per paragraph creates separate lists and breaks numbering. `_bullet_lists` (#1072) returns each top-level list with the lists nested in it that aren't merged into it: every list of the other type, and a same-type list that follows another sublist under the same item (which restarts). A top-level list's range spans every item nested in it, a trailing nested list's too, or that list's tabs go unconsumed. Each of those is then taken back out, parent before child (`_top_list_requests`: delete its bullets, zero its indent, re-insert its tabs, anchor-wrap it with its own preset), so the outer list keeps counting past it. A `createParagraphBullets` range containing an already-listed paragraph joins the unlisted ones to that paragraph's list (confirmed live), so the outer call can't come last. A range whose preceding paragraph is in a same-preset list joins that list too (confirmed live, #1241), so a nested list's anchor wrap puts a barrier paragraph before its anchor (`barrier=True`). Each `BulletItem` carries `list_path`, the ids of its enclosing `<ul>`/`<ol>` lists (`html_parser._push_list`), captured when its block segment opens (`_block_lists`), since an unclosed `<li>` is flushed after `</ul>` pops. An open list that doesn't enclose an item is closed first (`_encloses`), and a shallower item rejoins the block's first list only from a list enclosing it (`_rejoins_root`), so two adjacent same-type lists stay two Docs lists at any depth; an item without ids groups by depth and type alone. A nested list re-inserts and re-consumes its own tabs, so each item's tabs are consumed once, net.
|
|
@@ -96,6 +96,7 @@ Each entry below says what a module owns and the invariants a future edit could
|
|
|
96
96
|
- `insertTable` gives the table its indent, which no API field can reset afterward, and every empty cell its text style, from the paragraph at the insertion point. So each table gets a placeholder paragraph in the phase-1 text, reset to plain after the bullet pass by `_table_placeholder_requests` (a table between two same-preset list items sits inside their one bullet range; a quoted table keeps its quote's indent via `Table.blockquote_depth`, which `_quoted` still ignores). The table is inserted there, and the placeholder, pushed past the table, is deleted by the `deleteContentRange` right after the `insertTable` (`_inserted_table_length`). Never insert a table at the start of the next block's text: it takes that block's indent and first-character style (#1097). `_positional_insert_tail_start` keeps that delete in the positional tail by its place after an `insertTable`.
|
|
97
97
|
- `_strip_bullet_leading_tabs` removes a literal leading tab from a bullet's own text (it would read as depth) and returns new `Run`/`Image` objects; it must never mutate caller-owned AST nodes. Dropping an authored leading tab is a deliberate, documented loss.
|
|
98
98
|
- Blockquote nodes get a gray `borderLeft` and `indentStart` = `indentFirstLine` = 36pt × depth (`indentFirstLine` is absolute, not relative to `indentStart`). `createParagraphBullets` overwrites both indents, so a quoted `BulletItem`'s request runs after the whole bullet pass and before the table/image inserts, at the list level's own indents (`list_level_indents`, level capped at 8 as Docs caps `nestingLevel`) plus the quote's. Every position used after the bullet pass maps through `_after_bullet_pass`. `doc_to_ast.py` subtracts the level's indent when inferring depth.
|
|
99
|
+
- A rule (`Paragraph.rule`, #1096) is an empty 6pt paragraph with a gray `borderBottom`, since the API can't insert a `horizontalRule`. It also sets `borderBetween`: Docs joins adjacent paragraphs with the same borders and draws `borderBottom` only under the last, so two rules in a row drew one line (confirmed live). A quoted rule gets no `spaceBelow` and a 4pt line instead: a paragraph's spacing isn't covered by the quote bar when its neighbours' borders differ, so any gap split one quote into two on screen (PR #1326 QA round 1). `_rule_requests` must come before a quote's edge spacing for the same range, and a rule inside a list item is drawn after the bullet pass (`deferred`), like its indent. The read side recognizes the stand-in by its whole signature (`is_rule_style`: bottom and matching between-border, no top or right), never a bottom border alone, which an empty line in a bordered box also has; `_build_blank_para_before_table_collapses` uses the same test to skip a rule before a table. `doc_to_ast` splits a paragraph at each native `horizontalRule` (`_split_at_horizontal_rules`; Drive's import puts one in the same paragraph as text, a list item's included), keeping the text's own type and putting a line after a list item's text inside that item. `ast_to_markdown` writes a rule `---`, or `***` as the first line, where `---` opens front matter.
|
|
99
100
|
- A code-styled paragraph's internal `"\n"` breaks become `"\v"` (`_is_code_paragraph`) so a fenced block stays one Docs paragraph; `doc_to_ast.py` reverses it.
|
|
100
101
|
- Table-cell images (#1073) go in during `fill_tables`, not phase 1, from the URIs in `CellImages`. `fill_tables` requires one, and every cell-fill `batchUpdate` goes through `CellImages.execute`. `_run_group_fill_requests` writes a cell's text and styles as if no image were there, then each image (through `CellImages.insert_requests`) at the offset of the text before it, latest first. `execute` replaces an unfetchable image with its fallback text and resends through `batch_retry.send_dropping_failed_images`, the loop the body's retry also uses, and splits a batch only at a failed image, never elsewhere: an earlier cell's text moves every later position. Its request bookkeeping holds the request dicts and is cleared after each batch, so an `id()` key can't outlive its request. `_text_offset_since_last_table` counts what each image took (`CellImages.doc_length`): one index embedded, its fallback text's UTF-16 length when a never-resolved or dropped image was written as text (#1099). `CellImages.embedded` grows as each batch lands, and `_apply_doc_content` gives every resolved cell image missing from it an `error` after the fill and on any `Exception` out of the edit, phase 1 included, but not on a cancellation, whose last batch can still land (`docs/design/image-conversion.md`).
|
|
101
102
|
- Docs joins the left borders of adjacent paragraphs with the same border and indents into one bar, whatever their spacing. So `_with_quote_separators` puts an unbordered empty paragraph, collapsed by `_collapse_paragraph_requests`, between two adjacent quoted nodes whose `blockquote_path`s aren't one a prefix of the other (#1098). `_block_spacing` gives a code block 8pt above and below, and a quote 8pt where it meets content outside it. A separator is spaced on one side only (below the quote before it), and a `Heading` or TITLE/SUBTITLE `NamedBlock` gets no quote-edge space, since an explicit value replaces its named style's own. `_spacing_requests` puts `spaceAbove` on a node's first Docs paragraph and `spaceBelow` on its last, since a node with a `"\n"` in its text spans several. A quoted `BulletItem`'s spacing is emitted after the bullet pass, like its quote styling. `_build_blank_para_before_table_collapses` resets every empty paragraph before a table to plain, a backstop since the table placeholder (above). It deletes the bullet first, because `deleteParagraphBullets` adds indent, then clears indents and border. It leaves the paragraph between two tables at full height, so they don't touch.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: mcp-gee-sweet
|
|
3
|
-
Version: 0.9.6.
|
|
3
|
+
Version: 0.9.6.dev136
|
|
4
4
|
Summary: MCP server for Google Workspace — Sheets, Drive, Docs (HTML or Markdown), Calendar, and Gmail.
|
|
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
|
|
@@ -91,15 +91,19 @@ needs Markdown inside it as HTML instead.
|
|
|
91
91
|
**What:** A table inside a list's last item (`1. a\n\n | x |\n |---|\n`) is written to the
|
|
92
92
|
Doc indented under the item, but `get_doc_as_markdown` exports it at the margin, after the list.
|
|
93
93
|
A quote that ends a list's last item (`- a\n\n > q\n`) exports as a deeper quote at the margin
|
|
94
|
-
(`> > q`).
|
|
95
|
-
list
|
|
96
|
-
|
|
94
|
+
(`> > q`). So does a paragraph, code block, heading, or rule inside the last item of a *quoted*
|
|
95
|
+
list (`> - a\n>\n> b\n` exports `b` as `> > b`). Any of these exports inside its item when the
|
|
96
|
+
list goes on after it: a later item of the list, a sublist nested under the item (of either
|
|
97
|
+
type), or a paragraph indented in the item. A paragraph, code block, or heading inside an item of
|
|
98
|
+
an unquoted list always exports inside it.
|
|
97
99
|
|
|
98
100
|
**Why:** The export finds a block inside an item from its indent (#1302). A table's indent isn't
|
|
99
101
|
reported anywhere in the Docs API: the JSON of a table inside an item matches one at the margin
|
|
100
102
|
(checked live). A quote's is, but a quote inside an item sits exactly where a deeper quote at the
|
|
101
103
|
margin would (a quote in a top-level item is at 72pt, like a margin `> >`), so the indent can't
|
|
102
|
-
tell them apart.
|
|
104
|
+
tell them apart. A block inside a quoted list's item is written the same way, at the item's text
|
|
105
|
+
indent plus the quote's and with the quote's border, which is exactly where a deeper quote at the
|
|
106
|
+
margin goes. Such a block counts as inside an item only when the list goes on after it, which
|
|
103
107
|
it never does after a list's last item. Reading an ambiguous quote as the margin quote keeps every
|
|
104
108
|
quote level; reading it into the item would drop one.
|
|
105
109
|
|
|
@@ -134,6 +138,27 @@ lines join into one paragraph. Decided in #1242.
|
|
|
134
138
|
|
|
135
139
|
---
|
|
136
140
|
|
|
141
|
+
### A horizontal rule is a bordered paragraph, not a Docs horizontal line
|
|
142
|
+
|
|
143
|
+
**What:** A Markdown thematic break (`---`, `***`, `___`) or an HTML `<hr>` written by `create_doc`,
|
|
144
|
+
`write_doc_content`, `create_doc_from_file`, `update_doc_from_file`, or `upload_file` (Markdown
|
|
145
|
+
source) appears as a thin gray line, but in Docs it's an empty paragraph with a bottom border
|
|
146
|
+
(6pt, or 4pt inside a quote), not the element Insert > Horizontal line adds, so editing it in Docs
|
|
147
|
+
behaves like editing a paragraph. Inside a quote the line sits closer to the text below it than
|
|
148
|
+
to the text above: space below it would break the quote bar. `get_doc_as_markdown` reads both
|
|
149
|
+
kinds of line back as `---` (`***` when it's the document's first line, where `---` would open
|
|
150
|
+
front matter). A native line that shares a list item's paragraph but comes before its text
|
|
151
|
+
exports at the margin, ending the list there. An `<hr>` inside a table cell is dropped: a cell
|
|
152
|
+
holds no paragraph styling the converter writes (#1327).
|
|
153
|
+
|
|
154
|
+
**Why:** The Docs API has no request that inserts a horizontal line (#1096).
|
|
155
|
+
|
|
156
|
+
**Workaround:** For a native line, use Insert > Horizontal line in the Docs UI, or convert the
|
|
157
|
+
file with `upload_local_file(convert=True)`, whose Drive import draws native lines (with the other
|
|
158
|
+
differences listed under Google Drive below).
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
137
162
|
### `create_bookmark` doesn't create a bookmark Docs lists
|
|
138
163
|
|
|
139
164
|
**What:** `create_bookmark` creates a one-character named range. It doesn't appear under
|
|
@@ -2466,7 +2466,7 @@ Known gap: 2/3-space indented ordered children flatten (documented bug-3 behavio
|
|
|
2466
2466
|
**Checks**
|
|
2467
2467
|
- Same nesting expectations as TC-DOC104 — 4-space indentation is the one width where `_md_to_html` produces genuinely nested `<ul>`/`<ol>` HTML, so this TC should behave identically to TC-DOC104 once bugs 1+2 are fixed
|
|
2468
2468
|
- Case 2's bare `-` is an empty bullet at level 0, with "Bare-nested child C1"/"C2" one level under it; no literal `-` text (#1075)
|
|
2469
|
-
- Case 4: an empty bullet between "Bullet before" and "Bullet after" (three bullets, one list), and an empty numbered item between "Number one" and "Number three", which show 1., 2. (empty) and 3.; no literal `-` or `2.` text (#1075). Then "Rule item before", an empty bullet with the rule
|
|
2469
|
+
- Case 4: an empty bullet between "Bullet before" and "Bullet after" (three bullets, one list), and an empty numbered item between "Number one" and "Number three", which show 1., 2. (empty) and 3.; no literal `-` or `2.` text (#1075). Then "Rule item before", an empty bullet with the rule under it, unbulleted and drawn as a thin gray line at the item's text indent (the item's only content is the rule; #1096), and "Rule item after", three bullets in one list (PR #1322 QA round 1)
|
|
2470
2470
|
- Confirms whether a "use 4 spaces" workaround note in tool docstrings would be sufficient today (it would not — bugs 1+2 still apply at this indentation)
|
|
2471
2471
|
|
|
2472
2472
|
**Cleanup:** write fixture content back
|
|
@@ -2495,6 +2495,8 @@ Run 2 (ash).
|
|
|
2495
2495
|
|
|
2496
2496
|
**Result (2026-10-31, PR #1322 @ fca630bb, round 2) ✅ PASS — rule-only item added.** `create_doc_from_file` on the updated fixture into a scratch doc (trashed after), `mcp-gee-sweet-sky`, graded from `get_doc_structure` and Playwright on screen. Cases 1–4 as in round 1 (Case 2: empty bullet at level 0, C1/C2 at level 1; Case 4: empty bullet and empty "2." in place). New list: "Rule item before", an empty bullet, the rule's unbulleted empty paragraph, then "Rule item after", the three bullets sharing one `listId`. On screen the rule is a blank gap with no line; a control doc's top-level `---` renders the same way, so that's #1096, not this PR. Check wording updated to match.
|
|
2497
2497
|
|
|
2498
|
+
**Result (2026-11-01, PR #1326 @ 28c464a4, round 2) ✅ PASS — Case 4 only (#1096).** Case 4 of the fixture via `create_doc` into a scratch doc (trashed after), `mcp-gee-sweet-sky`, Playwright on screen. Empty bullet between "Bullet before" and "Bullet after"; 1., 2. (empty), 3.; then "Rule item before", an empty bullet, the rule drawn as a thin gray line at the item's text indent, and "Rule item after", one list. Cases 1–3 not re-run: no code they reach changed in this PR's emitter beyond the rule paragraph.
|
|
2499
|
+
|
|
2498
2500
|
---
|
|
2499
2501
|
|
|
2500
2502
|
**Tooling gap note (not a fix, just a QA-visibility limitation):** `get_doc_structure` doesn't currently expose paragraph indentation or the Docs API's bullet `nestingLevel` field, so confirming bug 2's live-rendering half (or a future fix for it) requires a Playwright visual check every time rather than a structural assertion. Worth a follow-up ticket if this bug is fixed — the same way `font_family` is already a known `get_doc_structure` gap (TC-DOC42/47).
|
|
@@ -6664,12 +6666,12 @@ asyncio.run(main())
|
|
|
6664
6666
|
- LB-06: ` > Quoted text inside the item.` (2 spaces, one `>`), between the two bullets. No `> >` anywhere in the output except where the source has a nested quote
|
|
6665
6667
|
- LB-07: every table line indented 3 spaces, between `1. Compare the options:` and `2. Pick one.`
|
|
6666
6668
|
- LB-08: `- # Heading inside an item`, then `- Plain item after it`
|
|
6667
|
-
- LB-10: ` After the rule` (3 spaces) before `2. Next item`
|
|
6669
|
+
- LB-10: ` ---` (3 spaces), a blank line, then ` After the rule` (3 spaces) before `2. Next item` (the rule exported since #1096)
|
|
6668
6670
|
- LB-11: ` > First quote.` and ` > Second quote.`, separated by at least two blank lines, before `2. Next item`
|
|
6669
6671
|
- LB-12: ` Inner second paragraph` (4 spaces), then `- Outer two`
|
|
6670
6672
|
- LB-13: `> 1. Quoted one`, `>`, `> More of quoted one`, `>`, `> 2. Quoted two`
|
|
6671
6673
|
- LB-14: the table at the margin (no leading spaces), then `1. Step two`: the write side makes two Docs lists here, so the table is outside the list. The second list starts at 1, not 2, because the write side ignores `start` (#1118)
|
|
6672
|
-
- Step 3's export equals step 2's
|
|
6674
|
+
- Step 3's export equals step 2's. (Until #1096, LB-10's rule exported as an empty paragraph, which the re-import dropped, so step 3 had two fewer blank lines there.)
|
|
6673
6675
|
- Step 4, W-1: every line from `> - a` through `> - c` starts with `>`; the table lines are `> | …` (the quote, then 2 spaces)
|
|
6674
6676
|
- W-2: the table lines indented 3 spaces between `1. a` and `- b`, then `2. c`. (`- b` itself is indented 2 spaces, which is #1128)
|
|
6675
6677
|
- W-3: the table lines indented 2 spaces (in a), not 4 (in b), before `- c`
|
|
@@ -6684,6 +6686,9 @@ asyncio.run(main())
|
|
|
6684
6686
|
**Result (2026-10-31) ✅ PASS — PR #1311 round 2 (Sky)**
|
|
6685
6687
|
`mcp-gee-sweet-sky` at b5e90da6, reconnected after the reset, on scratch docs in `{FOLDER_ID}`. Steps 1–3 as in round 1: every LB check holds, and step 3's export equals step 2's except LB-10's two fewer blank lines (#1096). Step 4: W-1 keeps `>` on every line from `> - a` to `> - c`, with the table at `> | …`; W-2 has the table at 3 spaces between `1. a` and ` - b` (#1128), then `2. c`; W-3 has the table at 2 spaces, then `- c`; W-4 exports `> > b` at the margin, then `After.`. Extra probes, all exported as written: a quote then a paragraph in a last item (` > q`, ` tail`), a quote under a nested item before a parent item (` > q`), a quote inside a quoted list's item (`> > inner`), a table then a quote in one item before the next item. Scratch docs trashed.
|
|
6686
6688
|
|
|
6689
|
+
**Result (2026-11-01) ✅ PASS — PR #1326 round 2 (Sky)**
|
|
6690
|
+
`mcp-gee-sweet-sky` at 28c464a4, reconnected, on scratch docs in `{FOLDER_ID}`. Run because the PR rewrote the export's block separator (`_OpenItems.separator`). Step 2: every LB check holds, LB-10 now ` ---`, a blank line, ` After the rule`, then `2. Next item`. Step 3's export is byte-identical to step 2's (diffed). Step 4: W-1 keeps `>` on every line, with the table at `> | …`; W-2 has the table at 3 spaces, ` - b` (#1128), then `2. c`; W-3 has the table at 2 spaces, then `- c`; W-4 is `> > b` at the margin, then `After.`. Scratch docs trashed.
|
|
6691
|
+
|
|
6687
6692
|
### TC-DOC245: A file's relative image path resolves against the file, and a replace uploads its local images to folder_id, the server default, or the doc's own folder (#1121, #1139) ⚠️ requires-oauth ⚠️ destructive
|
|
6688
6693
|
**Purpose:** `create_doc_from_file`/`update_doc_from_file` resolved a relative image path against the server's working directory, so a README's images never embedded (#1121). `update_doc_from_file` had no `folder_id`, so on a server with no default folder every local image failed after the doc was cleared, leaving it without its images (#1139). A relative path is now joined to the file's own folder; `write_doc_content`/`update_doc_from_file` take `folder_id`, fall back to the server's default folder and then to the doc's own folder, and refuse before touching the doc when none is known. The doc's-folder fallback only runs on a server with no `DRIVE_FOLDER_ID`, so parts B and C use a scratch script against the checkout under test with `folder_id=None`, the way TC-DOC243 part B does.
|
|
6689
6694
|
|
|
@@ -6746,3 +6751,36 @@ Fresh interpreter against eb6ce267, `folder_id=None`, with an existing local ima
|
|
|
6746
6751
|
|
|
6747
6752
|
**Result (2026-10-31) ✅ PASS (step 6 only) — PR #1310 round 4 (Kit)**
|
|
6748
6753
|
Fresh interpreter at 78f5536a (the `develop` merge that renumbered this case from TC-DOC244). Step 6 as first written stubbed `_doc_parents` to return `None`, but it returns `(parents, error)` since round 2, so the stub would raise on unpacking; corrected to `result=(None, None)`. With it, step 6 printed the documented refusal ending "Pass folder_id. The document was left unchanged." on a fresh scratch doc, which was then trashed. The other steps' code paths are unchanged since round 3.
|
|
6754
|
+
|
|
6755
|
+
### TC-DOC246: A thematic break is drawn as a line, survives a replace without leaking, and exports as a rule, native lines included (#1096) ⚠️ requires-oauth
|
|
6756
|
+
**Purpose:** A Markdown `---`/`***`/`___` or an HTML `<hr>` rendered as an empty paragraph, a blank gap with no line (#1096). The Docs API can't insert a horizontal line, so a rule is now an empty 6pt paragraph with a gray bottom border (plus a between-border, so two rules in a row draw two lines). `<p><hr></p>` is the rule alone, and text after `<p><hr>` is no longer lost. `get_doc_as_markdown` reads both that stand-in and a native horizontal line (Drive's Markdown import, Insert > Horizontal line) back as `---`, or `***` when the rule is the doc's first line, where `---` would open front matter. TC-MD003 (C8), TC-MD114 and the TC-MD2xx rule cases grade the rendering across every entry point; this case covers the export, the HTML spellings and the replace path.
|
|
6757
|
+
|
|
6758
|
+
**Playwright: required**
|
|
6759
|
+
|
|
6760
|
+
**Setup:** `F` = `{REPO}/docs/qa/fixtures/md/tc-doc246/rules.md`, `H` = `{REPO}/docs/qa/fixtures/md/tc-doc246/native-lines.html`.
|
|
6761
|
+
|
|
6762
|
+
**Steps**
|
|
6763
|
+
1. `create_doc(title="TC-DOC246 md", folder_id={FOLDER_ID}, content_format="markdown", content="***\n\nPara before\n\n---\n\nPara between\n\n***\n___\n\nTwo rules above\n\n---\n\n## Heading after a rule\n\n> Quoted one\n>\n> ---\n>\n> Quoted two\n\n1. Item one\n\n ---\n\n2. Item two\n\n- - -\n\n| a | b |\n|---|---|\n| 1 | 2 |\n\nTail\n\n---\n")`; call its `docId` `{MD}`
|
|
6764
|
+
2. `get_doc_as_markdown(doc_id={MD})`
|
|
6765
|
+
3. `create_doc(title="TC-DOC246 html", folder_id={FOLDER_ID}, content="<p>A</p><hr><p>B</p><p><hr></p><p>C</p><p><hr>D</p>")`, then `get_doc_as_markdown` on its `docId`
|
|
6766
|
+
4. `write_doc_content(doc_id={MD}, content_format="markdown", content="Plain one\n\nPlain two\n")`, then `get_doc_as_markdown(doc_id={MD})`
|
|
6767
|
+
5. `upload_local_file(local_path=F, parent_folder_id={FOLDER_ID}, name="TC-DOC246 native.md", convert=True)`, then `get_doc_as_markdown` on its `fileId` (the `.md` in `name` is required: `convert` reads the extension from `name`, and without it the call returns "Conversion not supported for extension ''")
|
|
6768
|
+
6. `upload_local_file(local_path=H, parent_folder_id={FOLDER_ID}, name="TC-DOC246 native.html", convert=True)`, then `get_doc_as_markdown` on its `fileId`
|
|
6769
|
+
7. `create_doc(title="TC-DOC246 lists", folder_id={FOLDER_ID}, content_format="markdown", content="- a\n-\n ---\n- b\n\n> - qa\n>\n> qb\n>\n> - qc\n>\n> after\n")`, then `get_doc_as_markdown` on its `docId`
|
|
6770
|
+
|
|
6771
|
+
**Checks**
|
|
6772
|
+
- Step 1 returns `docId` with no `error`. On screen, every rule is a thin gray line across the text column with space above and below it: the first line of the doc; between "Para before" and "Para between"; two separate lines (not one, not touching) above "Two rules above"; above the heading; between the quoted paragraphs, starting at the quote bar, with the bar running unbroken from "Quoted one" through the rule to "Quoted two" (one quote, not two; the line sits closer to "Quoted two" than to "Quoted one"); in the list, indented to "Item one"'s text, with the next item numbered **2.**; above the table; and after "Tail". No blank gap stands in for any rule
|
|
6773
|
+
- Step 2 is `***`, then each rule as `---` in the same places: one quote, `> Quoted one`, `>`, `> ---`, `>`, `> Quoted two` (a `>` on the blank lines, so it isn't three quotes), ` ---` (3 spaces) between `1. Item one` and `2. Item two`, and two `---` blocks in a row above "Two rules above"
|
|
6774
|
+
- Step 3, on screen: A, a line, B, a line, C, a line, D. The export is `A`, `---`, `B`, `---`, `C`, `---`, `D`, a blank line between each: `<p><hr></p>` gives one rule and no extra empty paragraph, and "D" is kept
|
|
6775
|
+
- Step 4's export is `Plain one` and `Plain two` with no `---`, and on screen neither paragraph has a line under it: the replaced rules' borders didn't carry over
|
|
6776
|
+
- Step 5's Doc draws Drive's own horizontal lines; its export has a `---` between "Para before" and "Para after" and another between "Quoted before" and "Quoted after". Extra blank lines and the quote's missing `>` are Drive's import, not this case (`docs/known-limitations.md`)
|
|
6777
|
+
- Step 6: Drive's import puts each line in the same paragraph as the text next to it. The export keeps every line and every piece of text, in order: `text before`, `---`, `text after`; the "item one" item, then `---` indented under it (inside the item), then the "item two" item, one list; `## heading`, then `---`; `end`. How the list itself exports (Drive imports it indented, which reads as a quoted numbered list) is Drive's import, not this case
|
|
6778
|
+
- Step 7, on screen: three bullets in one list with the line under the empty middle one, at the item's text indent. The quoted list's bars are offset from the quote's own, as `docs/known-limitations.md` describes. The export has `- a`, `- `, ` ---` on the line right after the empty item's marker (no blank line between, which would end the item), then a blank line and `- b`; then a blank line, and `> - qa`, `>`, `> qb`, `>`, `> - qc`, `>`, `> after`, a `>` on every blank line (one quote)
|
|
6779
|
+
|
|
6780
|
+
**Cleanup:** `delete_file` the five docs
|
|
6781
|
+
|
|
6782
|
+
**Result (2026-11-01) ⚠️ PARTIAL — PR #1326 round 1 (Sky)**
|
|
6783
|
+
Live on `mcp-gee-sweet-sky` at 42e755df (OAuth, Shared Drive folder). Steps 2–5 pass. Step 2's export matches every listed line, including `***` first and `> ---` with `>` on the blank lines. Step 3 draws A, line, B, line, C, line, D and exports the same, with "D" kept. Step 4 exports `Plain one`/`Plain two` with no lines left on screen. Step 5's Drive import draws native lines and exports both `---`. Step 5 as first written passed `name="TC-DOC246 native"`, and `convert` reads the extension from `name`, so the call errored ("Conversion not supported for extension ''"); the step now uses `TC-DOC246 native.md`. Step 1: every rule draws (first line, between paragraphs, two separate lines in a row, above the heading, in the list at "Item one"'s text indent with **2.** after it, above the table, after "Tail"). One defect: the quoted rule's line starts at the bar, but the bar breaks below it. "Quoted one" plus the rule carry one bar, then the rule's 6pt space below opens a gap, and "Quoted two" gets a bar of its own, so on screen it reads as two quotes while the export says one. Sent back. Cleanup: the three docs plus a probe doc trashed.
|
|
6784
|
+
|
|
6785
|
+
**Result (2026-11-01) ✅ PASS — PR #1326 round 2 (Sky)**
|
|
6786
|
+
Live on `mcp-gee-sweet-sky` at 28c464a4, after `/mcp reconnect` (OAuth, Shared Drive folder), all seven steps. Step 1: every rule draws as listed. The quote bar now runs unbroken from "Quoted one" through the rule to "Quoted two", with the line closer to "Quoted two". Step 2's export is unchanged from round 1 and matches every check. Steps 3 and 4 pass as in round 1, re-run on this head. Step 5 exports both `---`. Step 6 exports `text before`, `---`, `text after`; then `> 1. item one`, `>`, `> ---`, `>`, `> 2. item two` (the line inside "item one", one list; Drive imports the list indented, which reads as a quote); then `## heading`, `---`, `end`. Step 7 draws three bullets in one list with the line under the empty middle one at the item's text indent. The quoted list is a separate Docs list (its own `listId`), with offset bars. Its export is `- a`, `- `, ` ---` (no blank line after the empty marker), a blank line, `- b`, a blank line, then `> - qa`, `>`, `> qb`, `>`, `> - qc`, `>`, `> after`. Cleanup: the five docs trashed.
|
|
@@ -399,8 +399,8 @@ Feature set and fill set by the maintainer 2026-10-18, under the release-scoping
|
|
|
399
399
|
- [x] A numbered list restarts its numbering after a nested bullet list. #1241, the reverse case in the same run grouping, was folded in (maintainer, 2026-10-28) but PR #1286 didn't fix it, so #1241 stays open below — lane A ([#1072](https://github.com/khuisman/mcp-gee-sweet/issues/1072)) (PR #1286)
|
|
400
400
|
- [x] Two adjacent Markdown lists render as one Docs list. `wrong/narrow`, observed in TC-MD105/108. Folded into #1072 (maintainer, 2026-10-28), but PR #1286 didn't fix it: it needs a list boundary in the AST — lane A ([#1241](https://github.com/khuisman/mcp-gee-sweet/issues/1241)) (PR #1295)
|
|
401
401
|
- [x] Images inside table cells are dropped silently ([#1073](https://github.com/khuisman/mcp-gee-sweet/issues/1073)) (PR #1215)
|
|
402
|
-
- [
|
|
403
|
-
- [
|
|
402
|
+
- [x] HTML input: whitespace between list tags becomes stray bullet paragraphs — lane A ([#1074](https://github.com/khuisman/mcp-gee-sweet/issues/1074)) (PR #1315)
|
|
403
|
+
- [x] A bare '-' (empty list item) isn't recognized in Markdown — lane A ([#1075](https://github.com/khuisman/mcp-gee-sweet/issues/1075)) (PR #1322)
|
|
404
404
|
- [ ] **Bundle:** Markdown/HTML inline styles — one PR closing [#1077](https://github.com/khuisman/mcp-gee-sweet/issues/1077) (strikethrough, `<del>`) and [#1119](https://github.com/khuisman/mcp-gee-sweet/issues/1119) (`<sub>`, `<sup>`, `<mark>`); after PR #1228, check how much of #1077 the GFM strikethrough rule already fixed. Bundled 2026-10-25 (maintainer) — lane B ([#1231](https://github.com/khuisman/mcp-gee-sweet/issues/1231))
|
|
405
405
|
- [x] get_doc_as_markdown indents a numbered sub-list under a numbered item by only 2 spaces, so it re-imports flat. Found in the 0.9.3 release-QA pass ([#1128](https://github.com/khuisman/mcp-gee-sweet/issues/1128)) (PR #1234)
|
|
406
406
|
- [x] get_doc_as_markdown renumbers a numbered list from 1 after a table (or any block) interrupts it, so it re-imports as two lists. `wrong/narrow`, observed in PR #1201 QA ([#1208](https://github.com/khuisman/mcp-gee-sweet/issues/1208)) (PR #1234)
|
|
@@ -413,7 +413,7 @@ Feature set and fill set by the maintainer 2026-10-18, under the release-scoping
|
|
|
413
413
|
- [ ] **Bundle:** Markdown/HTML line breaks — one PR closing [#1093](https://github.com/khuisman/mcp-gee-sweet/issues/1093) (soft break), [#1117](https://github.com/khuisman/mcp-gee-sweet/issues/1117) (hard break) and [#1091](https://github.com/khuisman/mcp-gee-sweet/issues/1091) (`<br>` in prose), html_parser cluster; after PR #1228. Bundled 2026-10-25 (maintainer) ([#1230](https://github.com/khuisman/mcp-gee-sweet/issues/1230))
|
|
414
414
|
- [x] A fenced code block or second paragraph inside a list item breaks the list — html_parser cluster; fence half may fold into #1116 ([#1094](https://github.com/khuisman/mcp-gee-sweet/issues/1094)) (PR #1301)
|
|
415
415
|
- [x] Adjacent blocks run together: separate quotes merge, consecutive code blocks merge, quotes have no spacing — lane B, after #1056 ([#1098](https://github.com/khuisman/mcp-gee-sweet/issues/1098)) (PR #1199)
|
|
416
|
-
- [ ] A thematic break (`---`) renders as an empty paragraph ([#1096](https://github.com/khuisman/mcp-gee-sweet/issues/1096))
|
|
416
|
+
- [ ] A thematic break (`---`) renders as an empty paragraph — lane A ([#1096](https://github.com/khuisman/mcp-gee-sweet/issues/1096))
|
|
417
417
|
- [ ] GFM table column alignment is ignored ([#1095](https://github.com/khuisman/mcp-gee-sweet/issues/1095))
|
|
418
418
|
- [x] A table takes on the style of the paragraph that follows it ([#1097](https://github.com/khuisman/mcp-gee-sweet/issues/1097)) (PR #1201)
|
|
419
419
|
- [x] Drive's native Markdown import (EP-5 to EP-7) renders differently from our converter — ornithomimus scope: document EP-5 to EP-7 as Drive's engine; routing is #1113 ([#1101](https://github.com/khuisman/mcp-gee-sweet/issues/1101)) (PR #1270)
|
|
@@ -573,7 +573,7 @@ Fill candidates moved from v0.9.3 and the decision-needed backlog on 2026-10-18.
|
|
|
573
573
|
|
|
574
574
|
- [ ] `style_doc_range`: a `font_family`-only change turns off bold inherited from a named style (Docs writes `bold: false`). `wrong/narrow`, observed in #1153's live check; the second follow-up in #1273's family, after #1289 ([#1300](https://github.com/khuisman/mcp-gee-sweet/issues/1300))
|
|
575
575
|
- [ ] `upload_local_file(convert=True)`: a `name=` without an extension errors instead of falling back to `local_path`'s extension. `loud/narrow`; the drop-`.md`-from-the-Doc-name half split out to #1309 ([#1307](https://github.com/khuisman/mcp-gee-sweet/issues/1307))
|
|
576
|
-
- [ ] `get_doc_as_markdown`
|
|
576
|
+
- [ ] **Bundle:** `get_doc_as_markdown` list-item export — one PR closing [#1314](https://github.com/khuisman/mcp-gee-sweet/issues/1314) (item under a numbered item indented 2 spaces, short of the parent's content column; found in #1144's live check), [#1323](https://github.com/khuisman/mcp-gee-sweet/issues/1323) (empty first sub-item reads back as a setext underline) and [#1324](https://github.com/khuisman/mcp-gee-sweet/issues/1324) (empty item holding only a block exports with a blank line, splitting the list); the last two from PR #1322's review. All `wrong/narrow`, observed. Bundled 2026-10-31 (maintainer) ([#1325](https://github.com/khuisman/mcp-gee-sweet/issues/1325))
|
|
577
577
|
|
|
578
578
|
### velociraptor — Parity 1 _(queued, label [`planned: velociraptor`](https://github.com/khuisman/mcp-gee-sweet/issues?q=is%3Aissue+label%3A%22planned%3A+velociraptor%22); gets a number when it goes in flight)_
|
|
579
579
|
|
{mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/ast.py
RENAMED
|
@@ -163,6 +163,11 @@ class Paragraph:
|
|
|
163
163
|
blockquote_path: tuple[int, ...] = field(default=(), compare=False) # #1098, see above Heading
|
|
164
164
|
list_depth: int | None = None # #1094, see above Heading
|
|
165
165
|
list_path: tuple[int, ...] = field(default=(), compare=False) # #1094, see above Heading
|
|
166
|
+
# A thematic break (`---`, `<hr>`, #1096): an empty paragraph (runs=[]) the emitter
|
|
167
|
+
# draws as a horizontal line, since the Docs API can't insert a horizontalRule
|
|
168
|
+
# element. doc_to_ast.py reads both that stand-in and a native horizontal line back
|
|
169
|
+
# as one, and ast_to_markdown.py writes it as a rule.
|
|
170
|
+
rule: bool = False
|
|
166
171
|
|
|
167
172
|
|
|
168
173
|
@dataclass
|
|
@@ -65,6 +65,13 @@ _LEADING_ORDERED_MARKER = re.compile(r"^(\d{1,9})([.)])(?=[ \t]|$)")
|
|
|
65
65
|
# rules can't occur: _escape has already escaped both (#993).
|
|
66
66
|
_LEADING_RULE_LINE = re.compile(r"^(?:=+[ \t]*|-[- \t]*)$")
|
|
67
67
|
|
|
68
|
+
# A thematic break (#1096). Every block is separated by a blank line, so "---"
|
|
69
|
+
# never sits under a paragraph line, where it would be a setext underline. As the
|
|
70
|
+
# document's first line it would open YAML front matter, which the Markdown import
|
|
71
|
+
# drops (D3, #1173) when what follows reads as a mapping, so there it's "***".
|
|
72
|
+
_RULE = "---"
|
|
73
|
+
_FIRST_LINE_RULE = "***"
|
|
74
|
+
|
|
68
75
|
_NESTED_TABLE_PLACEHOLDER = (
|
|
69
76
|
"*(nested table omitted — Markdown tables can't contain a table; "
|
|
70
77
|
"use get_doc_structure for full fidelity)*"
|
|
@@ -78,18 +85,10 @@ def ast_to_markdown(nodes: list[DocNode], comments: list[dict] | None = None) ->
|
|
|
78
85
|
open_items = _OpenItems()
|
|
79
86
|
for node in nodes:
|
|
80
87
|
rendered = open_items.render(node, prev_node)
|
|
88
|
+
if prev_node is None and rendered == _RULE:
|
|
89
|
+
rendered = _FIRST_LINE_RULE
|
|
81
90
|
if prev_node is not None:
|
|
82
|
-
|
|
83
|
-
# they render as one "tight" list (CommonMark still parses a blank-
|
|
84
|
-
# line-separated run as the same list, just needlessly "loose" —
|
|
85
|
-
# no correctness difference, only readability of the output).
|
|
86
|
-
same_list_run = isinstance(node, BulletItem) and isinstance(prev_node, BulletItem)
|
|
87
|
-
if same_list_run:
|
|
88
|
-
parts.append("\n")
|
|
89
|
-
elif open_items.shared_quote:
|
|
90
|
-
parts.append(f"\n{'>' * open_items.shared_quote}\n")
|
|
91
|
-
else:
|
|
92
|
-
parts.append("\n\n")
|
|
91
|
+
parts.append(open_items.separator)
|
|
93
92
|
parts.append(rendered)
|
|
94
93
|
prev_node = node
|
|
95
94
|
|
|
@@ -106,7 +105,7 @@ def ast_to_markdown(nodes: list[DocNode], comments: list[dict] | None = None) ->
|
|
|
106
105
|
|
|
107
106
|
class _OpenItems:
|
|
108
107
|
"""Renders each node, writing a block inside a list item (its `list_depth`,
|
|
109
|
-
#1302) under that item instead of at the margin.
|
|
108
|
+
#1302) under that item instead of at the margin, and picks the separator before it.
|
|
110
109
|
|
|
111
110
|
CommonMark keeps a block in an item when it's indented to the item's content
|
|
112
111
|
column, the width of the item's own indent and marker. So every list item
|
|
@@ -117,32 +116,57 @@ class _OpenItems:
|
|
|
117
116
|
A quoted item's quote markers come before its indent ("> - a"). A block inside
|
|
118
117
|
it is inside that quote too, whatever depth it reads with (a table reads none),
|
|
119
118
|
so the item's quote levels go outside the block's indent and only its own extra
|
|
120
|
-
levels inside ("> - a" then "> > b").
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
119
|
+
levels inside ("> - a" then "> > b").
|
|
120
|
+
|
|
121
|
+
After `render`, `separator` is what goes between the node and the one before it:
|
|
122
|
+
a newline between two items at the same quote depth, so a list stays tight (items at
|
|
123
|
+
different depths are different lists, "- b" then "> - qa"); a newline between an
|
|
124
|
+
empty item and its first block, since CommonMark ends an item that starts with a
|
|
125
|
+
blank line there (PR #1326 QA round 1); otherwise a blank line carrying the quote
|
|
126
|
+
levels both nodes write at the margin, so it doesn't end a quote they share. Two
|
|
127
|
+
adjacent quoted blocks in Docs are one quote: Docs draws their left borders as one
|
|
128
|
+
bar, and emitter.py puts an unquoted separator paragraph between two separate
|
|
129
|
+
quotes (#1098), which reads back as a node of its own with no quote."""
|
|
126
130
|
|
|
127
131
|
def __init__(self) -> None:
|
|
128
132
|
# Per open item depth: (content column, blockquote_depth).
|
|
129
133
|
self._items: list[tuple[int, int]] = []
|
|
130
|
-
|
|
134
|
+
# Quote levels the last node wrote at the margin, before any indent.
|
|
135
|
+
self._margin_quote = 0
|
|
136
|
+
self.separator = "\n\n"
|
|
131
137
|
|
|
132
138
|
def render(self, node: DocNode, prev_node: DocNode | None) -> str:
|
|
133
|
-
|
|
139
|
+
prev_quote = self._margin_quote
|
|
140
|
+
rendered = self._render(node, prev_node)
|
|
141
|
+
same_list = (
|
|
142
|
+
isinstance(node, BulletItem)
|
|
143
|
+
and isinstance(prev_node, BulletItem)
|
|
144
|
+
and node.blockquote_depth == prev_node.blockquote_depth
|
|
145
|
+
)
|
|
146
|
+
if same_list or _starts_empty_item(prev_node, node):
|
|
147
|
+
self.separator = "\n"
|
|
148
|
+
else:
|
|
149
|
+
shared = min(prev_quote, self._margin_quote)
|
|
150
|
+
self.separator = f"\n{'>' * shared}\n" if shared else "\n\n"
|
|
151
|
+
return rendered
|
|
152
|
+
|
|
153
|
+
def _render(self, node: DocNode, prev_node: DocNode | None) -> str:
|
|
134
154
|
if isinstance(node, BulletItem):
|
|
135
155
|
opens_sublist = _opens_sublist(node, prev_node)
|
|
136
|
-
if self._items:
|
|
137
|
-
self.shared_quote = min(node.blockquote_depth, self._items[0][1])
|
|
138
156
|
del self._items[node.depth :]
|
|
139
157
|
self._items.extend([(0, 0)] * (node.depth - len(self._items)))
|
|
140
158
|
self._items.append((len(_item_prefix(node, opens_sublist)), node.blockquote_depth))
|
|
159
|
+
self._margin_quote = node.blockquote_depth
|
|
141
160
|
return _render_block(node, opens_sublist=opens_sublist)
|
|
142
161
|
list_depth = node.list_depth
|
|
143
162
|
if list_depth is None or list_depth >= len(self._items):
|
|
144
163
|
self._items.clear()
|
|
145
|
-
|
|
164
|
+
if isinstance(node, Table):
|
|
165
|
+
# A table renders unquoted (_render_table writes no quote markers).
|
|
166
|
+
self._margin_quote = 0
|
|
167
|
+
return _render_table(node)
|
|
168
|
+
self._margin_quote = node.blockquote_depth
|
|
169
|
+
return _render_block(node)
|
|
146
170
|
del self._items[list_depth + 1 :]
|
|
147
171
|
column, item_quote = self._items[list_depth]
|
|
148
172
|
# A block inside a quoted list's item is inside that list's quote too. Its
|
|
@@ -151,7 +175,7 @@ class _OpenItems:
|
|
|
151
175
|
quote = 0 if isinstance(node, Table) else node.blockquote_depth
|
|
152
176
|
quote = max(quote, item_quote)
|
|
153
177
|
outer = item_quote
|
|
154
|
-
self.
|
|
178
|
+
self._margin_quote = outer
|
|
155
179
|
if isinstance(node, Table):
|
|
156
180
|
body = _render_table(node)
|
|
157
181
|
else:
|
|
@@ -160,6 +184,20 @@ class _OpenItems:
|
|
|
160
184
|
return _blockquote(body, outer) if outer else body
|
|
161
185
|
|
|
162
186
|
|
|
187
|
+
def _starts_empty_item(prev_node: DocNode | None, node: DocNode) -> bool:
|
|
188
|
+
"""Whether `node` is the first block inside `prev_node`, a list item with no text.
|
|
189
|
+
CommonMark lets an item start with at most one blank line, its marker's own, so
|
|
190
|
+
"-", a blank line, then " ---" is an empty item ended by a rule at the margin."""
|
|
191
|
+
return (
|
|
192
|
+
isinstance(prev_node, BulletItem)
|
|
193
|
+
and not isinstance(node, BulletItem)
|
|
194
|
+
and not prev_node.runs
|
|
195
|
+
and prev_node.checked is None
|
|
196
|
+
and prev_node.heading_level is None
|
|
197
|
+
and node.list_depth == prev_node.depth
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
163
201
|
def _indent_lines(body: str, column: int) -> str:
|
|
164
202
|
# Blank lines stay empty: CommonMark keeps them in the item either way.
|
|
165
203
|
pad = " " * column
|
|
@@ -255,7 +293,9 @@ def _render_block(
|
|
|
255
293
|
) -> str:
|
|
256
294
|
"""`blockquote_depth` overrides the node's own, for a block whose outer quote
|
|
257
295
|
levels its caller writes (see `_OpenItems`)."""
|
|
258
|
-
if isinstance(node, Paragraph) and
|
|
296
|
+
if isinstance(node, Paragraph) and node.rule:
|
|
297
|
+
body = _RULE
|
|
298
|
+
elif isinstance(node, Paragraph) and _is_code_block(node):
|
|
259
299
|
body = _render_code_block(node)
|
|
260
300
|
elif isinstance(node, Heading):
|
|
261
301
|
body = f"{'#' * node.level} {_render_inline(node.runs)}".rstrip()
|
{mcp_gee_sweet-0.9.6.dev134 → mcp_gee_sweet-0.9.6.dev136}/src/mcp_gee_sweet/tools/docs/doc_to_ast.py
RENAMED
|
@@ -25,7 +25,7 @@ from .ast import (
|
|
|
25
25
|
Run,
|
|
26
26
|
Table,
|
|
27
27
|
)
|
|
28
|
-
from .emitter import _BLOCKQUOTE_INDENT_PT_PER_LEVEL, list_level_indents
|
|
28
|
+
from .emitter import _BLOCKQUOTE_INDENT_PT_PER_LEVEL, is_rule_style, list_level_indents
|
|
29
29
|
|
|
30
30
|
_HEADING_LEVELS = {f"HEADING_{i}": i for i in range(1, 7)}
|
|
31
31
|
|
|
@@ -45,6 +45,10 @@ def document_to_ast(document: dict) -> list[DocNode]:
|
|
|
45
45
|
for elem in document.get("body", {}).get("content", []):
|
|
46
46
|
if "paragraph" in elem:
|
|
47
47
|
para = elem["paragraph"]
|
|
48
|
+
pieces = _split_at_horizontal_rules(para)
|
|
49
|
+
if len(pieces) > 1:
|
|
50
|
+
nodes.extend(_paragraph_with_rules(para, pieces, document, list_counter, items))
|
|
51
|
+
continue
|
|
48
52
|
node = _paragraph_to_node(para, document, list_counter)
|
|
49
53
|
if isinstance(node, BulletItem):
|
|
50
54
|
items.item(para["bullet"])
|
|
@@ -280,7 +284,90 @@ def _paragraph_to_node(
|
|
|
280
284
|
)
|
|
281
285
|
if named_style in ("TITLE", "SUBTITLE"):
|
|
282
286
|
return NamedBlock(style_type=named_style, runs=runs, blockquote_depth=blockquote_depth)
|
|
283
|
-
return Paragraph(
|
|
287
|
+
return Paragraph(
|
|
288
|
+
runs=runs, blockquote_depth=blockquote_depth, rule=_is_rule_paragraph(para, runs)
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _is_rule_paragraph(para: dict, runs: list[Run | Image]) -> bool:
|
|
293
|
+
"""Whether a paragraph with no text is emitter.py's rule stand-in (#1096): an
|
|
294
|
+
empty paragraph with the rule's borders. A native horizontal line is split out
|
|
295
|
+
before this (_split_at_horizontal_rules)."""
|
|
296
|
+
return not runs and is_rule_style(para.get("paragraphStyle", {}))
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _split_at_horizontal_rules(para: dict) -> list[list[dict] | None]:
|
|
300
|
+
"""The paragraph's elements split at each native horizontalRule (#1096), with
|
|
301
|
+
None standing for each rule; a single piece when it has none."""
|
|
302
|
+
pieces: list[list[dict] | None] = []
|
|
303
|
+
current: list[dict] = []
|
|
304
|
+
for pe in para.get("elements", []):
|
|
305
|
+
if "horizontalRule" in pe:
|
|
306
|
+
pieces.extend([current, None])
|
|
307
|
+
current = []
|
|
308
|
+
else:
|
|
309
|
+
current.append(pe)
|
|
310
|
+
pieces.append(current)
|
|
311
|
+
return pieces
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _paragraph_with_rules(
|
|
315
|
+
para: dict,
|
|
316
|
+
pieces: list[list[dict] | None],
|
|
317
|
+
document: dict,
|
|
318
|
+
list_counter: _ListCounter,
|
|
319
|
+
items: _ListItemContext,
|
|
320
|
+
) -> list[DocNode]:
|
|
321
|
+
"""A paragraph holding native horizontal lines (#1096), as a rule node for each
|
|
322
|
+
line and a node for the text between them, in order. Drive's import puts a line in
|
|
323
|
+
the same paragraph as text before or after it, a list item's included (checked
|
|
324
|
+
live: `[hr, "text after"]`, `["item one", hr]`).
|
|
325
|
+
|
|
326
|
+
The text keeps the paragraph's own type: a heading's is a heading, and a list
|
|
327
|
+
item's first text is the item, with a rule or text after it a block inside the
|
|
328
|
+
item (#1302). A list item with only a line is an empty item with the rule in it."""
|
|
329
|
+
bullet = para.get("bullet")
|
|
330
|
+
texts = [
|
|
331
|
+
i
|
|
332
|
+
for i, piece in enumerate(pieces)
|
|
333
|
+
if piece is not None and _paragraph_runs({**para, "elements": piece}, document)
|
|
334
|
+
]
|
|
335
|
+
item: BulletItem | None = None
|
|
336
|
+
item_at = -1
|
|
337
|
+
if bullet:
|
|
338
|
+
item_at = texts[0] if texts else 0
|
|
339
|
+
node = _paragraph_to_node(
|
|
340
|
+
{**para, "elements": pieces[item_at] or []}, document, list_counter
|
|
341
|
+
)
|
|
342
|
+
assert isinstance(node, BulletItem)
|
|
343
|
+
item = node
|
|
344
|
+
out: list[DocNode] = []
|
|
345
|
+
for i, piece in enumerate(pieces):
|
|
346
|
+
if item is not None and i == item_at:
|
|
347
|
+
items.item(para["bullet"])
|
|
348
|
+
out.append(item)
|
|
349
|
+
continue
|
|
350
|
+
node: Heading | Paragraph | BulletItem | NamedBlock
|
|
351
|
+
if piece is None:
|
|
352
|
+
node = Paragraph(runs=[], rule=True)
|
|
353
|
+
elif i not in texts:
|
|
354
|
+
continue
|
|
355
|
+
elif item is not None:
|
|
356
|
+
node = Paragraph(runs=_paragraph_runs({**para, "elements": piece}, document))
|
|
357
|
+
else:
|
|
358
|
+
node = _paragraph_to_node({**para, "elements": piece}, document, list_counter)
|
|
359
|
+
assert not isinstance(node, BulletItem)
|
|
360
|
+
if item is not None and i > item_at:
|
|
361
|
+
# Inside the item: its depth and quote, not read from the paragraph's
|
|
362
|
+
# indents, which are the item's own.
|
|
363
|
+
node.list_depth = item.depth
|
|
364
|
+
node.blockquote_depth = item.blockquote_depth
|
|
365
|
+
else:
|
|
366
|
+
if isinstance(node, Paragraph) and node.rule:
|
|
367
|
+
node.blockquote_depth = _infer_blockquote_depth(para.get("paragraphStyle", {}))
|
|
368
|
+
items.block(node, para)
|
|
369
|
+
out.append(node)
|
|
370
|
+
return out
|
|
284
371
|
|
|
285
372
|
|
|
286
373
|
def _nesting_level(bullet: dict, document: dict) -> dict | None:
|
|
@@ -350,8 +437,10 @@ def _paragraph_runs(para: dict, document: dict) -> list[Run | Image]:
|
|
|
350
437
|
image = _resolve_inline_image(ioe.get("inlineObjectId"), document, link_url)
|
|
351
438
|
if image is not None:
|
|
352
439
|
items.append(image)
|
|
353
|
-
# footnoteReference,
|
|
354
|
-
#
|
|
440
|
+
# footnoteReference, pageBreak, columnBreak, autoText, richLink, person,
|
|
441
|
+
# equation: no Markdown equivalent, silently skipped. A horizontalRule
|
|
442
|
+
# never gets here: document_to_ast splits it out as a rule node first
|
|
443
|
+
# (_split_at_horizontal_rules, #1096).
|
|
355
444
|
|
|
356
445
|
# Every paragraph's content ends with a trailing "\n" (Docs API convention)
|
|
357
446
|
# — strip exactly one from the end of the last Run, dropping it entirely if
|