crossby 0.2.4__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 (232) hide show
  1. crossby-0.2.4/.claude/settings.local.json +67 -0
  2. crossby-0.2.4/.claude/skills/.gitignore +6 -0
  3. crossby-0.2.4/.gitignore +26 -0
  4. crossby-0.2.4/.wade/.gitignore +1 -0
  5. crossby-0.2.4/.wade/.wade-managed +2 -0
  6. crossby-0.2.4/.wade/wade.db +0 -0
  7. crossby-0.2.4/.wade.yml +51 -0
  8. crossby-0.2.4/CONTRIBUTING.md +258 -0
  9. crossby-0.2.4/LICENSE +21 -0
  10. crossby-0.2.4/PKG-INFO +260 -0
  11. crossby-0.2.4/PUBLISH_PLAN.md +161 -0
  12. crossby-0.2.4/README.md +226 -0
  13. crossby-0.2.4/pyproject.toml +94 -0
  14. crossby-0.2.4/scripts/check-all.sh +9 -0
  15. crossby-0.2.4/scripts/check.sh +24 -0
  16. crossby-0.2.4/scripts/fmt.sh +5 -0
  17. crossby-0.2.4/scripts/probe_models.py +553 -0
  18. crossby-0.2.4/scripts/setup-worktree.sh +20 -0
  19. crossby-0.2.4/scripts/test.sh +5 -0
  20. crossby-0.2.4/src/crossby/__init__.py +3 -0
  21. crossby-0.2.4/src/crossby/ai_tools/__init__.py +17 -0
  22. crossby-0.2.4/src/crossby/ai_tools/antigravity.py +59 -0
  23. crossby-0.2.4/src/crossby/ai_tools/base.py +484 -0
  24. crossby-0.2.4/src/crossby/ai_tools/claude.py +179 -0
  25. crossby-0.2.4/src/crossby/ai_tools/codex.py +89 -0
  26. crossby-0.2.4/src/crossby/ai_tools/copilot.py +96 -0
  27. crossby-0.2.4/src/crossby/ai_tools/cursor.py +157 -0
  28. crossby-0.2.4/src/crossby/ai_tools/gemini.py +73 -0
  29. crossby-0.2.4/src/crossby/ai_tools/model_utils.py +49 -0
  30. crossby-0.2.4/src/crossby/ai_tools/opencode.py +82 -0
  31. crossby-0.2.4/src/crossby/ai_tools/transcript.py +668 -0
  32. crossby-0.2.4/src/crossby/ai_tools/vscode.py +58 -0
  33. crossby-0.2.4/src/crossby/cli/__init__.py +0 -0
  34. crossby-0.2.4/src/crossby/cli/agents.py +134 -0
  35. crossby-0.2.4/src/crossby/cli/convert.py +106 -0
  36. crossby-0.2.4/src/crossby/cli/handoff.py +422 -0
  37. crossby-0.2.4/src/crossby/cli/init.py +450 -0
  38. crossby-0.2.4/src/crossby/cli/launch.py +263 -0
  39. crossby-0.2.4/src/crossby/cli/main.py +180 -0
  40. crossby-0.2.4/src/crossby/cli/stats.py +82 -0
  41. crossby-0.2.4/src/crossby/cli/sync.py +748 -0
  42. crossby-0.2.4/src/crossby/config/__init__.py +17 -0
  43. crossby-0.2.4/src/crossby/config/allowlist_util.py +81 -0
  44. crossby-0.2.4/src/crossby/config/claude_allowlist.py +128 -0
  45. crossby-0.2.4/src/crossby/config/copilot_hooks.py +59 -0
  46. crossby-0.2.4/src/crossby/config/cursor_allowlist.py +109 -0
  47. crossby-0.2.4/src/crossby/config/cursor_hooks.py +55 -0
  48. crossby-0.2.4/src/crossby/config/defaults.py +60 -0
  49. crossby-0.2.4/src/crossby/config/detection.py +273 -0
  50. crossby-0.2.4/src/crossby/config/gemini_hooks.py +47 -0
  51. crossby-0.2.4/src/crossby/config/instructions.py +52 -0
  52. crossby-0.2.4/src/crossby/config/json_utils.py +41 -0
  53. crossby-0.2.4/src/crossby/config/linker.py +76 -0
  54. crossby-0.2.4/src/crossby/config/loader.py +249 -0
  55. crossby-0.2.4/src/crossby/config/skills.py +63 -0
  56. crossby-0.2.4/src/crossby/data/__init__.py +18 -0
  57. crossby-0.2.4/src/crossby/data/models.json +243 -0
  58. crossby-0.2.4/src/crossby/data/prompts/launch_initial_message.md +1 -0
  59. crossby-0.2.4/src/crossby/data/prompts/summarize_cc_compact.md +72 -0
  60. crossby-0.2.4/src/crossby/data/prompts/summarize_default.md +13 -0
  61. crossby-0.2.4/src/crossby/data/skill/SKILL.md +175 -0
  62. crossby-0.2.4/src/crossby/data/skill/__init__.py +0 -0
  63. crossby-0.2.4/src/crossby/data/skill/agents/__init__.py +0 -0
  64. crossby-0.2.4/src/crossby/data/skill/agents/openai.yaml +4 -0
  65. crossby-0.2.4/src/crossby/data/skill/references/__init__.py +0 -0
  66. crossby-0.2.4/src/crossby/data/skill/references/differences.md +108 -0
  67. crossby-0.2.4/src/crossby/handoff/__init__.py +30 -0
  68. crossby-0.2.4/src/crossby/handoff/_utils.py +28 -0
  69. crossby-0.2.4/src/crossby/handoff/models.py +82 -0
  70. crossby-0.2.4/src/crossby/handoff/picker.py +22 -0
  71. crossby-0.2.4/src/crossby/handoff/prompts.py +54 -0
  72. crossby-0.2.4/src/crossby/handoff/readers/__init__.py +7 -0
  73. crossby-0.2.4/src/crossby/handoff/readers/claude.py +210 -0
  74. crossby-0.2.4/src/crossby/handoff/readers/codex.py +202 -0
  75. crossby-0.2.4/src/crossby/handoff/readers/copilot.py +187 -0
  76. crossby-0.2.4/src/crossby/handoff/readers/cursor.py +177 -0
  77. crossby-0.2.4/src/crossby/handoff/summarizer.py +343 -0
  78. crossby-0.2.4/src/crossby/handoff/truncate.py +66 -0
  79. crossby-0.2.4/src/crossby/handoff/writer.py +97 -0
  80. crossby-0.2.4/src/crossby/logging/__init__.py +5 -0
  81. crossby-0.2.4/src/crossby/logging/setup.py +42 -0
  82. crossby-0.2.4/src/crossby/models/__init__.py +0 -0
  83. crossby-0.2.4/src/crossby/models/ai.py +105 -0
  84. crossby-0.2.4/src/crossby/models/config.py +277 -0
  85. crossby-0.2.4/src/crossby/services/__init__.py +0 -0
  86. crossby-0.2.4/src/crossby/services/ai_resolution.py +360 -0
  87. crossby-0.2.4/src/crossby/services/confirm.py +145 -0
  88. crossby-0.2.4/src/crossby/services/handoff_resolution.py +85 -0
  89. crossby-0.2.4/src/crossby/services/prompt_delivery.py +37 -0
  90. crossby-0.2.4/src/crossby/services/sync_resolution.py +74 -0
  91. crossby-0.2.4/src/crossby/subagents/__init__.py +38 -0
  92. crossby-0.2.4/src/crossby/subagents/api.py +61 -0
  93. crossby-0.2.4/src/crossby/subagents/emitters.py +428 -0
  94. crossby-0.2.4/src/crossby/subagents/ir.py +101 -0
  95. crossby-0.2.4/src/crossby/subagents/parsers.py +342 -0
  96. crossby-0.2.4/src/crossby/subagents/tool_map.py +78 -0
  97. crossby-0.2.4/src/crossby/sync/__init__.py +202 -0
  98. crossby-0.2.4/src/crossby/sync/agent_models.py +230 -0
  99. crossby-0.2.4/src/crossby/sync/agents.py +1241 -0
  100. crossby-0.2.4/src/crossby/sync/base.py +136 -0
  101. crossby-0.2.4/src/crossby/sync/file_utils.py +55 -0
  102. crossby-0.2.4/src/crossby/sync/gitignore_utils.py +96 -0
  103. crossby-0.2.4/src/crossby/sync/hooks.py +600 -0
  104. crossby-0.2.4/src/crossby/sync/instruction_markers.py +127 -0
  105. crossby-0.2.4/src/crossby/sync/json_utils.py +83 -0
  106. crossby-0.2.4/src/crossby/sync/manual_fix.py +130 -0
  107. crossby-0.2.4/src/crossby/sync/mcp.py +291 -0
  108. crossby-0.2.4/src/crossby/sync/mcp_discovery.py +137 -0
  109. crossby-0.2.4/src/crossby/sync/mcp_transports.py +160 -0
  110. crossby-0.2.4/src/crossby/sync/permissions.py +433 -0
  111. crossby-0.2.4/src/crossby/sync/plan.py +200 -0
  112. crossby-0.2.4/src/crossby/sync/plugins.py +164 -0
  113. crossby-0.2.4/src/crossby/sync/readers.py +730 -0
  114. crossby-0.2.4/src/crossby/sync/report.py +174 -0
  115. crossby-0.2.4/src/crossby/sync/rules.py +397 -0
  116. crossby-0.2.4/src/crossby/sync/skill_install.py +154 -0
  117. crossby-0.2.4/src/crossby/sync/skills.py +550 -0
  118. crossby-0.2.4/src/crossby/sync/slash_commands.py +156 -0
  119. crossby-0.2.4/src/crossby/sync/translation.py +237 -0
  120. crossby-0.2.4/src/crossby/sync/validate.py +444 -0
  121. crossby-0.2.4/src/crossby/ui/__init__.py +0 -0
  122. crossby-0.2.4/src/crossby/ui/console.py +366 -0
  123. crossby-0.2.4/src/crossby/ui/prompts.py +192 -0
  124. crossby-0.2.4/src/crossby/utils/__init__.py +0 -0
  125. crossby-0.2.4/src/crossby/utils/clipboard.py +43 -0
  126. crossby-0.2.4/src/crossby/utils/process.py +130 -0
  127. crossby-0.2.4/src/crossby/utils/terminal.py +528 -0
  128. crossby-0.2.4/steps.md +213 -0
  129. crossby-0.2.4/tests/__init__.py +0 -0
  130. crossby-0.2.4/tests/conftest.py +0 -0
  131. crossby-0.2.4/tests/fixtures/handoff/claude_empty.jsonl +1 -0
  132. crossby-0.2.4/tests/fixtures/handoff/claude_happy.jsonl +5 -0
  133. crossby-0.2.4/tests/fixtures/handoff/claude_malformed.jsonl +3 -0
  134. crossby-0.2.4/tests/fixtures/handoff/codex_empty.jsonl +1 -0
  135. crossby-0.2.4/tests/fixtures/handoff/codex_happy.jsonl +6 -0
  136. crossby-0.2.4/tests/fixtures/handoff/codex_malformed.jsonl +4 -0
  137. crossby-0.2.4/tests/fixtures/handoff/copilot_events.jsonl +5 -0
  138. crossby-0.2.4/tests/fixtures/handoff/copilot_workspace.yaml +6 -0
  139. crossby-0.2.4/tests/fixtures/handoff/cursor_chat.json +7 -0
  140. crossby-0.2.4/tests/fixtures/handoff/cursor_empty.json +1 -0
  141. crossby-0.2.4/tests/fixtures/handoff/cursor_malformed.json +8 -0
  142. crossby-0.2.4/tests/fixtures/transcripts/claude_session.txt +57 -0
  143. crossby-0.2.4/tests/fixtures/transcripts/codex_session.txt +4 -0
  144. crossby-0.2.4/tests/fixtures/transcripts/copilot_session.txt +48 -0
  145. crossby-0.2.4/tests/fixtures/transcripts/gemini_session.txt +30 -0
  146. crossby-0.2.4/tests/fixtures/transcripts/generic_session.txt +10 -0
  147. crossby-0.2.4/tests/unit/__init__.py +0 -0
  148. crossby-0.2.4/tests/unit/test_ai_tools/__init__.py +0 -0
  149. crossby-0.2.4/tests/unit/test_ai_tools/test_allowed_commands.py +138 -0
  150. crossby-0.2.4/tests/unit/test_ai_tools/test_base_registry.py +243 -0
  151. crossby-0.2.4/tests/unit/test_ai_tools/test_claude_preserve_session.py +162 -0
  152. crossby-0.2.4/tests/unit/test_ai_tools/test_cursor_preserve_session.py +143 -0
  153. crossby-0.2.4/tests/unit/test_ai_tools/test_cursor_resolve_effort_model.py +56 -0
  154. crossby-0.2.4/tests/unit/test_ai_tools/test_model_registry.py +66 -0
  155. crossby-0.2.4/tests/unit/test_ai_tools/test_resume_command.py +85 -0
  156. crossby-0.2.4/tests/unit/test_ai_tools/test_trusted_dirs.py +46 -0
  157. crossby-0.2.4/tests/unit/test_cli/__init__.py +0 -0
  158. crossby-0.2.4/tests/unit/test_cli/test_init.py +299 -0
  159. crossby-0.2.4/tests/unit/test_cli/test_launch.py +515 -0
  160. crossby-0.2.4/tests/unit/test_cli/test_main_menu.py +264 -0
  161. crossby-0.2.4/tests/unit/test_cli/test_sync_cmd.py +682 -0
  162. crossby-0.2.4/tests/unit/test_config/__init__.py +0 -0
  163. crossby-0.2.4/tests/unit/test_config/test_allowlist_reader.py +82 -0
  164. crossby-0.2.4/tests/unit/test_config/test_allowlist_util.py +138 -0
  165. crossby-0.2.4/tests/unit/test_config/test_claude_allowlist.py +238 -0
  166. crossby-0.2.4/tests/unit/test_config/test_configure_plan_hooks.py +364 -0
  167. crossby-0.2.4/tests/unit/test_config/test_configure_worktree_hooks.py +324 -0
  168. crossby-0.2.4/tests/unit/test_config/test_cursor_allowlist.py +168 -0
  169. crossby-0.2.4/tests/unit/test_config/test_defaults.py +31 -0
  170. crossby-0.2.4/tests/unit/test_config/test_detection.py +278 -0
  171. crossby-0.2.4/tests/unit/test_config/test_instructions.py +74 -0
  172. crossby-0.2.4/tests/unit/test_config/test_linker.py +109 -0
  173. crossby-0.2.4/tests/unit/test_config/test_loader.py +277 -0
  174. crossby-0.2.4/tests/unit/test_config/test_pattern_translation.py +165 -0
  175. crossby-0.2.4/tests/unit/test_config/test_shims.py +148 -0
  176. crossby-0.2.4/tests/unit/test_config/test_skills_reader.py +76 -0
  177. crossby-0.2.4/tests/unit/test_handoff/__init__.py +0 -0
  178. crossby-0.2.4/tests/unit/test_handoff/conftest.py +20 -0
  179. crossby-0.2.4/tests/unit/test_handoff/test_e2e_cli.py +491 -0
  180. crossby-0.2.4/tests/unit/test_handoff/test_picker.py +46 -0
  181. crossby-0.2.4/tests/unit/test_handoff/test_prompts.py +54 -0
  182. crossby-0.2.4/tests/unit/test_handoff/test_readers_claude.py +95 -0
  183. crossby-0.2.4/tests/unit/test_handoff/test_readers_codex.py +97 -0
  184. crossby-0.2.4/tests/unit/test_handoff/test_readers_copilot.py +104 -0
  185. crossby-0.2.4/tests/unit/test_handoff/test_readers_cursor.py +99 -0
  186. crossby-0.2.4/tests/unit/test_handoff/test_summarizer.py +277 -0
  187. crossby-0.2.4/tests/unit/test_handoff/test_truncate.py +96 -0
  188. crossby-0.2.4/tests/unit/test_handoff/test_writer.py +126 -0
  189. crossby-0.2.4/tests/unit/test_logging/__init__.py +0 -0
  190. crossby-0.2.4/tests/unit/test_logging/test_setup.py +17 -0
  191. crossby-0.2.4/tests/unit/test_models/__init__.py +0 -0
  192. crossby-0.2.4/tests/unit/test_models/test_ai_models.py +459 -0
  193. crossby-0.2.4/tests/unit/test_models/test_config_models.py +80 -0
  194. crossby-0.2.4/tests/unit/test_services/__init__.py +0 -0
  195. crossby-0.2.4/tests/unit/test_services/test_ai_resolution.py +87 -0
  196. crossby-0.2.4/tests/unit/test_services/test_confirm.py +327 -0
  197. crossby-0.2.4/tests/unit/test_services/test_handoff_resolution.py +93 -0
  198. crossby-0.2.4/tests/unit/test_services/test_sync_resolution.py +77 -0
  199. crossby-0.2.4/tests/unit/test_subagents/__init__.py +0 -0
  200. crossby-0.2.4/tests/unit/test_subagents/test_cli.py +109 -0
  201. crossby-0.2.4/tests/unit/test_subagents/test_emitters.py +165 -0
  202. crossby-0.2.4/tests/unit/test_subagents/test_parsers.py +167 -0
  203. crossby-0.2.4/tests/unit/test_subagents/test_round_trip.py +118 -0
  204. crossby-0.2.4/tests/unit/test_sync/__init__.py +0 -0
  205. crossby-0.2.4/tests/unit/test_sync/test_agent_models.py +143 -0
  206. crossby-0.2.4/tests/unit/test_sync/test_agents.py +1570 -0
  207. crossby-0.2.4/tests/unit/test_sync/test_base.py +135 -0
  208. crossby-0.2.4/tests/unit/test_sync/test_config_rules.py +62 -0
  209. crossby-0.2.4/tests/unit/test_sync/test_gitignore.py +170 -0
  210. crossby-0.2.4/tests/unit/test_sync/test_hooks.py +793 -0
  211. crossby-0.2.4/tests/unit/test_sync/test_instruction_markers.py +102 -0
  212. crossby-0.2.4/tests/unit/test_sync/test_manual_fix.py +149 -0
  213. crossby-0.2.4/tests/unit/test_sync/test_mcp_config.py +65 -0
  214. crossby-0.2.4/tests/unit/test_sync/test_mcp_discovery.py +158 -0
  215. crossby-0.2.4/tests/unit/test_sync/test_mcp_transports.py +149 -0
  216. crossby-0.2.4/tests/unit/test_sync/test_mcp_writers.py +518 -0
  217. crossby-0.2.4/tests/unit/test_sync/test_permissions.py +563 -0
  218. crossby-0.2.4/tests/unit/test_sync/test_plan.py +168 -0
  219. crossby-0.2.4/tests/unit/test_sync/test_plugins.py +109 -0
  220. crossby-0.2.4/tests/unit/test_sync/test_readers.py +263 -0
  221. crossby-0.2.4/tests/unit/test_sync/test_report.py +183 -0
  222. crossby-0.2.4/tests/unit/test_sync/test_rules.py +460 -0
  223. crossby-0.2.4/tests/unit/test_sync/test_run_sync.py +297 -0
  224. crossby-0.2.4/tests/unit/test_sync/test_skill_install.py +82 -0
  225. crossby-0.2.4/tests/unit/test_sync/test_skills.py +798 -0
  226. crossby-0.2.4/tests/unit/test_sync/test_slash_commands.py +131 -0
  227. crossby-0.2.4/tests/unit/test_sync/test_sync_integration.py +134 -0
  228. crossby-0.2.4/tests/unit/test_sync/test_translation.py +183 -0
  229. crossby-0.2.4/tests/unit/test_sync/test_validate.py +250 -0
  230. crossby-0.2.4/tests/unit/test_transcript/__init__.py +0 -0
  231. crossby-0.2.4/tests/unit/test_transcript/test_parser.py +582 -0
  232. crossby-0.2.4/uv.lock +792 -0
