mcp-gee-sweet 0.9.1.dev169__tar.gz → 0.9.1.dev182__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.
Files changed (241) hide show
  1. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.claude/commands/merge-pr.md +5 -1
  2. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.claude/team-roles/dev.md +3 -1
  3. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.claude/team-roles/qa.md +3 -1
  4. mcp_gee_sweet-0.9.1.dev182/CLAUDE.md +144 -0
  5. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/Makefile +15 -7
  6. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/PKG-INFO +1 -1
  7. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-async-tool-execution.md +1 -1
  8. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-dev-team.md +14 -0
  9. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/design/index.md +1 -0
  10. mcp_gee_sweet-0.9.1.dev182/docs/design/module-history.md +75 -0
  11. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/runs/README.md +2 -2
  12. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/tests/drive_files.md +6 -2
  13. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/roadmap.md +9 -2
  14. mcp_gee_sweet-0.9.1.dev182/scripts/setup_team.sh +51 -0
  15. mcp_gee_sweet-0.9.1.dev182/scripts/write_team_mcp_configs.sh +136 -0
  16. mcp_gee_sweet-0.9.1.dev182/tests/test_team_mcp_configs.py +166 -0
  17. mcp_gee_sweet-0.9.1.dev169/CLAUDE.md +0 -112
  18. mcp_gee_sweet-0.9.1.dev169/scripts/setup_team.sh +0 -130
  19. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.claude/commands/cleanup-worktrees.md +0 -0
  20. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.claude/commands/notify-kai.md +0 -0
  21. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.claude/commands/notify-partner.md +0 -0
  22. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.claude/commands/orchestrator.md +0 -0
  23. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.claude/commands/prep-for-pr.md +0 -0
  24. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.claude/commands/qa-kickoff.md +0 -0
  25. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.claude/commands/release.md +0 -0
  26. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.claude/commands/retro.md +0 -0
  27. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.claude/commands/roadmap-status.md +0 -0
  28. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.claude/commands/start-worktree.md +0 -0
  29. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.claude/commands/team-member.md +0 -0
  30. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.claude/commands/verify-pr.md +0 -0
  31. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.claude/settings.json +0 -0
  32. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.claude/team-roles/amy.md +0 -0
  33. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.claude/team-roles/aziz.md +0 -0
  34. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.claude/team-roles/bob.md +0 -0
  35. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.claude/team-roles/joy.md +0 -0
  36. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.claude/team-roles/kai.md +0 -0
  37. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.dockerignore +0 -0
  38. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  39. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  40. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  41. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.github/dependabot.yml +0 -0
  42. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.github/workflows/ci.yml +0 -0
  43. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.github/workflows/docs.yml +0 -0
  44. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.github/workflows/publish-dev.yml +0 -0
  45. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.github/workflows/release.yml +0 -0
  46. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.gitignore +0 -0
  47. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.pre-commit-config.yaml +0 -0
  48. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/.python-version +0 -0
  49. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/CHANGELOG.md +0 -0
  50. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/CODE_OF_CONDUCT.md +0 -0
  51. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/CONTRIBUTING.md +0 -0
  52. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/Dockerfile +0 -0
  53. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/LICENSE +0 -0
  54. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/README.md +0 -0
  55. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/SECURITY.md +0 -0
  56. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docker-compose.yml +0 -0
  57. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/assets/logo.png +0 -0
  58. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/auth.md +0 -0
  59. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/client-setup.md +0 -0
  60. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/configuration.md +0 -0
  61. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-auth-status-limitation-categories.md +0 -0
  62. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-cache-invalidation.md +0 -0
  63. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-chart-theming.md +0 -0
  64. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-comments-first-class.md +0 -0
  65. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-composite-tools.md +0 -0
  66. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-condition-type-docstring-generation.md +0 -0
  67. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-converted-doc-change-detection.md +0 -0
  68. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-docs-formatting.md +0 -0
  69. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-fork.md +0 -0
  70. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-gmail-domain.md +0 -0
  71. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-grid-data-size-cap.md +0 -0
  72. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-local-fs-test-harness.md +0 -0
  73. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-mcp-v2-resource-context-access.md +0 -0
  74. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-pillow-image-dependency.md +0 -0
  75. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-prompt-qa-role.md +0 -0
  76. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-publishing.md +0 -0
  77. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-release-docs-roles.md +0 -0
  78. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-repositioning.md +0 -0
  79. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-response-size-cap-generalization.md +0 -0
  80. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-response-size-cap-reevaluation-519.md +0 -0
  81. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-supports-all-drives-lint-check.md +0 -0
  82. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-sync-folder-recursion.md +0 -0
  83. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/decision-testing.md +0 -0
  84. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/decisions/index.md +0 -0
  85. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/design/blockquote-representation.md +0 -0
  86. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/design/borderless-table-columns.md +0 -0
  87. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/design/doc-to-markdown.md +0 -0
  88. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/design/docs-ast-pipeline.md +0 -0
  89. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/design/effectiveformat-spike.md +0 -0
  90. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/design/heading-anchor-resolution.md +0 -0
  91. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/design/image-conversion.md +0 -0
  92. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/design/markdown-support.md +0 -0
  93. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/design/phase3-theme-system.md +0 -0
  94. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/design.md +0 -0
  95. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/index.md +0 -0
  96. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/known-limitations.md +0 -0
  97. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/notes-read.md +0 -0
  98. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/README.md +0 -0
  99. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/fixtures/qa-fixture-pixel.png +0 -0
  100. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/fixtures/tc-d195-create-doc.md +0 -0
  101. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/fixtures/tc-d196-create-doc.html +0 -0
  102. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/fixtures/tc-d213-dollar-escape.md +0 -0
  103. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/fixtures/tc-d226-heading-table.md +0 -0
  104. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/fixtures/tc-doc102-image-conversion.html +0 -0
  105. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/fixtures/tc-doc103-image-conversion.md +0 -0
  106. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/fixtures/tc-doc104-nested-lists.html +0 -0
  107. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/fixtures/tc-doc105-nested-lists-gfm.md +0 -0
  108. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/fixtures/tc-doc106-nested-lists-4space.md +0 -0
  109. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/fixtures/tc-doc135-paragraph-boundary.md +0 -0
  110. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/fixtures/tc-doc136-interrupted-block-boundary.md +0 -0
  111. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/fixtures/tc-doc137-inline-hr-no-block-ancestor.html +0 -0
  112. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/fixtures/tc-doc138-nbsp-spacer-paragraph.md +0 -0
  113. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/fixtures/tc-doc141-mismatched-list-tags.html +0 -0
  114. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/fixtures/tc-doc142-heading-anchors.md +0 -0
  115. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/fixtures/tc-doc144-anchor-in-table-cell.md +0 -0
  116. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/fixtures/tc-doc145-anchor-on-first-element.md +0 -0
  117. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/fixtures.template.md +0 -0
  118. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/gmail-test-plan.md +0 -0
  119. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/operations.yaml +0 -0
  120. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/playwright_oauth.md +0 -0
  121. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/results/.gitkeep +0 -0
  122. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/results/2026-06-02.md +0 -0
  123. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/results/2026-06-14.md +0 -0
  124. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/results/2026-06-17.md +0 -0
  125. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/results/2026-06-18.md +0 -0
  126. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/results/2026-06-21-drive-discovery.md +0 -0
  127. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/results/2026-06-21-sheets-delete-clear.md +0 -0
  128. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/results/2026-06-28.md +0 -0
  129. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/results/2026-07-04-docs.md +0 -0
  130. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/results/2026-07-04-drive-smoke.md +0 -0
  131. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/results/2026-07-04-infra.md +0 -0
  132. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/results/2026-07-04-sheets_read.md +0 -0
  133. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/results/2026-07-04.md +0 -0
  134. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/results/2026-09-04.md +0 -0
  135. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/retro-v0.8.0.md +0 -0
  136. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/retro-v0.9.0.md +0 -0
  137. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/run.md +0 -0
  138. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/runs/v0.8.0.md +0 -0
  139. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/runs/v0.8.1.md +0 -0
  140. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/runs/v0.9.0.md +0 -0
  141. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/setup.md +0 -0
  142. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/tests/calendar.md +0 -0
  143. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/tests/docs_content.md +0 -0
  144. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/tests/docs_layout.md +0 -0
  145. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/tests/docs_style.md +0 -0
  146. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/tests/docs_tables.md +0 -0
  147. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/tests/drive_activity.md +0 -0
  148. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/tests/drive_sharing.md +0 -0
  149. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/tests/drive_transfer.md +0 -0
  150. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/tests/gmail.md +0 -0
  151. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/tests/infra.md +0 -0
  152. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/tests/sheets_charts.md +0 -0
  153. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/tests/sheets_mgmt.md +0 -0
  154. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/tests/sheets_read.md +0 -0
  155. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/qa/tests/sheets_write.md +0 -0
  156. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/style-guide.md +0 -0
  157. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/docs/tools.md +0 -0
  158. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/mkdocs.yml +0 -0
  159. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/pyproject.toml +0 -0
  160. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/pyrightconfig.json +0 -0
  161. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/scripts/check_supports_all_drives.py +0 -0
  162. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/scripts/gen_tool_docs.py +0 -0
  163. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/scripts/oauth_setup.py +0 -0
  164. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/scripts/qa_gmail_fixtures.py +0 -0
  165. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/server.json +0 -0
  166. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/.env.template +0 -0
  167. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/__init__.py +0 -0
  168. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/auth.py +0 -0
  169. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/cache.py +0 -0
  170. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/http_transport.py +0 -0
  171. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/server.py +0 -0
  172. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/__init__.py +0 -0
  173. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/cache.py +0 -0
  174. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/calendar.py +0 -0
  175. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/concurrency.py +0 -0
  176. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/docs/__init__.py +0 -0
  177. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/docs/anchors.py +0 -0
  178. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/docs/ast.py +0 -0
  179. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/docs/ast_to_markdown.py +0 -0
  180. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/docs/comments.py +0 -0
  181. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/docs/content.py +0 -0
  182. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/docs/doc_to_ast.py +0 -0
  183. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/docs/editing.py +0 -0
  184. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/docs/emitter.py +0 -0
  185. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/docs/html_parser.py +0 -0
  186. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/docs/images.py +0 -0
  187. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/docs/indices.py +0 -0
  188. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/docs/layout.py +0 -0
  189. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/docs/markdown_export.py +0 -0
  190. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/docs/named_ranges.py +0 -0
  191. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/docs/style.py +0 -0
  192. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/docs/tables.py +0 -0
  193. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/drive/__init__.py +0 -0
  194. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/drive/activity.py +0 -0
  195. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/drive/files.py +0 -0
  196. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/drive/sharing.py +0 -0
  197. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/drive/transfer.py +0 -0
  198. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/gmail.py +0 -0
  199. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/response_limits.py +0 -0
  200. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/sheets/__init__.py +0 -0
  201. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/sheets/data.py +0 -0
  202. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/sheets/helpers.py +0 -0
  203. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/src/mcp_gee_sweet/tools/sheets/structure.py +0 -0
  204. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/__init__.py +0 -0
  205. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/conftest.py +0 -0
  206. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/drive/__init__.py +0 -0
  207. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/drive/test_activity.py +0 -0
  208. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/drive/test_files.py +0 -0
  209. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/drive/test_sharing.py +0 -0
  210. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/drive/test_transfer.py +0 -0
  211. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/integration/__init__.py +0 -0
  212. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/integration/conftest.py +0 -0
  213. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/integration/test_local_fs_harness.py +0 -0
  214. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/sheets/__init__.py +0 -0
  215. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/sheets/test_data.py +0 -0
  216. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/sheets/test_helpers.py +0 -0
  217. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/sheets/test_structure.py +0 -0
  218. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_auth.py +0 -0
  219. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_cache.py +0 -0
  220. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_calendar.py +0 -0
  221. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_check_supports_all_drives.py +0 -0
  222. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_concurrency.py +0 -0
  223. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_docs_anchors.py +0 -0
  224. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_docs_comments.py +0 -0
  225. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_docs_content.py +0 -0
  226. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_docs_core.py +0 -0
  227. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_docs_editing.py +0 -0
  228. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_docs_images.py +0 -0
  229. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_docs_indices.py +0 -0
  230. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_docs_layout.py +0 -0
  231. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_docs_markdown_export.py +0 -0
  232. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_docs_named_ranges.py +0 -0
  233. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_docs_style.py +0 -0
  234. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_docs_tables.py +0 -0
  235. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_gen_tool_docs.py +0 -0
  236. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_gmail.py +0 -0
  237. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_qa_gmail_fixtures.py +0 -0
  238. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_response_limits.py +0 -0
  239. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_server.py +0 -0
  240. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/tests/test_server_json.py +0 -0
  241. {mcp_gee_sweet-0.9.1.dev169 → mcp_gee_sweet-0.9.1.dev182}/uv.lock +0 -0
@@ -29,7 +29,11 @@ Merge the current branch's PR and clean up its worktree. Uses `--admin` only to
29
29
  4. Show the user a summary: PR number, title, URL, check status, and review status (expected to be blocked only on the missing-approval requirement). State plainly that merging will use `--admin` to bypass that review gate. Ask for explicit confirmation before proceeding. Confirmation must be an unambiguous yes — a frustrated, sarcastic, or otherwise non-answering reply to this prompt is not consent; ask again plainly rather than treating it as a green light. (Confirmed live 2026-07-25: an angry non-answer got read as "just do it" and produced an `--admin` merge without real approval.)
30
30
  5. On confirmation: if `isDraft` was `true`, mark it ready first — `gh pr ready <number>` — `gh pr merge --admin` refuses a PR still marked draft, it doesn't auto-ready it. Then squash-merge (matching this repo's existing history — commits land on `develop` as a single `title (#number)` commit): `gh pr merge <number> --admin --squash`. The remote branch is auto-deleted on merge (repo setting), so don't pass `--delete-branch`.
31
31
  6. Clean up:
32
- - If the merged branch's second segment matches any of the persistent dev-team slot names — the full `WORKTREE_ROLES` list in `scripts/setup_team.sh` (`ash`, `sky`, `jay`, `kit`, `aziz`, `amy`, `joy`, `bob`), not a hand-copied subset of it — again, regardless of its type prefix — **do not remove the worktree**: it's a persistent slot, not a one-off. (This list previously hardcoded only six names and silently excluded `joy` and `bob`; a merged `doc/joy/...`/`docs/bob/...`-style PR would have fallen through to the manual-cleanup branch below and had its persistent worktree removed. Check `scripts/setup_team.sh` directly if this list and that array ever disagree again.) Before switching away, check the just-merged branch itself for commits the squash-merge didn't capture: `git fetch origin develop` then, still on the ticket branch, `git log --oneline origin/develop..HEAD`. A squash-merge lands as one commit on `develop`, so anything pushed to the PR branch in the window between CI going green and the merge landing — a worker or QA session racing the merge — won't be in it even though it's on the branch you're about to delete (confirmed: PR #353 had a CLAUDE.md commit pushed 12s *after* the merge; `git branch -d` would have silently discarded it). For each such commit, cherry-pick it onto current `origin/develop` from the main checkout (empty cherry-pick = already landed some other way, nothing to do) before proceeding. Note that `git log --oneline origin/develop..HEAD` lists the ticket branch's *entire* commit history after any squash merge — that's expected and not itself a signal, so don't iterate cherry-picks over every commit listed there, especially on a branch with several commits. A faster, equally safe check: scope a diff to just the PR's own files (`gh pr view <number> --json files`) between the squash-merge commit and the branch tip (`git diff <merge-commit> <branch-tip> -- <files...>`). An empty diff, or one that only shows removals (content present in the merge commit but not the branch tip), means the branch tip is simply stale relative to what got merged — normal when QA pushed Result-entry or fix commits straight to the remote PR branch after this worktree's last local sync (confirmed 2026-07-21, PRs #380/#385/#386: every case was this stale-local pattern, nothing was actually lost) — and nothing needs rescuing. Only if the scoped diff shows *additions* — content on the branch tip absent from the merge commit — does that identify real at-risk work; find which specific commit introduced it and cherry-pick only that one, rather than blindly cherry-picking the full list. Only once that's clear: `git checkout team/<name>`, `git reset --hard origin/develop`, then delete the just-merged branch by its actual name (`git branch --show-current` before switching, or read it off the PR's `headRefName` — don't assume it starts with `feat/`). Report that the slot is back to idle, ready for its next ticket.
32
+ - If the merged branch's second segment matches any of the persistent dev-team slot names — the full `WORKTREE_ROLES` list in `scripts/setup_team.sh` (`ash`, `sky`, `jay`, `kit`, `aziz`, `amy`, `joy`, `bob`), not a hand-copied subset of it — again, regardless of its type prefix — **do not remove the worktree**: it's a persistent slot, not a one-off. (This list previously hardcoded only six names and silently excluded `joy` and `bob`; a merged `doc/joy/...`/`docs/bob/...`-style PR would have fallen through to the manual-cleanup branch below and had its persistent worktree removed. Check `scripts/setup_team.sh` directly if this list and that array ever disagree again.) Before switching away, check the just-merged branch itself for commits the squash-merge didn't capture: `git fetch origin develop` then, still on the ticket branch, `git log --oneline origin/develop..HEAD`. A squash-merge lands as one commit on `develop`, so anything pushed to the PR branch in the window between CI going green and the merge landing — a worker or QA session racing the merge — won't be in it even though it's on the branch you're about to delete (confirmed: PR #353 had a CLAUDE.md commit pushed 12s *after* the merge; `git branch -d` would have silently discarded it). For each such commit, cherry-pick it onto current `origin/develop` from the main checkout (empty cherry-pick = already landed some other way, nothing to do) before proceeding. Note that `git log --oneline origin/develop..HEAD` lists the ticket branch's *entire* commit history after any squash merge — that's expected and not itself a signal, so don't iterate cherry-picks over every commit listed there, especially on a branch with several commits. A faster, equally safe check: scope a diff to just the PR's own files (`gh pr view <number> --json files`) between the squash-merge commit and the branch tip (`git diff <merge-commit> <branch-tip> -- <files...>`). An empty diff, or one that only shows removals (content present in the merge commit but not the branch tip), means the branch tip is simply stale relative to what got merged — normal when QA pushed Result-entry or fix commits straight to the remote PR branch after this worktree's last local sync (confirmed 2026-07-21, PRs #380/#385/#386: every case was this stale-local pattern, nothing was actually lost) — and nothing needs rescuing. Only if the scoped diff shows *additions* — content on the branch tip absent from the merge commit — does that identify real at-risk work; find which specific commit introduced it and cherry-pick only that one, rather than blindly cherry-picking the full list. Only once that's clear, reset the slot as described in the next bullet.
33
+ - **Before resetting, check what the slot's worktree is doing now.** Run `git -C <slot-path> branch --show-current` and `git -C <slot-path> status --short`, where `<slot-path>` is `.claude/worktrees/<name>` under the repo root (absolute path). The merged branch's name is the PR's `headRefName`; don't assume it starts with `feat/`. Then take exactly one of these three paths:
34
+ - **The worktree is on the merged branch or on `team/<name>`, and `status --short` is empty:** the slot is idle. `git -C <slot-path> checkout team/<name>`, `git -C <slot-path> reset --hard origin/develop`, then `git branch -D <headRefName>`. Report that the slot is back to idle, ready for its next ticket.
35
+ - **The worktree is on any other branch** (another ticket is in flight): never run `checkout` or `reset --hard` in it, because that throws away the other ticket's uncommitted work. Move the ref without checking it out: `git branch -f team/<name> origin/develop`, then `git branch -D <headRefName>`. Report that `team/<name>` now points at `origin/develop` and that the worktree was left on its current branch, untouched. This mirrors the receiving side's guard in `dev.md`. (Found 2026-09-27, #838: a #831 merge notice nearly wiped #511's uncommitted work.)
36
+ - **The worktree is on the merged branch or on `team/<name>`, but `status --short` is not empty:** stop. Don't reset or delete anything; report the uncommitted paths and ask the user what to do with them.
33
37
  - If the current branch is a `review/<headRefName>` branch created by `/verify-pr` (not a `.claude/worktrees/*` worktree — just a local branch in the main checkout), there's no worktree to remove: switch to `develop` and `git branch -D review/<headRefName>`.
34
38
  - If this session created the current worktree (i.e. it got here via `/start-worktree` or `/next-issue` earlier in this same session), use `ExitWorktree` with `action: "remove"`.
35
39
  - Otherwise (worktree predates this session), fall back to manual cleanup from the main checkout: `git worktree remove <path>`, then `git branch -d <branch>`, then `git worktree prune`.
@@ -8,6 +8,8 @@ Branch naming for this slot always puts `<name>` as the **second** `/`-separated
8
8
 
9
9
  **Team-process changes go through Bob's prompt-QA gate, not a direct push — every edit, no exceptions.** Retro-driven or ad-hoc edits to team-process/instruction files — `CLAUDE.md`, `.claude/team-roles/*.md`, `.claude/commands/*.md`, `docs/qa/run.md`/`setup.md` process content — get committed to a short branch off `develop` (`doc/<name>/retro-<date>`) and opened as a PR, same as any other work, and need a `prompt-qa-approved` label from Bob (`/team-member Bob`) before Bob merges it — a separate track from Kai's product-PR merges — no fast path for small or mechanical-looking edits, per direct user instruction 2026-07-21. Full flow in `.claude/team-roles/bob.md`. (This replaces a 2026-07-18 grant that allowed direct-to-`develop` pushes for these files with no review step at all — retired because that no-review gap is exactly what let the QA inline-fix exception below harden without anyone checking its wording; see `qa.md`'s Retro.) This does **not** extend to product code (`src/`, `tests/`, generated docs like `docs/tools.md`) or QA test-case content (`docs/qa/tests/*.md`) — those always go through the normal PR flow below.
10
10
 
11
+ **One ticket per session — refuse a second one.** Once this session has created or committed to a ticket branch (`<type>/<name>/issue-<n>`), it never starts a different ticket, even when the user tells it to. Instead, name the next ticket (the idle flow's queue lookup below) and tell the user it needs a fresh session, as two commands on their own lines: `/clear`, then `/team-member <name>`. A session can't clear itself. Stays in this session: QA send-back rounds on this ticket's own PR, and this ticket's `/retro` if the user approves one — run it before the `/clear`, since it needs this context. Why: every turn re-reads the full context, so a ticket started on top of a finished one pays for that carried context on every turn (measured in #843).
12
+
11
13
  - **On `team/<name>` (idle):** this slot is free. This slot's lane label is `lane-a` for Ash, `lane-b` for Jay. Get the queue, filtered to that label: `gh issue list --repo khuisman/mcp-gee-sweet --label "ready-for-development,<lane-label>" --state open --json number,title,body,labels`. Filter out any issue number already claimed by an *open* PR anywhere in the repo (so Ash and Jay never grab the same ticket, though the lane label should already prevent that): `gh pr list --state open --json headRefName --jq '[.[].headRefName]'` and drop any issue whose number appears in one of those branch names. Take the lowest-numbered remaining issue — check its comment thread, not just its body, before treating the body as the current scope: a maintainer triage comment can re-scope or split an issue after filing without editing the original text (e.g. #316 was narrowed to just its concurrency bug and had its progress-notification ask split into a separate backlog issue, #319, entirely via a comment — a session that only reads the issue body and implements straight through will silently redo work that's already been descoped or moved elsewhere). Scope can also go stale with *no* comment at all: if the body cross-references a sibling issue (a "Related" section, "see also #N"), check whether that sibling has already merged — its shipped shape may have generalized past what this issue's own body still literally proposes (#211 proposed a `convert_markdown` param mirroring #188's *original*, narrower proposal on `upload_local_file`; #188 shipped first with a broader `convert: bool` covering CSV/XLSX/DOCX/MD/HTML/PPTX, so implementing #211 exactly as filed would have bolted on a redundant second markdown-conversion switch instead of just wiring `sync_folder` into the mechanism #188 already built). **Stop here and name that issue to the user; don't create a branch or edit files until the user tells you, in this session, to start that ticket.** Bootstrapping via `/team-member`, a peer's `SendMessage` wake, a just-merged PR, or a finished `/retro` is not that instruction: on 2026-09-27 a session bootstrapped only to review open PRs started #511 on its own, leaving uncommitted work nobody had asked for. Once told, then:
12
14
  1. Pick `<type>` to match the ticket (feature vs. fix vs. chore/docs), then `git fetch origin develop && git checkout --no-track -b <type>/<name>/issue-<n> origin/develop`. Keep `--no-track`: without it the ticket branch's upstream is `origin/develop`, so a bare `git pull` merges `develop` in and a push under a non-default `push.default` can land WIP on `develop` (found 2026-09-27 on `chore/jay/issue-511`). Push with `git push -u origin <branch>` at step 6 to set the right upstream.
13
15
  2. Work through the issue fully — code, tests, doc changes. When adding a tool to a file with existing siblings that share a parameter (e.g. every `sheet`-taking tool in `structure.py` calls `_get_sheet_id` first and returns a clean `{"error": ...}` for a bad name), grep for that shared pattern across *every* sibling in the file before finishing, not just the one or two tools used as an implementation reference — `get_data_validation` shipped without the check despite ~22 sibling tools having it, caught by QA instead of before opening the PR (PR #361, TC-S100). Separately: converting a sequential loop to concurrent `asyncio.gather` fan-out can silently break implicit ordering-based safety the sequential version had for free (e.g. an existence/dedup check computed once against a snapshot of pre-batch state, safe when items were handled one at a time, unsafe once they all race against that same stale snapshot at once) — audit a sequential block for order-dependent assumptions before parallelizing it, don't just port the body into a gather (PR #351, TC-D200: `download_folder`'s new concurrent fan-out let two same-named Drive files race to write the same local path, something the old sequential loop was accidentally safe against).
@@ -18,7 +20,7 @@ Branch naming for this slot always puts `<name>` as the **second** `/`-separated
18
20
  7. Remove the `ready-for-development` label once the PR is open.
19
21
  8. Run `/notify-partner` (`.claude/commands/notify-partner.md`), first-pass case — wakes the QA partner directly if one is already running as a live agent; a silent no-op otherwise.
20
22
  9. Report the PR URL. Leave the branch checked out — don't return to `team/<name>` until the ticket is fully merged (see `/merge-pr`'s team-slot reset step).
21
- - **When a peer (Bob, Kai) says a PR merged and asks you to reset `team/<name>`:** run `git branch --show-current` and `git status --short` first. If this worktree is on a different, still-open ticket branch, don't check out `team/<name>` or `reset --hard`: that throws away the in-flight ticket's uncommitted work. Move the ref without checking it out (`git fetch origin develop && git branch -f team/<name> origin/develop`) and delete the merged branch with `git branch -D`. The peer can't see your worktree, so its instructions assume the slot is idle (2026-09-27: Bob's merge notice for #831 arrived while #511 was uncommitted here).
23
+ - **When a peer (Bob, Kai) says a PR merged and asks you to reset `team/<name>`:** run `git branch --show-current` and `git status --short` first. If this worktree is on a different, still-open ticket branch, don't check out `team/<name>` or `reset --hard`: that throws away the in-flight ticket's uncommitted work. Move the ref without checking it out (`git fetch origin develop && git branch -f team/<name> origin/develop`) and delete the merged branch with `git branch -D`. The peer can't see your worktree, so its instructions assume the slot is idle (2026-09-27: Bob's merge notice for #831 arrived while #511 was uncommitted here). After an idle-slot reset, don't pick up the next ticket here — see "One ticket per session" above.
22
24
  - **Whenever a new commit goes up on an already-open PR in response to QA feedback:** after the push, run `/notify-partner` again, re-verification case.
23
25
  - **If the open PR falls behind `develop`** (e.g. GitHub reports it out of date): `git fetch origin <branch>` first, *then* `git merge origin/develop --no-edit` — QA can push commits directly to an open PR's branch (Result entries, fix confirmations), and merging `origin/develop` onto a stale local HEAD before pulling those creates an avoidable divergent local merge commit that then has to be discarded (`git reset --hard origin/<branch>`) and redone on top of the QA-updated tip instead of just pushed (confirmed 2026-07-18, PR #357).
24
26
  - **If `git push` itself is rejected non-fast-forward on this same branch** (not `develop` falling behind — the remote copy of *this* branch has commits this local HEAD doesn't): same root cause as above, QA pushed a Result-entry commit directly while a fix round was still in flight locally. `git fetch origin <branch>`, then `git merge origin/<branch> --no-edit` (never rebase — QA's commit is already on the shared remote and possibly already reviewed against). When the conflict lands in a shared `docs/qa/tests/*.md` file, it's almost always the same shape: QA's `**Result (...)**` entry for a test case you both touched needs to end up immediately after that test case's own `**Cleanup:**` line, *before* any new test cases you're adding later in the same file — don't take either side of the conflict wholesale, splice QA's Result block back into its own test case first. Confirmed twice in one PR (#369, both QA review rounds).
@@ -4,10 +4,12 @@ Sky and Kit each have their own dedicated server — `mcp-gee-sweet-sky` / `mcp-
4
4
 
5
5
  **Team-process changes go through Bob's prompt-QA gate, not a direct push — every edit, no exceptions.** Retro-driven or ad-hoc edits to team-process/instruction files — `CLAUDE.md`, `.claude/team-roles/*.md`, `.claude/commands/*.md`, `docs/qa/run.md`/`setup.md` process content — get committed to a short branch off `develop` (`doc/<name>/retro-<date>`) and opened as a PR, same as any other work, and need a `prompt-qa-approved` label from Bob (`/team-member Bob`) before Bob merges it — a separate track from Kai's product-PR merges — no fast path for small or mechanical-looking edits, per direct user instruction 2026-07-21. Full flow in `.claude/team-roles/bob.md`. (This replaces a 2026-07-18 grant that allowed direct-to-`develop` pushes for these files with no review step at all — retired because that no-review gap is exactly what let the inline-fix exception below harden without anyone checking its wording.) This does **not** extend to product code (`src/`, `tests/`, generated docs like `docs/tools.md`) or QA test-case content (`docs/qa/tests/*.md`) — those always go through the normal PR flow.
6
6
 
7
+ **One PR per session — refuse a second one.** Once this session has reset this worktree to a PR's branch (step 1 below), it never starts a pass on a different PR number, even when the user tells it to. Instead, name that PR and tell the user it needs a fresh session, as two commands on their own lines: `/clear`, then `/team-member <name>`. A session can't clear itself. Stays in this session: re-verification rounds on the same PR, and that PR's `/retro` if the user approves one — run it before the `/clear`, since it needs this context. Why: same as `dev.md`'s "One ticket per session" (#843).
8
+
7
9
  Find the partner Dev's open PR by matching the branch's second `/`-separated segment against the partner's lowercase name — not a `feat/`-specific prefix, since the Dev's branch type varies: `gh pr list --state open --json number,headRefName,url --jq '[.[] | select((.headRefName | split("/"))[1] == "<partner>")]'`. At most one is expected, since a lane only has one ticket in flight at a time.
8
10
 
9
11
  - **None found:** self-heal first — `git fetch origin develop`. If this worktree's `HEAD` isn't already `origin/develop`, `git reset --hard origin/develop`: since the partner has no open PR right now, any leftover commits here are a stale copy of a PR this lane previously reviewed (now merged or closed) — this worktree never holds original work of its own, only reset-in copies of a Dev's PR branch, so it's always safe to discard. Then report that Lane {A/B} has nothing to verify right now.
10
- - **One found:** **stop here and name the PR to the user; don't reset this worktree or start the pass until the user tells you, in this session, to verify it.** Bootstrapping via `/team-member`, a `/notify-partner` wake from the Dev, or a finished `/retro` is not that instruction (same rule as `dev.md`'s ticket pickup, added 2026-09-27 after sessions bootstrapped only to review open PRs started work on their own). Once told, this is now essentially `/verify-pr`'s steps 4 onward, but simpler — this worktree already IS a dedicated review space, so skip `/verify-pr`'s steps 1–3 (the main-checkout precondition and the `review/<branch>` fetch trick don't apply here):
12
+ - **One found:** **stop here and name the PR to the user; don't reset this worktree or start the pass until the user tells you, in this session, to verify it.** Bootstrapping via `/team-member`, a `/notify-partner` wake from the Dev, or a finished `/retro` is not that instruction (same rule as `dev.md`'s ticket pickup, added 2026-09-27 after sessions bootstrapped only to review open PRs started work on their own). That instruction, once given in this session, covers the PR's whole QA cycle, not just one round: a later `/notify-partner` wake saying the Dev pushed a fix on the *same* PR is a re-verification round, so go straight to step 1 and `/qa-kickoff` without asking again. A new session still needs its own instruction (PR #842, 2026-09-28); a different PR number needs a new session — see "One PR per session" above. Once told, this is now essentially `/verify-pr`'s steps 4 onward, but simpler — this worktree already IS a dedicated review space, so skip `/verify-pr`'s steps 1–3 (the main-checkout precondition and the `review/<branch>` fetch trick don't apply here):
11
13
  1. `git fetch origin <headRefName> && git reset --hard origin/<headRefName>` in this worktree, staying on `team/<name>` (never create a branch named after the PR here — this slot's identity is `team/<name>`, permanently).
12
14
  2. Run `/qa-kickoff` (`.claude/commands/qa-kickoff.md`). It contains the exact, hard-coded message to send the user for this round — first-pass vs re-verification wording included — so there's nothing to compose or remember here (see [[feedback_batch_independent_user_commands]] and [[feedback_no_silent_fix_downgrade]]: this exact ask broke the batching rule three times before it was moved into a scripted command instead of left as prose to interpret).
13
15
  3. Follow `/qa-kickoff`'s own instructions for what happens after the reconnect lands (first pass: read the `/code-review` result; re-verification: `git show <fix-sha>` + live-verify instead).
@@ -0,0 +1,144 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Commands
6
+
7
+ Run locally from the cloned repo:
8
+ ```bash
9
+ uv run mcp-gee-sweet
10
+ uv run mcp-gee-sweet --transport sse # SSE instead of stdio
11
+ ```
12
+
13
+ Build and publish:
14
+ ```bash
15
+ uv build # produces dist/*.whl
16
+ uv sync # install deps from uv.lock
17
+ ```
18
+
19
+ Docker:
20
+ ```bash
21
+ docker build -t mcp-gee-sweet .
22
+ docker run --rm -p 8000:8000 \
23
+ -e CREDENTIALS_CONFIG=<base64> \
24
+ -e DRIVE_FOLDER_ID=<id> \
25
+ mcp-gee-sweet
26
+ ```
27
+
28
+ Tests live in `tests/` and can be run with `uv run pytest`.
29
+
30
+ ## Architecture
31
+
32
+ Logic is split across `src/mcp_gee_sweet/`: `server.py` (MCP setup, tool decorator, resources), `auth.py` (lifespan, `SpreadsheetContext`), and `tools/` (domain-based layout).
33
+
34
+ Each entry below says what a module owns and the invariants a future edit could break. The per-PR history behind them (review rounds, reversed approaches, the incident behind each rule) is in [`docs/design/module-history.md`](docs/design/module-history.md), linked from each entry as *History*. Read a module's history before changing its behavior. Record new history in a design or decision doc, not here.
35
+
36
+ - `tools/sheets/data.py` — read/write cell data (`get_sheet_data`, `get_sheet_formulas`, `get_multiple_*`, `find_in_spreadsheet`, `clear_values`, `update_cells`, `batch_update_cells`, `batch_update`)
37
+ - `tools/sheets/structure.py` — sheet structure (`list_sheets`, `copy_sheet`, `duplicate_sheet`, `rename_sheet`, `create_sheet`, `delete_sheet`, `add_rows`, `add_columns`, `delete_rows`, `delete_columns`, `hide_rows`, `unhide_rows`, `hide_columns`, `unhide_columns`, `resize_rows`, `resize_columns`, `format_cells`, `update_borders`, `add_data_validation`, `get_data_validation`, `clear_data_validation`, `merge_cells`, `unmerge_cells`, `freeze`, `update_sheet_properties`, `sort_range`, `add_chart`)
38
+ - `tools/sheets/helpers.py` — A1 notation helpers (`_parse_a1_notation`, `_column_index_to_letter`, etc.)
39
+ - `tools/drive/files.py` — file/folder operations (`create_spreadsheet`, `import_csv_to_sheet`, `list_spreadsheets`, `list_files`, `search_files`, `create_folder`, `create_shortcut`, `copy_file`, `move_file`, `rename_file`, `star_file`, `unstar_file`, `delete_file`, `restore_file`, `empty_trash`, `list_shared_with_me`, `list_recent_files`, `get_storage_quota`, etc.). `rename_file` and `star_file`/`unstar_file` mark the file's parent folder(s) dirty in `drive_folder_cache`, so a later `list_files` reflects the change without a manual `refresh_cache` (TC-D202/203/260 in `docs/qa/tests/drive_files.md`). `get_file_metadata`/`list_files`/`search_files`/`search_spreadsheets` surface `starred`; `list_files`/`get_file_metadata` surface `md5_checksum` (`None` for Workspace files, which have no fixed bytes) so callers can detect real content drift instead of trusting `modifiedTime`. [History](docs/design/module-history.md#toolsdrivefilespy).
40
+ - `tools/drive/sharing.py` — permissions (`share_spreadsheet`, `share_file`, `list_permissions`, `update_permission`, `remove_permission`, `transfer_ownership`). `transfer_ownership` requires OAuth — service accounts have no personal Drive identity to transfer ownership to/from, so Drive's API rejects the transfer.
41
+ - `tools/drive/transfer.py` — upload/download/sync/export/revisions. [History](docs/design/module-history.md#toolsdrivetransferpy). Invariants:
42
+ - `_upload_local_file` (the core of `upload_local_file`) stays module-level: `docs/images.py` imports it cross-package, the same way `docs/content.py` imports `_SA_QUOTA_ERROR`. A `storageQuotaExceeded` error renders as `_SA_QUOTA_ERROR` at every site.
43
+ - **Two conversion mechanisms; don't conflate them.** Drive *native import conversion* uploads with the source mimeType and sets the target Workspace mimeType in metadata (`_CONVERT_MIME`: CSV/XLSX→Sheets, DOCX/MD/HTML→Docs, PPTX→Slides). It backs `upload_local_file(convert=)`, `upload_local_folder(convert=)`, `sync_folder(convert_markdown=)`, and `upload_file(convert_to_doc=)` (raw content, always a Doc; shares `_GOOGLE_DOC_MIME`). `docs/content.py`'s `create_doc_from_file` is different: it parses locally and rebuilds via Docs API requests.
44
+ - **Converted `.md` Docs** keep the `.md` name in Drive and are recognized on every later sync by a Drive `properties` marker stamped at creation through `_convert_properties` (by both `_sync_level` and `_upload_local_file(convert=True)`), regardless of the current call's flags. Values must fit Drive's 124-byte per-property cap: `_CONVERT_MARKDOWN_SOURCE_PROP` holds the raw name when it fits, else `_CONVERT_SOURCE_PROP` holds a `sha256:` digest (a digest-marked Doc stops matching if renamed; see `docs/known-limitations.md`). Classify through `_converted_md_source_name`/`_is_converted_md_entry`, recomputed from the resource each time; never cache the classification on a `drive_map` entry. There is no reverse conversion: a drive-only or drive-newer converted Doc is a `conflict` (message via `_NO_REVERSE_CONVERSION_CLAUSE`), except `direction='upload'`, which reports a plain `skip`.
45
+ - **Name collisions** in `drive_map` (any duplicate name, any type) go through the plan as a `"collision"` action: `conflict` under `dry_run`, a `failed` entry (`kind: "collision_fail"`) on execution. Never write to `failed` from the `drive_map`-building loop.
46
+ - **`dry_run`** never reports failures and never hashes. It leaves `uploaded`/`downloaded`/`skipped`/`conflicts`/`failed` empty; `actions` alone is the complete preview (duplicating it tripped the response cap).
47
+ - **`modifiedTime` stamping.** Every upload stamps `modifiedTime` from the local mtime in `create()`. A converted upload also needs a metadata-only `update()` afterward, because Drive's asynchronous conversion overwrites the create-time value (the doubled latency is accepted). If that restamp fails after `create()` succeeded, report the created file's ID via `_restamp_failure_result` (`{"error", "fileId"}`); `_sync_level`, `upload_local_file`, and `upload_local_folder` must carry `fileId` into `failed` and treat that result as cache-dirtying. `_upload_local_file` catches broadly and returns `{"error": ...}`, since the tool has no try/except of its own.
48
+ - **`sync_folder` comparison** for a name on both sides: mtime diff first, against `_SYNC_MTIME_TOLERANCE`. Within tolerance but a different `size` (non-Workspace files only) is a `conflict` for every direction: recency is unknown, so never auto-transfer. `use_checksum=True` hashes every both-sides pair that has a Drive `md5Checksum` (after the plan loop, concurrently, capped by `_SYNC_HASH_CONCURRENCY`, filling placeholder steps from `_mtime_step`): a match is in sync, a within-tolerance mismatch is a `conflict` (`_diverged_same_mtime_step`), and a mismatch outside tolerance falls through to the mtime logic. Under `dry_run` those reasons end in `(checksum not verified in dry_run)`. A local file that can't be read or stat'd becomes a `"local_read_fail"` plan action, never an uncaught raise. Workspace and converted files have no `md5Checksum`/`size` and always compare by mtime alone.
49
+ - **`upload_local_folder`** routes each file through `_upload_local_file(skip_if_exists=False)` but keeps its own single bulk `list()` for `skip_if_exists` (name match; name plus target mimeType when `convert=True`), so existence is checked once. An unsupported extension under `convert` lands in `failed`, never uploads as-is.
50
+ - **`sync_folder(result_local_path=)`** is its response-cap bypass (`local_path` already means the sync directory). It's rejected with `ValueError` before any API call if it resolves inside `local_path`, where the manifest would be synced up. `enforce_response_size_cap(local_path_param=...)` names the right parameter in the cap error.
51
+ - `tools/drive/activity.py` — `list_file_activity` (Drive Activity API v2; requires `drive.activity.readonly` scope)
52
+ - `tools/docs/` — Google Docs package; `__init__.py` is a thin dispatcher that delegates to four submodules:
53
+ - `docs/content.py` — helpers (`_md_to_html`, `_to_doc_requests`, `_html_to_doc_requests`) and tools (`create_doc`, `get_doc_content`, `write_doc_content`, `create_doc_from_file`, `update_doc_from_file`, `get_doc_structure`, `find_in_doc`). `_apply_doc_content` is shared by `create_doc`/`create_doc_from_file`/`write_doc_content`; `_replace_doc_content` (clear, then insert, as separate `batchUpdate` calls because of the same-batch `insertText` style-inheritance quirk) by `write_doc_content`/`update_doc_from_file`. `find_in_doc` returns UTF-16 offsets usable directly with `style_doc_range`. `get_doc_structure` surfaces each paragraph's `headingId` and `bullet` (`{listId, nestingLevel}`, a top-level item's absent level normalized to `0`). After conversion, `_has_pending_anchor_links(content_requests, tables)` gates `_resolve_heading_anchors`, which rewrites a `#slug` link to `.../edit?tab=t.0#heading=<headingId>` or strips it when no heading matches confidently: a link to the wrong section is worse than no link (`docs/design/heading-anchor-resolution.md`). Its walker, `_walk_headings_and_anchor_runs`, must recurse into table cells and carry the offset forward when an element has no `startIndex` (the doc's first element often doesn't). Markdown/HTML images go through `_resolve_image_source`: a local path (uploaded), `drive:<file_id>`, or a public `http(s)://` URL. Local and `drive:` sources are shared `anyone:reader` and revoked after embedding unless `revoke_sharing=False`. One unfetchable `insertInlineImage` fails the whole `batchUpdate`, so `_apply_doc_content` retries with that request stripped (its index parsed from Google's error); see `docs/design/image-conversion.md`. [History](docs/design/module-history.md#toolsdocscontentpy).
54
+ - `docs/editing.py` — text-range editing tools (`insert_doc_text`, `delete_doc_range`, `insert_page_break`, `insert_softbreak_paragraph`). `insert_softbreak_paragraph` joins lines with `"\v"` in a single `insertText` (the API keeps it one paragraph, like Shift+Enter) and sets `namedStyleType` explicitly over the inserted span. [History](docs/design/module-history.md#toolsdocseditingpy).
55
+ - `docs/images.py` — tools (`insert_inline_image`, `insert_local_images`) plus the size validation, downscaling, and error rewriting shared by every `insertInlineImage` call site. The Docs backend fetches an image anonymously, so a `drive_file_id` must be shared `anyone`/`reader` first. Google's limits are 25 megapixels, 50MB, PNG/JPEG/GIF, and its own error names neither. `check_image_file` checks both limits before any upload, share, or embed call, reading only `stat()` and the header (it builds Pillow's format plugin directly, since `Image.open`'s decompression-bomb guard would skip huge headers); `check_drive_image_metadata` does the same for Drive sources from metadata. `auto_downscale` (default `False`) is opt-in: `downscale_drive_file` makes a `" (resized)"` copy and never mutates the original, and `prepare_local_image` uploads resized bytes for a local path. `downscale_image_bytes` runs via `asyncio.to_thread`, declines animated GIFs (returns `None`), and iterates when bytes, not pixels, are over. A bare `http(s)://` source is deliberately never fetched to pre-check; it gets `rewrite_too_large_error`, which names both limits because it can't tell which one was hit. [History](docs/design/module-history.md#toolsdocsimagespy).
56
+ - `docs/anchors.py` — `resolve_heading_anchor`, the slug matching behind the anchor pass. It tries GitHub's non-collapsing hyphen scheme (hand-rolled) and GitLab's collapsing one (python-markdown's `toc` `slugify`), with `-1`/`-2` duplicate-heading suffixes, against the doc's full heading list. The fuzzy fallback (`_fuzzy_match`) tries the anchor's literal tokens at occurrence 0 first, and only then treats a trailing `-<digits>` as an occurrence index. A requested occurrence that doesn't exist returns `None`, never the nearest candidate. [History](docs/design/module-history.md#toolsdocsanchorspy).
57
+ - `docs/tables.py` — tools (`insert_doc_table`, `insert_table_row`, `delete_table_row`, `insert_table_column`, `delete_table_column`, `merge_table_cells`)
58
+ - `docs/style.py` — helpers (`_NAMED_STYLE_TYPES`, `_read_body_styles`, `_read_named_styles`, `_build_named_style_requests`, `_text_style_and_fields`, `_add_or_clear_field`) and tools (`style_doc_range`, `style_doc_table_cells`, `create_paragraph_bullets`, `delete_paragraph_bullets`, `get_doc_theme`, `get_doc_named_styles`, `apply_theme`). [History](docs/design/module-history.md#toolsdocsstylepy). Invariants:
59
+ - `createParagraphBullets` has no `nestingLevel` field. The API infers each paragraph's level from its leading tabs *relative to the other paragraphs in the same call*, then consumes the tabs, and it is a no-op on a paragraph already in a list. So `create_paragraph_bullets` always `deleteParagraphBullets` first, expands each target to its contiguous same-`listId` neighbors (keeping their levels), and issues one `createParagraphBullets` per contiguous same-preset run.
60
+ - A run's preset comes from the doc's own `lists` map (`glyphType` = numbered, `glyphSymbol` = bulleted) unless the caller passed `bullet_preset`; explicit always wins. Conflicting explicit presets joined by a shared context paragraph return `{"error": ...}` instead of picking one.
61
+ - A run with no level-0 paragraph is wrapped with a throwaway 0-tab anchor paragraph (insert `"\n"`, create over the range +1, delete the anchor), the same fix as `emitter.py`; otherwise the whole run collapses to level 0.
62
+ - To clear a nested field such as a link, omit the key from `textStyle` but name it in `fields` (`_add_or_clear_field`); the API rejects `link: {}`. Guard the request on `if fields:`, not `if text_style:`, since a clear-only call has an empty style.
63
+ - `docs/layout.py` — tools (`create_header`, `create_footer`)
64
+ - `docs/comments.py` — tools (`list_doc_comments`, `add_doc_comment`, `resolve_doc_comment`) via the Drive `comments`/`replies` resource, not the Docs API
65
+ - `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).
66
+ - `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`). An image inside a code paragraph renders outside the fence (`_render_code_block`). 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 `"\v"` back into `"\n"` only for Courier New runs. A nested table renders a placeholder note. [History](docs/design/module-history.md#toolsdocsdoc_to_astpy--docsast_to_markdownpy--docsmarkdown_exportpy).
67
+ - `docs/ast.py` — dataclass schema
68
+ - `docs/html_parser.py` — HTML→AST. [History](docs/design/module-history.md#toolsdocshtml_parserpy). Invariants:
69
+ - 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.
70
+ - `_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 `&nbsp;` lines are deliberate spacers. A bare `<hr>` appends an empty `Paragraph`. `<pre>` builds its node directly and applies the same fresh-vs-resumed rule itself, keeping a fresh `<pre>`'s whitespace.
71
+ - 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.
72
+ - `_resume_interrupted_block` flushes whatever block is open, unconditionally, before anything else.
73
+ - Every direct `Paragraph` construction must pass `blockquote_depth=self._blockquote_depth` (`docs/design/blockquote-representation.md`). `_make_bullet_item` clamps depth to `>= 0` for an orphan `<li>`.
74
+ - Known gap: a resumed block's genuine trailing `&nbsp;` is dropped (#444).
75
+ - `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:
76
+ - Bullet depth is encoded as leading `"\t"` on each paragraph's text, and `createParagraphBullets` computes levels only within one call. So each maximal contiguous run of same-`ordered` `BulletItem`s gets exactly one call; one call per paragraph creates separate lists and breaks numbering.
77
+ - A run with no depth-0 item is wrapped with a throwaway 0-tab anchor paragraph (insert, create over range +1, delete), so every item's `nestingLevel` equals its depth, unclamped. A fragment whose parents were dropped rendering deeper than expected is intended (#714). Each `bullet_run_requests` entry is therefore a list of requests.
78
+ - Tab consumption shifts later positions, so the bullet pass runs after all other per-node requests and before the `insertTable` pass, latest-position-first. Each `_PendingTable` position is decremented by the cumulative tab count of the runs before it (`utf16_len`, not `len`).
79
+ - `_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.
80
+ - Blockquote nodes get `indentStart` (36pt × depth) and a gray `borderLeft`.
81
+ - 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.
82
+ - `tools/cache.py` — `refresh_cache`, `set_cache_ttl`/`get_cache_ttl` (runtime TTL change, no restart needed)
83
+ - `tools/calendar.py` — Calendar API tools
84
+ - `tools/response_limits.py` — shared response-size safety net (`enforce_response_size_cap`, `write_capped_result_to_disk`); imported cross-package the same way `tools/drive/__init__.py`'s `_SA_QUOTA_ERROR` is. Cap configured via `MAX_TOOL_RESPONSE_CHARS` (see `docs/configuration.md` for the current default — raised in issue #519 after live-testing found the client-connection-death failure mode it defends against no longer reproduces at the old default's scale, kept as defense-in-depth for other/untested MCP clients); see `docs/decisions/decision-response-size-cap-generalization.md` and `docs/decisions/decision-response-size-cap-reevaluation-519.md`
85
+
86
+ `__init__.py` loads `src/mcp_gee_sweet/.env` via `python-dotenv` before importing `server`, so env vars from that file are in `os.environ` by the time any module-level `os.environ.get()` runs. Priority: real env var > `.env` > default.
87
+
88
+ **Startup / auth** (`spreadsheet_lifespan`): MCPServer lifespan context manager that authenticates on server start and injects a `SpreadsheetContext` (holding `sheets_service`, `drive_service`, `docs_service`, `calendar_service`, and `activity_service`) into every tool call via `ctx.request_context.lifespan_context`. It also stamps the same `SpreadsheetContext` into a module-level `auth._lifespan_context`, readable via `auth.get_lifespan_context()` — this is what `server.py`'s static `server://auth-status` resource reads instead of going through Context (see the MCP resources note below).
89
+
90
+ **The lifespan runs once per connection under SSE, not once per process.** mcp v2's `Server.run` enters `spreadsheet_lifespan` for every SSE connection; stdio has exactly one. So per-connection auth outcomes belong on the `SpreadsheetContext` it yields, never in a module global that a later connection's lifespan can overwrite or reset. PR #828 (#811) round 1 kept its degraded-start message in a global, and QA caught a second connection clearing the message the first still relied on. Genuinely process-wide decisions (e.g. "don't retry the blocking consent flow") are the exception, and should say so in a comment.
91
+
92
+ **No usable OAuth token (#811):** under stdio the server never runs the browser consent (stdout is the JSON-RPC channel). `AUTH_METHOD=oauth`, or the waterfall with no service account/ADC fallback, starts *degraded*: the yielded context has every service `None` and `unauthorized_message` set, and `server.py`'s `_timed` wrapper raises that message before any tool body runs. It raises rather than returns, because a `{"error": ...}` dict would fail a list-typed tool's output schema. Under SSE the consent runs with its prompt on stderr and an `OAUTH_CONSENT_TIMEOUT_SECONDS` bound. Any failure degrades that connection, and consent is never retried in the process, since the wait is synchronous and blocks every connection. The recovery path users are pointed at is the `mcp-gee-sweet auth` subcommand (`auth.run_auth_command`, argv parsing in `server._auth_main`). `scripts/oauth_setup.py` is only a QA wrapper over the same `run_consent_flow`/`write_token_json` (0600) helpers.
93
+
94
+ **CodeQL flags logging any value whose name looks like a secret.** `py/clear-text-logging-sensitive-data` is a *high* alert that blocks merge. It keys on names, so logging a raw `OAUTH_CONSENT_TIMEOUT_SECONDS` value (an integer) tripped it on PR #828 after QA had already passed. Don't interpolate the value of an `OAUTH_*`/`*TOKEN*`/`*SECRET*`/`*PASSWORD*`-named env var or variable into a log message; name the setting without echoing it.
95
+
96
+ The project philosophy is to be as powerful and flexible as possible by default, while giving security-conscious users the configuration knobs to lock things down. Two mechanisms reflect this:
97
+
98
+ - **Auth** (`AUTH_METHOD`): out of the box the waterfall tries everything so the server just works; setting `AUTH_METHOD` restricts it to exactly one method with no fallback.
99
+ - **Tool surface** (`ENABLED_TOOLS` / `--include-tools`): by default all tools are registered; setting `ENABLED_TOOLS` to a comma-separated list of function names limits the server to exactly that subset. This lets operators expose only the tools their users need.
100
+
101
+ By default (`AUTH_METHOD` unset), auth falls through in order: OAuth (`CREDENTIALS_PATH`/`TOKEN_PATH`) → service account (`CREDENTIALS_CONFIG` or `SERVICE_ACCOUNT_PATH`) → Application Default Credentials. OAuth is tried first because it authenticates as the user (full personal Drive access); service account is a fallback for headless/server deployments.
102
+
103
+ Set `AUTH_METHOD` to pin a specific method with no fallback — removes ambiguity for security audits, CI, or testing a tool's behavior under a known credential type:
104
+ - `AUTH_METHOD=oauth` — OAuth only; fails fast if the OAuth client JSON is missing, and starts degraded (see above) if there is no usable token
105
+ - `AUTH_METHOD=service_account` — service account only; requires `CREDENTIALS_CONFIG` or `SERVICE_ACCOUNT_PATH`
106
+ - `AUTH_METHOD=adc` — ADC only
107
+
108
+ **Logging** (`DEBUG_LEVEL` env var): when set, configures the `mcp_gee_sweet` package logger and `uvicorn.access` logger to the given level (`DEBUG`, `INFO`, `WARNING`, `ERROR`). The `_timed` wrapper in `server.py` emits one `INFO` line per tool call to `mcp_gee_sweet.access` — format: `"IP" "UA" "TOOL name" status elapsed`. IP/UA come from `ctx.request_context` in SSE mode; fall back to `-` in stdio. `LOG_FILE` and `ACCESS_LOG_FILE` env vars write logs to file (required for stdio where stderr is dropped by the host). Runtime config lives in `src/mcp_gee_sweet/.env` (gitignored); template at `src/mcp_gee_sweet/.env.template`.
109
+
110
+ **Tool registration** (`tool` decorator wrapper): A custom `@tool()` decorator wraps `@mcp.tool()`. It checks `ENABLED_TOOLS` (set via `ENABLED_TOOLS` env var or `--include-tools` CLI arg) and skips registration for any tool not in the allowlist. This is how tool filtering works — tools simply aren't registered with MCPServer rather than being conditionally hidden.
111
+
112
+ **Async execution** (`execute_in_thread` + `thread_http`, in `http_transport.py`; full rationale in `docs/decisions/decision-async-tool-execution.md`, [History](docs/design/module-history.md#async-execution)):
113
+ - Every `@tool()` function must be `async def`, even one with no API calls: `_timed` awaits it, so a plain `def` raises `TypeError` (`test_server.py::TestAllToolsAreAsync` guards this).
114
+ - Wrap every `.execute()` as `await execute_in_thread(chain.execute, service)`. Never write `asyncio.to_thread(chain.execute, http=thread_http(service))`: the kwarg is evaluated on the event-loop thread, so concurrent calls share one non-thread-safe `httplib2` transport.
115
+ - Six multi-call tools (`sync_folder`, `get_multiple_sheet_data`, `get_multiple_spreadsheet_summary`, `import_csv_to_sheet`, `share_spreadsheet`, `share_file`) fan out with `asyncio.gather(..., return_exceptions=True)`.
116
+ - A `ctx.report_progress()` call inside a gathered per-item coroutine must be wrapped in its own try/except, or a dropped client session turns an already-successful item into a failure.
117
+ - `cache.py`'s SQLite I/O stays synchronous on purpose (see `cache.py::_open()`). `_BaseCache.snapshot_epoch()`/`store(..., epoch=...)` stop a mid-fetch `refresh_cache()` from being overwritten, per key (`SheetStructureCache`, `SheetDataCache`, `DriveFolderCache`). Size-bounded caches share `_get_sized`/`_store_sized`.
118
+ - To unit-test real cross-thread concurrency, put the barrier inside the mocked `.execute()`, not `.list()`/`.create()`, which run on the event-loop thread (see `tests/drive/test_transfer.py::TestSyncFolderRecursive::test_recursive_sibling_subfolders_descend_concurrently`).
119
+
120
+ **Two Google API clients**: The server builds both a Sheets client (`sheets/v4`) and a Drive client (`drive/v3`). Most tools use only the Sheets client; `list_spreadsheets`, `create_spreadsheet`, `share_spreadsheet`, and `list_folders` use Drive. `create_spreadsheet` uses Drive to create the file (so it can place it in a folder), not the Sheets API.
121
+
122
+ **A1 notation helpers**: `_parse_a1_notation`, `_column_index_to_letter`, and `_letter_to_column_index` convert between A1 ranges and the 0-based row/column indices the Sheets batchUpdate API requires. The Sheets values API uses A1 notation directly; batchUpdate requires numeric indices — keep that distinction in mind when adding new tools.
123
+
124
+ **Docs API indices are UTF-16 code units, not Python code points**: every `startIndex`/`endIndex` in the Docs API counts UTF-16 code units — an astral-plane character (most emoji, some CJK/math symbols) is one Python `str` character but a 2-unit surrogate pair. `content.py`'s `find_in_doc` and `indices.py`'s `_collect_doc_paragraphs` account for this via `utf16_len`. `emitter.py`'s offset math (table positions, paragraph-style ranges, inline run-style ranges) does not — it derives offsets from plain `len()`/`enumerate()`, a confirmed-pattern bug tracked in #358. Any new code deriving a Docs API index from Python string length needs `utf16_len`-style accounting instead of raw `len()`.
125
+
126
+ **Sheets API `TextFormatRun.startIndex` is also UTF-16 code units.** `update_cells` (`tools/sheets/data.py`) accepts a rich-text cell as a list of `{"text", "hyperlink"}` runs; `_build_rich_text_cell` computes offsets with `_utf16_len` (`sheets/helpers.py`, delegating to `docs/indices.py`). A `data` array with only plain cells uses one `values().update()`. A mixed array writes plain cells by position via `values().batchUpdate()` and rich-text cells via a separate `batchUpdate`, so the two passes never touch each other's cells, and returns `{"values_update", "rich_text_update"}`. Never blank rich-text positions in the values write and rely on the second call to fill them: if it fails, the data is lost. [History](docs/design/module-history.md#sheets-api-textformatrunstartindex-is-also-utf-16-code-units).
127
+
128
+ **MCP resources**: `server://auth-status` (active auth method and Drive capability limits) and `spreadsheet://{spreadsheet_id}/info` (sheet list and grid properties). mcp v2 has no `get_context()` and can't inject Context into a *static* resource. So the template resource `get_spreadsheet_info` takes `ctx: Context` like a tool, while the static `get_auth_status` reads the process-wide `auth.get_lifespan_context()` singleton. See `docs/decisions/decision-mcp-v2-resource-context-access.md`. [History](docs/design/module-history.md#mcp-resources).
129
+
130
+ **`batch_update` tool**: This is a passthrough to the Sheets `spreadsheets().batchUpdate()` endpoint and accepts raw request objects. It's the escape hatch for any operation not covered by the named tools (formatting, conditional formatting, dimension properties, etc.).
131
+
132
+ ## Development workflow
133
+
134
+ **MCP restart**: after `docker compose restart mcp-gee-sweet`, restart Claude Code too; it doesn't reconnect to the SSE server. A team role's stdio `mcp-gee-sweet-<name>` server imports the package once at session start, so a live tool call after a mid-session edit runs the *pre-edit* code, which can look exactly like a real bug. To live-verify a same-session change, use a fresh interpreter: `uv run python3 -c "..."` with `mcp_gee_sweet.auth._oauth_creds()` plus `googleapiclient.discovery.build(...)`. That's also the only way to inspect raw fields no tool surfaces (e.g. `paragraph.bullet`, `lists`). [History](docs/design/module-history.md#mcp-restart).
135
+
136
+ **Generated tool docs**: `docs/tools.md` and the "Tool filtering" section of `docs/configuration.md` are generated by `scripts/gen_tool_docs.py` from the tool source (names, signatures, docstrings) — do not hand-edit the tool tables in either file. The script is wired into `.pre-commit-config.yaml` and runs whenever a file under `src/mcp_gee_sweet/tools/` or either generated doc changes; it fails the commit if a tool has no docstring, if a tool doesn't match any section in the script's `SECTIONS` config, or if a `SUBSETS` entry references a renamed/removed tool name. Run it manually with `uv run python scripts/gen_tool_docs.py`.
137
+
138
+ **PR creation**: open PRs ready-for-review (`gh pr create`, no `--draft`), not as drafts — draft should be an explicit, stated exception (e.g. genuinely incomplete work you want visible early), not the default. `gh pr merge --admin` refuses a draft PR outright (`merge-pr.md` now handles it by readying first), so an unnecessary draft is pure friction at merge time with no upside.
139
+
140
+ **Regression vs. pre-existing bug after a dependency bump**: don't answer from memory or changelogs. Install the old pinned version in a scratch venv and check the API surface directly, e.g. `python3 -m venv /tmp/x && /tmp/x/bin/pip install "mcp==1.27.1" && /tmp/x/bin/python -c "...hasattr(...)"`. [History](docs/design/module-history.md#regression-vs-pre-existing-bug-after-a-dependency-bump).
141
+
142
+ **Verify a ticket's API premise live before implementing, not after.** A field in a Google API discovery schema isn't necessarily writable; the schema lists read-only fields as ordinary properties. Before building on a capability not already confirmed live in this codebase, check the live discovery document with the scratch-script approach above: read the field's own `description` for a "read-only" note, and confirm some request type actually writes it. (`ParagraphStyle.tabStops` cost a full dev+QA cycle this way; #404.) [History](docs/design/module-history.md#verify-a-tickets-api-premise-live-before-implementing-not-after).
143
+
144
+ **Procedural feedback → the governing command file, not memory.** If the human's feedback corrects a repeatable team-workflow step that a `.claude/team-roles/*.md` or `.claude/commands/*.md` file governs (or should), edit that file, through Bob's prompt-QA gate (`.claude/team-roles/bob.md`); `.claude/commands/retro.md` treats this as a command decision. A memory-only fix is retrieved heuristically and loses to whatever the loaded prompt says. Memory is still right for project context, external pointers, and one-off judgment calls. [History](docs/design/module-history.md#procedural-feedback--the-governing-command-file-not-memory).
@@ -76,21 +76,29 @@ setup-team: ## Idempotently provision/refresh dev-team worktree slots and MCP co
76
76
  claude-team: setup-team ## Launch Claude Code with all dev-team MCP servers connected (Kai/Ash/Sky/Jay/Kit/Aziz/Amy/Joy/Bob) for Agent View.
77
77
  claude --mcp-config .claude/mcp-configs/team.mcp.json --strict-mcp-config --name "Kai"
78
78
 
79
+ # Each role session loads only its own servers (.claude/mcp-configs/<name>.mcp.json,
80
+ # written by setup_team.sh), not all of team.mcp.json: other roles' tool names cost
81
+ # ~26k tokens per session (#850). The flags go after the prompt because
82
+ # --mcp-config takes a variadic list and would swallow it. team-kai and team-aziz
83
+ # stay unflagged: they use every team server from the root .mcp.json plus the
84
+ # user-global ones, which --strict-mcp-config would drop.
85
+ team_mcp = --mcp-config "$(CURDIR)/.claude/mcp-configs/$(1).mcp.json" --strict-mcp-config
86
+
79
87
  .PHONY: team-ash
80
88
  team-ash: setup-team ## Launch Claude Code backgrounded directly into the Ash persona (Dev, lane A); shows up in `claude agents`.
81
- claude --bg --name "Ash" "/team-member Ash"
89
+ claude --bg --name "Ash" "/team-member Ash" $(call team_mcp,ash)
82
90
 
83
91
  .PHONY: team-sky
84
92
  team-sky: setup-team ## Launch Claude Code backgrounded directly into the Sky persona (QA, lane A); shows up in `claude agents`.
85
- claude --bg --name "Sky" "/team-member Sky"
93
+ claude --bg --name "Sky" "/team-member Sky" $(call team_mcp,sky)
86
94
 
87
95
  .PHONY: team-jay
88
96
  team-jay: setup-team ## Launch Claude Code backgrounded directly into the Jay persona (Dev, lane B); shows up in `claude agents`.
89
- claude --bg --name "Jay" "/team-member Jay"
97
+ claude --bg --name "Jay" "/team-member Jay" $(call team_mcp,jay)
90
98
 
91
99
  .PHONY: team-kit
92
100
  team-kit: setup-team ## Launch Claude Code backgrounded directly into the Kit persona (QA, lane B); shows up in `claude agents`.
93
- claude --bg --name "Kit" "/team-member Kit"
101
+ claude --bg --name "Kit" "/team-member Kit" $(call team_mcp,kit)
94
102
 
95
103
  .PHONY: team-aziz
96
104
  team-aziz: setup-team ## Launch Claude Code backgrounded directly into the Aziz persona (Release QA lead); shows up in `claude agents`.
@@ -98,15 +106,15 @@ team-aziz: setup-team ## Launch Claude Code backgrounded directly into the Aziz
98
106
 
99
107
  .PHONY: team-amy
100
108
  team-amy: setup-team ## Launch Claude Code backgrounded directly into the Amy persona (Tech writer); shows up in `claude agents`.
101
- claude --bg --name "Amy" "/team-member Amy"
109
+ claude --bg --name "Amy" "/team-member Amy" $(call team_mcp,amy)
102
110
 
103
111
  .PHONY: team-joy
104
112
  team-joy: setup-team ## Launch Claude Code backgrounded directly into the Joy persona (Lead architect); shows up in `claude agents`.
105
- claude --bg --name "Joy" "/team-member Joy"
113
+ claude --bg --name "Joy" "/team-member Joy" $(call team_mcp,joy)
106
114
 
107
115
  .PHONY: team-bob
108
116
  team-bob: setup-team ## Launch Claude Code backgrounded directly into the Bob persona (Senior prompt engineer); shows up in `claude agents`.
109
- claude --bg --name "Bob" "/team-member Bob"
117
+ claude --bg --name "Bob" "/team-member Bob" $(call team_mcp,bob)
110
118
 
111
119
  .PHONY: team-kai
112
120
  team-kai: setup-team ## Launch Claude Code backgrounded directly into the Kai persona (Orchestrator); shows up in `claude agents`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mcp-gee-sweet
3
- Version: 0.9.1.dev169
3
+ Version: 0.9.1.dev182
4
4
  Summary: MCP server for Google Workspace — Sheets, Drive, Docs (HTML or Markdown), and Calendar.
5
5
  Project-URL: Homepage, https://github.com/khuisman/mcp-gee-sweet
6
6
  Project-URL: Bug Tracker, https://github.com/khuisman/mcp-gee-sweet/issues
@@ -45,7 +45,7 @@ Confirmed via user decision over two alternatives (anyio pytest plugin, manual `
45
45
 
46
46
  - Full unit suite (665 tests) green; `ruff check` clean; zero unwrapped `.execute()` calls remaining (`grep -rn "\.execute()" src/mcp_gee_sweet/tools/`).
47
47
  - Manual smoke test: 5 concurrent `sync_folder` uploads (each artificially delayed 0.2s in the mock) completed in ~0.22s total, not the ~1.0s a serial implementation would take — confirms real concurrency, not just non-blocking syntax.
48
- - Live QA: see `docs/qa/tests/infra.md` TC-I24 and the six new per-tool cases (`docs/qa/tests/drive.md` TC-D176–179, `docs/qa/tests/sheets_read.md` TC-R36–37) — each forces enough distinct, identifiable concurrent items to catch cross-item attribution corruption under the real Drive/Sheets transport, which no mock can exercise.
48
+ - Live QA: see `docs/qa/tests/infra.md` TC-I24 and the six new per-tool cases: TC-D176/177 (`docs/qa/tests/drive_sharing.md`), TC-D178 (`drive_transfer.md`), TC-D179 (`drive_files.md`), and TC-R36–37 (`sheets_read.md`). The four TC-D cases were written into a single `drive.md` that PR #582 later split by submodule, which is why they now live in three files. Each case forces enough distinct, identifiable concurrent items to catch cross-item attribution corruption under the real Drive/Sheets transport, which no mock can exercise.
49
49
 
50
50
  ## Round 2 — QA findings from PR #293 review (2026-07-13)
51
51
 
@@ -35,6 +35,20 @@ Its existing rule — remove any worktree whose branch has a merged PR and is cl
35
35
 
36
36
  Decision 4 rejected a `lane:a`/`lane:b` label for QA-finds-Dev's-PR routing — the branch name already resolves that unambiguously at runtime. This addendum adds `lane-a`/`lane-b` labels for a different problem: an *idle* Dev slot picking its *next* ticket, before any branch exists. `dev.md`'s pickup query previously took the lowest-numbered `ready-for-development` issue with no lane awareness, which meant pre-queuing both lanes' next tickets at once (an established, previously-safe pattern) could misroute if a lane happened to free up before its intended ticket, e.g. Jay grabbing a lower-numbered ticket meant for Ash. `dev.md` now filters its pickup query by the matching lane label, and Kai always pairs `ready-for-development` with the correct lane label when queuing — see `.claude/team-roles/kai.md`, `.claude/team-roles/dev.md`.
37
37
 
38
+ ## Addendum (2026-09-29): per-role MCP configs for `make team-<name>` (#850)
39
+
40
+ Decision 3 still holds for Agent View: a session spawned from `make claude-team` sees every server. But `make team-<name>` / `make lane-a|lane-b` launch each role directly, so those can be scoped. `scripts/write_team_mcp_configs.sh` (called by `setup_team.sh`) now also writes `.claude/mcp-configs/<name>.mcp.json` with just the servers that role may call: Ash/Jay their own, Sky/Kit their own plus `playwright`, Amy/Joy/Bob none. The Makefile targets pass it with `--mcp-config ... --strict-mcp-config`. `team-kai`, `team-aziz`, and `claude-team` are unchanged: Kai and a release QA pass need every team server *and* the user-global `mcp-gee-sweet` (the Gmail cases read the sender mailbox through it), and `--strict-mcp-config` drops user-scope servers.
41
+
42
+ Measured with `claude -p --output-format json --strict-mcp-config` from the `jay` worktree, first-turn context (input + cache read + cache write): all team servers 68,227 tokens; `mcp-gee-sweet-jay` only 42,559; no servers 39,068. About 25.7k saved per lane session, before counting the user-global servers `--strict-mcp-config` also drops.
43
+
44
+ Why a launch flag and not the per-worktree `.mcp.json` #850 first proposed. Both checked live on Claude Code 2.1.285:
45
+
46
+ - A session keeps the MCP servers of the directory it was launched in. `EnterWorktree` into a worktree with a different `.mcp.json` added no servers. `make team-<name>` launches from the repo root, so a worktree's own `.mcp.json` would never load.
47
+ - A session launched *inside* a worktree loads both that worktree's `.mcp.json` and the repo root's, so the root's full config would leak in regardless.
48
+ - `claude --bg ... --mcp-config <file> --strict-mcp-config` is honored: that session saw only `mcp-gee-sweet-jay`, not the root/worktree `.mcp.json` servers or the user-global ones. The flags go after the prompt, since `--mcp-config` (like `--allowedTools`) takes a variadic list and swallows a prompt that follows it.
49
+
50
+ The root and per-worktree `.mcp.json` copies stay the combined config, so Agent View spawns behave as before.
51
+
38
52
  ## When to Re-evaluate
39
53
 
40
54
  - If the shared-MCP-config tool boundary (decision 3) turns out to be leaky in practice (an agent calling another role's tools by mistake), look at per-agent permission allow-lists rather than reintroducing per-role processes — Agent View's constraints haven't changed, so separate processes still won't fit the workflow.
@@ -11,3 +11,4 @@ These are more granular than the [decision records](../decisions/index.md), whic
11
11
  | [Native Markdown/HTML Image Support](image-conversion.md) | 2026-08-02 | `Image` as a zero-width AST node vs. a marker-based alternative, the combined table+image descending-position insertion pass, three-source-kind resolution + share/revoke lifecycle, the retry-on-image-failure fix, and the deliberate table-cell-image gap |
12
12
  | [Borderless-Table Columns](borderless-table-columns.md) | 2026-08-02 | Form-style column alignment via a zero-padding table, once `tabStops` was found read-only (#404) — border-suppression step flagged unverified pending live check |
13
13
  | [Blockquote Representation](blockquote-representation.md) | 2026-08-06 | Flat `blockquote_depth` field (mirroring `BulletItem.depth`) vs. a wrapper node; left border + scaled indent via `paragraphStyle.borderLeft`, live-verified writable before implementing |
14
+ | [Module Implementation History](module-history.md) | 2026-09-29 | Per-PR history behind root `CLAUDE.md`'s module and workflow rules, moved there verbatim by #849 so `CLAUDE.md` keeps only lasting rules and invariants |