@@ -0,0 +1,67 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(git pull:*)",
5
+ "WebFetch(domain:pypi.org)",
6
+ "Bash(wade task:*)",
7
+ "Bash(.venv/bin/python -m pytest tests/ -x -q)",
8
+ "Bash(.venv/bin/python -m pytest tests/unit/test_sync/test_mcp_writers.py::TestCodexMCPWriter -q)",
9
+ "Bash(.venv/bin/python -m pytest tests/ -q --ignore=tests/unit/test_sync/test_mcp_writers.py -k \"not Codex\")",
10
+ "Bash(.venv/bin/python -m pytest tests/ -q -k \"not Codex and not codex\")",
11
+ "Bash(.venv/bin/python -m pytest tests/unit/test_sync/test_sync_integration.py::TestFullSyncMCP::test_syncs_to_all_five_tools -q)",
12
+ "Bash(.venv/bin/python -m pytest tests/ -q -k \"not \\(test_syncs_to_all_five_tools or test_env_vars_preserved_across_all_formats\\)\" --ignore=tests/unit/test_sync/test_mcp_writers.py)",
13
+ "Bash(.venv/bin/python -m ruff check src/ 2>&1)",
14
+ "Bash(.venv/bin/python -m mypy src/crossby/)",
15
+ "Bash(.venv/bin/python -m ruff check src/crossby/config/loader.py --fix 2>&1)",
16
+ "Bash(.venv/bin/python -m ruff check src/crossby/config/loader.py src/crossby/cli/convert.py src/crossby/sync/permissions.py src/crossby/config/detection.py src/crossby/ai_tools/base.py src/crossby/sync/agents.py src/crossby/sync/rules.py 2>&1)",
17
+ "Bash(.venv/bin/python -m pytest tests/unit/test_sync/test_mcp_writers.py -k \"not Codex\" -q)",
18
+ "Bash(git status:*)",
19
+ "Bash(git checkout:*)",
20
+ "Bash(python3 -m pytest tests/ -x -q)",
21
+ "Bash(pip3 list:*)",
22
+ "Bash(uv run:*)",
23
+ "Bash(git add:*)",
24
+ "Bash(gh api:*)",
25
+ "WebSearch",
26
+ "Bash(python -m pytest tests/unit/test_handoff -x)",
27
+ "Bash(git stash *)",
28
+ "Bash(git commit -m ' *)",
29
+ "Bash(./scripts/test.sh tests/unit/test_models/test_ai_models.py::TestPlanModeArgs -v)",
30
+ "Bash(find /Users/ivanviragine/Documents/workspace/crossby/tests -name \"*.py\" -exec grep -l \"launch.*plan\\\\|--plan\" {} \\\\;)",
31
+ "Bash(./scripts/test.sh tests/)",
32
+ "Bash(python3 *)",
33
+ "Bash(find tests *)",
34
+ "Bash(find *)",
35
+ "Bash(python -m pytest tests/unit/test_sync/test_readers.py -x -q)",
36
+ "Bash(uv sync *)",
37
+ "Bash(./scripts/test.sh)",
38
+ "Bash(./scripts/check.sh)",
39
+ "Bash(./scripts/check.sh --types)",
40
+ "Bash(gh pr *)",
41
+ "Read(//tmp/**)",
42
+ "Bash(awk '/^diff --git a\\\\/CONTRIBUTING.md/{found=1} found{print} /^diff --git/ && !/CONTRIBUTING/{if \\(found\\) exit}' /tmp/pr43.diff)",
43
+ "Bash(awk '/^diff --git a\\\\/src\\\\/crossby\\\\/cli\\\\/launch.py/{found=1} found{print} /^diff --git/ && !/launch.py/{if \\(found\\) exit}' /tmp/pr43.diff)",
44
+ "Bash(rtk git *)",
45
+ "Bash(rtk gh *)",
46
+ "Bash(gh repo *)",
47
+ "Bash(rtk grep *)",
48
+ "Bash(bash scripts/check.sh)",
49
+ "Bash(rtk ls *)",
50
+ "Bash(rtk read *)",
51
+ "Bash(env)",
52
+ "Bash(git check-ignore *)",
53
+ "Bash(git rm *)",
54
+ "Bash(./scripts/check-all.sh)",
55
+ "Bash(rm -rf /tmp/crossby-publish-test)",
56
+ "Bash(/tmp/crossby-publish-test/bin/pip install *)",
57
+ "Bash(/tmp/crossby-publish-test/bin/crossby --version)",
58
+ "Bash(/tmp/crossby-publish-test/bin/crossby --help)",
59
+ "Bash(/tmp/crossby-publish-test/bin/crossby sync *)",
60
+ "Bash(rm -rf /tmp/publish-skill-test)",
61
+ "Bash(mkdir -p /tmp/publish-skill-test/.claude)",
62
+ "Bash(/tmp/crossby-publish-test/bin/crossby init *)",
63
+ "Bash(/tmp/crossby-publish-test/bin/crossby convert *)",
64
+ "Bash(uv build *)"
65
+ ]
66
+ }
67
+ }
@@ -0,0 +1,6 @@
1
+ # Wade-managed session skills — installed per worktree, not committed
2
+ implementation-session/
3
+ review-pr-comments-session/
4
+ plan-session/
5
+ deps/
6
+ task/
@@ -0,0 +1,26 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ *.egg
8
+ .eggs/
9
+ *.so
10
+ .venv/
11
+ venv/
12
+ .env
13
+ .mypy_cache/
14
+ .pytest_cache/
15
+ .ruff_cache/
16
+ .coverage
17
+ htmlcov/
18
+ *.log
19
+ .commit-msg
20
+ .crossby.yml
21
+ KNOWLEDGE.md
22
+ .agents/
23
+ # wade-managed symlinks (skills sync)
24
+ .cursor/skills
25
+ .gemini/
26
+ .github/skills
@@ -0,0 +1 @@
1
+ *
@@ -0,0 +1,2 @@
1
+ .wade.yml
2
+ # Managed by wade 0.20.6
Binary file
@@ -0,0 +1,51 @@
1
+ version: 2
2
+ project:
3
+ main_branch: main
4
+ issue_label: feature-plan
5
+ worktrees_dir: ../.worktrees
6
+ branch_prefix: feat
7
+ merge_strategy: PR
8
+ ai:
9
+ default_tool: claude
10
+ default_model: claude-sonnet-4.6
11
+ effort: max
12
+ deps:
13
+ tool: copilot
14
+ model: claude-sonnet-4.6
15
+ mode: headless
16
+ review_plan:
17
+ tool: copilot
18
+ model: claude-sonnet-4.6
19
+ mode: headless
20
+ enabled: true
21
+ review_implementation:
22
+ tool: copilot
23
+ model: claude-sonnet-4.6
24
+ mode: headless
25
+ enabled: true
26
+ plan:
27
+ tool: claude
28
+ model: claude-opus-4.7
29
+ review_batch:
30
+ tool: copilot
31
+ model: claude-sonnet-4.6
32
+ mode: interactive
33
+ enabled: true
34
+ models:
35
+ claude:
36
+ easy: claude-sonnet-4.6
37
+ medium: claude-sonnet-4.6
38
+ complex: claude-opus-4.7
39
+ very_complex: claude-opus-4.7
40
+ provider:
41
+ name: github
42
+ hooks:
43
+ post_worktree_create: scripts/setup-worktree.sh
44
+ copy_to_worktree:
45
+ - .env
46
+ - KNOWLEDGE.md
47
+ - .wade.yml
48
+ - KNOWLEDGE.ratings.yml
49
+ knowledge:
50
+ enabled: true
51
+ path: KNOWLEDGE.md
@@ -0,0 +1,258 @@
1
+ # Contributing to crossby
2
+
3
+ Thanks for your interest in contributing. This document is the maintainer/developer guide — architecture, conventions, and how to extend crossby safely. If you're looking for usage docs, see [README.md](README.md).
4
+
5
+ ## Development Setup
6
+
7
+ Requires Python 3.11+ and [uv](https://docs.astral.sh/uv/).
8
+
9
+ ```bash
10
+ git clone https://github.com/ivanviragine/crossby
11
+ cd crossby
12
+ uv sync --extra dev
13
+ ```
14
+
15
+ ## Running Checks
16
+
17
+ | Command | What it does |
18
+ | ---------------------- | -------------------------------------------- |
19
+ | `./scripts/test.sh` | Run the full test suite |
20
+ | `./scripts/check.sh` | Lint (ruff) + type check (mypy strict) |
21
+ | `./scripts/fmt.sh` | Auto-format with ruff |
22
+ | `./scripts/check-all.sh` | Tests + lint + format check + type check |
23
+
24
+ Run `./scripts/check-all.sh` before opening a PR.
25
+
26
+ `tomli-w` is a base dependency (required for Codex MCP sync), so a plain `uv sync` pulls in everything the test suite needs.
27
+
28
+ ## Architecture
29
+
30
+ ### Directory Layout
31
+
32
+ ```
33
+ src/crossby/
34
+ ├── cli/ # Typer commands (entry point: cli/main.py:cli_main)
35
+ ├── services/ # High-level orchestrators (sync, launch, handoff)
36
+ ├── ai_tools/ # Per-tool adapters (Claude, Copilot, Gemini, Codex, …)
37
+ ├── sync/ # Sync writers — translate and write config per tool
38
+ ├── subagents/ # Subagent format translation (canonical IR + parsers/emitters)
39
+ ├── handoff/ # Session readers, summarizer, prompt loader, handoff writer
40
+ ├── config/ # .crossby.yml loading and Pydantic models
41
+ ├── models/ # Shared data models (AIToolID, capabilities, …)
42
+ ├── data/ # Static model catalog and bundled prompt presets
43
+ ├── ui/ # Rich/questionary UI components
44
+ └── logging/ # structlog configuration
45
+ ```
46
+
47
+ ### Subagent format translation
48
+
49
+ `src/crossby/subagents/` translates a single subagent definition between
50
+ Claude Code, Cursor, Gemini, Copilot, and Codex. The architecture is a
51
+ canonical intermediate representation (`SubagentIR`) with one parser and one
52
+ emitter per tool — no pairwise converters. Tool-specific fields that don't
53
+ generalize live in `SubagentIR.extras` and are only re-emitted when the
54
+ target tool matches the original source.
55
+
56
+ Lossy translations surface as `ConversionWarning(severity=lossy|dropped)`
57
+ rather than silent drops — Cursor (no tool allowlist), Codex (only
58
+ `sandbox_mode`), and Copilot's required `description` are the main offenders.
59
+
60
+ Codex is the asymmetric case: its emitter returns a `CodexEmission`
61
+ containing both the agent `.toml` body and a `[agents.<name>]` fragment for
62
+ `~/.codex/config.toml`. Orchestration features (`/fleet`, `/multitask`,
63
+ Gemini's A2A `kind: remote`, Codex `max_depth`) are out of scope and
64
+ documented as not translatable.
65
+
66
+ CLI: `crossby agents convert --from <tool> --to <tool> <input>`.
67
+
68
+ ### Request Flow
69
+
70
+ ```
71
+ CLI command
72
+ → service (e.g. run_sync)
73
+ → AI tool adapter (AbstractAITool — auto-registered via __init_subclass__)
74
+ → sync writer (AbstractSyncWriter, keyed by (tool_id, concern) in SyncRegistry)
75
+ ```
76
+
77
+ ### Key Concepts
78
+
79
+ - **`AIToolID`** (`models/ai.py`) — a `StrEnum`. Works as both an enum member and a string key.
80
+ - **`AbstractAITool`** (`ai_tools/base.py`) — every adapter subclasses this. Setting the `TOOL_ID` class variable auto-registers the adapter via `__init_subclass__` — no other file needs to change.
81
+ - **`SyncRegistry`** (`sync/base.py`) — maps `(tool_id, concern)` → writer instance. Populated in `sync/__init__.py`; `run_sync()` orchestrates matching writers and collects `SyncResult`s.
82
+ - **`SyncConcern`** — enumeration of what a writer handles: `RULES`, `AGENTS`, `SKILLS`, `PERMISSIONS`, `HOOKS`, `MCP`, `PLUGINS`. `PLUGINS` is detect-only — `run_sync()` injects findings via `sync/plugins.py` after the regular writer pass.
83
+ - **Canonical agent IR** lives in `subagents/` (PR #46): `SubagentIR` plus one parser and one emitter per tool. `sync.agents._sync_translate` / `CodexAgentsWriter` delegate to `subagents.api.convert` for cross-tool translation; `ConversionWarning`s with `severity=lossy|dropped` are turned into `<!-- crossby:manual-fix -->` blocks by `_ir_body_with_manual_fix` before emit so the lossy edge surfaces inside the artifact, not just on the terminal.
84
+ - **Canonical skill model** (`sync/agent_models.py`) — `SkillDefinition` is a tool-neutral dataclass plus `parse_markdown_skill` / `render_markdown_skill` / `translate_skill_for_target`. Skills use the same on-disk SKILL.md shape across every tool today, so the canonical layer exists only to attach manual-fix notes for fields the target tool doesn't honour (Claude `allowed-tools` on non-Claude targets).
85
+ - **Manual-fix block** (`sync/manual_fix.py`) — when a writer can't faithfully translate a source field, it embeds a stable `<!-- crossby:manual-fix:start --> ... <!-- crossby:manual-fix:end -->` block in the rendered file. The block survives markdown rendering, sits inside TOML multi-line strings without escaping, and is replaced 1:1 on re-runs (`strip_manual_fix_blocks` + `append_manual_fix_block` + `find_manual_fix_blocks`).
86
+ - **Cross-provider mappings** (`sync/translation.py`) — Claude↔Codex family table for `model`, family-aware `effort` bias, and `permissionMode` ↔ `sandbox_mode`. Used by both the agents writer and `crossby launch`'s `build_launch_command` for cross-provider model translation.
87
+ - **Pre-write inspection** (`sync/plan.py`, `sync/validate.py`) — `--plan` summarizes a dry-run by concern + manual-fix count; `--doctor` adds validation findings and a coarse `high`/`medium`/`low` readiness rating; `--validate-target` re-parses every synced file (TOML / JSON parseability, agent required fields, skill frontmatter, MCP `command` on PATH, instruction file size).
88
+ - **Persistent reports** (`sync/report.py`) — every real (non-dry-run) sync writes `.crossby/sync-report.md` with a portable `| Status | Item | Notes |` table. Statuses: `Added`, `Check before using`, `Not Added` — driven by `(action, file_path)` rather than message-substring matching.
89
+ - **`.crossby.yml`** is loaded by `config/loader.py` into Pydantic v2 models. **Sync does not depend on it** — it reads each tool's native config directly from standard paths. The config is only consulted by `crossby launch` for defaults.
90
+ - **Symlinks are always relative** (`os.path.relpath`, `config/linker.py`) so they survive repo moves.
91
+ - **Sync is idempotent** — re-running on already-linked files is a no-op. Translate writers hash-compare rendered output before deciding `created` / `updated` / `skipped`.
92
+
93
+ ### Headless vs. interactive launches
94
+
95
+ `build_launch_command` takes two distinct prompt-related parameters:
96
+ - `prompt` — used for batch/headless invocations (one-shot runs, CI), gated by the tool's `headless_flag`.
97
+ - `initial_message` — used for interactive sessions, placed as the first positional arg before any flags.
98
+
99
+ Keep these separate when adding launch logic.
100
+
101
+ ## Adding a New AI Tool
102
+
103
+ The adapter pattern is designed so adding a tool is a single-file change.
104
+
105
+ 1. Add the tool ID to `AIToolID` in `src/crossby/models/ai.py`.
106
+ 2. Create `src/crossby/ai_tools/<tool>.py` subclassing `AbstractAITool`:
107
+ - Set `TOOL_ID = AIToolID.<TOOL>` (this auto-registers the adapter).
108
+ - Implement `capabilities()` returning an `AIToolCapabilities` with at minimum `binary`, `display_name`, `model_flag`, `supports_*` booleans.
109
+ - Override the optional hooks that apply — e.g. `yolo_args()`, `effort_args()`, `trusted_dirs_args()`, `normalize_model_format()`, `resolve_effort_model()`, `initial_message_args()`.
110
+ 3. If the tool should participate in `crossby sync`, add writers under `src/crossby/sync/<concern>.py` for each concern it supports (see below) and register them in `sync/__init__.py`.
111
+ 4. If the tool should be a handoff **source**, override `locate_sessions()` and `read_session()` in the adapter.
112
+ 5. Add static model entries to `src/crossby/data/` if the tool has a known model catalog.
113
+ 6. Add tests under `tests/` — unit tests for the adapter, and integration tests for any sync writers.
114
+
115
+ Adapters are imported via `src/crossby/ai_tools/__init__.py`. Make sure to add your import there so `__init_subclass__` runs.
116
+
117
+ ## Adding a New Sync Writer
118
+
119
+ Sync writers live in `src/crossby/sync/<concern>.py` and subclass `AbstractSyncWriter` (see `sync/base.py`). Each writer:
120
+
121
+ 1. Sets `tool_id: AIToolID` and `concern: SyncConcern`.
122
+ 2. Implements `sync(data, project_root, *, dry_run, force) -> SyncResult`.
123
+ 3. Must be idempotent — re-running on unchanged state should return `action="skipped"` (with `file_path` set when the artifact is already in place; `file_path=None` means "nothing was synced for this concern", which the report renderer maps to `Not Added`).
124
+ 4. Must respect `dry_run` — compute the intended change but make no filesystem writes.
125
+ 5. On write conflicts, honor `force` (backup + overwrite) vs. raising.
126
+
127
+ Register the instance in `src/crossby/sync/__init__.py` alongside the other writers. `SyncRegistry` enforces uniqueness by `(tool_id, concern)`.
128
+
129
+ ### Symlink, copy, or translate
130
+
131
+ Writers that own file-tree concerns (rules, agents, skills) support up to three strategies via `SyncData.<concern>_strategy`:
132
+
133
+ - **`symlink`** (default): create relative symlinks. Cheapest; edits propagate everywhere; only works when source and target use the same on-disk schema.
134
+ - **`copy`**: physical copy with optional per-file rewrite (e.g. translate tool names `Bash`→`Shell` for Cursor). Used when the user wants a real file to commit, or when a marker on the source content would otherwise leak across schemas.
135
+ - **`translate`**: agent writers delegate to `subagents.api.convert(from_tool, to_tool, content)` (canonical `SubagentIR` + per-tool parsers/emitters). Skill writers parse via `SkillDefinition` and call `translate_skill_for_target`. Both attach manual-fix notes for fields the target doesn't honour, render back to the target's on-disk shape, hash-compare for idempotency, and remove stale outputs whose source disappeared.
136
+
137
+ When you add a new writer that handles one of these concerns, decide which strategies it supports, plumb each through `_sync_symlink` / `_sync_copy` / `_sync_translate` (see `agents.py` / `skills.py` for the existing pattern), and add tests for each strategy plus the no-op idempotent case.
138
+
139
+ ### Adding a manual-fix path
140
+
141
+ If your writer translates a field that the target tool may not enforce or understand:
142
+
143
+ 1. Build a `ManualFixNote` with a short `category` (e.g. `permissionMode`, `allowed-tools`) and a user-facing `message`.
144
+ 2. Attach the note via `definition.with_notes([note])` on the canonical model.
145
+ 3. The renderer (`render_markdown_skill`, `render_markdown_agent`, `render_toml_agent`) appends a `<!-- crossby:manual-fix --> … <!-- /crossby:manual-fix -->` block at the bottom — no extra plumbing needed.
146
+
147
+ Keep notes short and literal. Avoid Crossby-internal terminology in the message; users editing the file shouldn't need to know about `SubagentIR` or `SkillDefinition` to act on the note.
148
+
149
+ ## Tool Reference
150
+
151
+ Crossby translates its unified CLI flags into each tool's native syntax. A dash (—) means the tool does not support that feature; crossby raises an error if you pass an explicit flag that the target tool doesn't support (e.g. `--yolo` with OpenCode).
152
+
153
+ ### Launch Flags
154
+
155
+ | Crossby Flag | Claude | Copilot | Gemini | Codex | OpenCode | Cursor | VS Code | Antigravity |
156
+ | ------------- | ---------------------------------- | ----------------- | ---------------------------- | ------------------------------------------ | ----------------- | -------------------------- | ------- | ----------- |
157
+ | Binary | `claude` | `copilot` | `gemini` | `codex` | `opencode` | `agent` | `code` | `antigravity` |
158
+ | `--model` | `--model` | `--model` | `--model` | `--model` | `--model` | `--model` | — | — |
159
+ | `--yolo` | `--dangerously-skip-permissions` | `--yolo` | `--yolo` | `--yolo` | — | `--force` | — | — |
160
+ | `--plan` | `--permission-mode plan` | `--plan` | `--approval-mode plan` | — | — | `--mode plan` | — | — |
161
+ | `--effort` | `--effort <level>` | — | — | `-c model_reasoning_effort="…"` | `--variant <level>` | model suffix (`-thinking`) | — | — |
162
+ | `--prompt` | positional | `-i <prompt>` | positional | positional | `--prompt <prompt>` | positional | — | — |
163
+ | `--transcript`| `script` wrapper | `script` wrapper | `script` wrapper | `script` wrapper | `script` wrapper | `script` wrapper | — | — |
164
+ | `--resume` | `--resume <id>` | `--resume=<id>` | `--resume <id>` | `codex resume <id>` (subcommand) | `-s <id>` | — | — | — |
165
+ | `--trusted-dir` | `--add-dir` | `--add-dir` | `--include-directories` | `--sandbox workspace-write --add-dir` | — | — | — | — |
166
+
167
+ ### Effort Level Mapping
168
+
169
+ | Crossby Level | Claude | Codex | OpenCode | Cursor |
170
+ | ------------- | -------- | ------- | -------- | ------------------- |
171
+ | `low` | `low` | `low` | `low` | — |
172
+ | `medium` | `medium` | `medium`| `medium` | — |
173
+ | `high` | `high` | `high` | `high` | `<model>-thinking` |
174
+ | `max` | `max` | `xhigh` | `high` | `<model>-thinking` |
175
+
176
+ ### Permission & Allowlist Configuration
177
+
178
+ Crossby stores canonical command patterns (e.g. `myapp:*`) and writes them into each tool's native config format.
179
+
180
+ | Feature | Claude | Copilot | Gemini | Cursor |
181
+ | ------------------ | --------------------------- | ------------------------------ | ---------------------- | ----------------------------- |
182
+ | Config file | `.claude/settings.json` | `.github/hooks/hooks.json` | `.gemini/settings.json`| `.cursor/cli.json` |
183
+ | Allowlist format | `Bash(cmd:args)` | `shell(cmd:args)` | `shell(cmd:args)` | `Shell(cmd:args)` |
184
+ | Launch flag | `--allowedTools` | `--allow-tool` | `--allowed-tools` | — (config-file only) |
185
+ | Hook config | `hooks.PreToolUse` | `hooks.preToolUse` | `hooks.BeforeTool` | `preToolUse` in `hooks.json` |
186
+ | Hook guard matcher | `Edit\|Write\|NotebookEdit` | `Write\|Delete` | file-write tools | `Write\|Delete` |
187
+
188
+ ### Session Preservation & Resume
189
+
190
+ | Feature | Claude | Copilot | Gemini | Codex | OpenCode | Cursor |
191
+ | ----------------------- | ----------------------- | ------------------- | --------------- | ------------------ | ---------- | ------------------------ |
192
+ | Resume command | `claude --resume <id>` | `copilot --resume=<id>` | `gemini --resume <id>` | `codex resume <id>` | `opencode -s <id>` | — |
193
+ | Session data path | `~/.claude/projects/` | — | — | — | — | `~/.cursor/projects/` |
194
+ | Session data preserved | Yes (worktree → main) | — | — | — | — | Yes (worktree → main) |
195
+
196
+ Session IDs are extracted automatically from transcripts when `--transcript` is used.
197
+
198
+ ### Transcript Parsing (`crossby stats`)
199
+
200
+ | Feature | Claude | Copilot | Gemini | Codex |
201
+ | ------------------------ | ------ | ------- | ------ | ----- |
202
+ | Total tokens | Yes | Yes | Yes | Yes |
203
+ | Input / output breakdown | Yes | Yes | Yes | Yes |
204
+ | Cached tokens | Yes | Yes | Yes | Yes |
205
+ | Per-model breakdown | — | Yes | Yes | — |
206
+ | Premium requests | — | Yes | — | — |
207
+ | Session ID extraction | Yes | Yes | Yes | Yes |
208
+
209
+ ### Handoff Sources & Targets
210
+
211
+ | Tool | Source (read) | Target (launch) |
212
+ | ------------------------------------- | ---------------------------------------------------- | --------------- |
213
+ | Claude | ✓ (`~/.claude/projects/<encoded>/<id>.jsonl`) | ✓ |
214
+ | Cursor | ✓ (`~/.cursor/projects/<encoded>/chat.json`) | ✓ |
215
+ | Codex | ✓ (`~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl`) | ✓ |
216
+ | Copilot | ✓ (`~/.copilot/session-state/<id>/events.jsonl`) | ✓ |
217
+ | Gemini, OpenCode, Antigravity, VS Code| — | ✓ |
218
+
219
+ ### Library API (not exposed via CLI)
220
+
221
+ Available on each adapter for programmatic use:
222
+
223
+ | Feature | Claude | Copilot | Gemini | Codex | OpenCode | Cursor |
224
+ | ----------------- | ---------------------------------------- | ------------------- | ------------------------ | ------------ | ------------------ | --------------- |
225
+ | Trusted dirs | `--add-dir` | `--add-dir` | `--include-directories` | `--add-dir` | — | — |
226
+ | Structured output | `--output-format json --json-schema …` | — | `--output-format json` | — | — | — |
227
+ | Model format | dashed (`claude-haiku-4-5`) | dotted (`claude-haiku-4.5`) | as-is | as-is | `provider/model` | as-is |
228
+
229
+ ## Commit Conventions
230
+
231
+ This project uses [Conventional Commits](https://www.conventionalcommits.org/):
232
+
233
+ ```
234
+ <type>(<scope>): <short summary>
235
+
236
+ <optional body>
237
+
238
+ <optional footer>
239
+ ```
240
+
241
+ Common types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`.
242
+
243
+ Examples:
244
+ ```
245
+ feat(sync): add allowlist conversion for Gemini
246
+ fix(cli): handle missing .crossby.yml gracefully
247
+ docs: update compatibility table for Codex effort levels
248
+ ```
249
+
250
+ Breaking changes: append `!` after the type (e.g. `feat!:`) and add a `BREAKING CHANGE:` footer.
251
+
252
+ ## Release Process
253
+
254
+ 1. Ensure `./scripts/check-all.sh` passes on `main`.
255
+ 2. Update the version in `pyproject.toml`.
256
+ 3. Commit: `chore: release vX.Y.Z`.
257
+ 4. Tag: `git tag vX.Y.Z`.
258
+ 5. Push both: `git push origin main && git push origin vX.Y.Z`.
crossby-0.2.4/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Ivan Viragine
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.