god-code 0.5.0__tar.gz → 0.5.1__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 (159) hide show
  1. god_code-0.5.1/CLAUDE.md +196 -0
  2. {god_code-0.5.0 → god_code-0.5.1}/PKG-INFO +1 -1
  3. {god_code-0.5.0 → god_code-0.5.1}/README.md +13 -8
  4. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/agents/dispatcher.py +2 -1
  5. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/cli.py +135 -88
  6. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/prompts/assembler.py +5 -0
  7. god_code-0.5.1/godot_agent/prompts/skill_library.py +130 -0
  8. god_code-0.5.1/godot_agent/prompts/skill_selector.py +80 -0
  9. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/runtime/engine.py +28 -6
  10. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/tui/input_handler.py +10 -3
  11. {god_code-0.5.0 → god_code-0.5.1}/pyproject.toml +1 -1
  12. {god_code-0.5.0 → god_code-0.5.1}/tests/prompts/test_prompt_assembler.py +14 -0
  13. god_code-0.5.1/tests/prompts/test_skill_selector.py +30 -0
  14. {god_code-0.5.0 → god_code-0.5.1}/tests/runtime/test_engine.py +51 -0
  15. god_code-0.5.1/tests/test_runtime_switch_commands.py +153 -0
  16. god_code-0.5.1/tests/tui/test_input_handler.py +125 -0
  17. god_code-0.5.0/CLAUDE.md +0 -215
  18. god_code-0.5.0/tests/test_runtime_switch_commands.py +0 -26
  19. god_code-0.5.0/tests/tui/test_input_handler.py +0 -19
  20. {god_code-0.5.0 → god_code-0.5.1}/.github/workflows/publish.yml +0 -0
  21. {god_code-0.5.0 → god_code-0.5.1}/.gitignore +0 -0
  22. {god_code-0.5.0 → god_code-0.5.1}/AGENTS.md +0 -0
  23. {god_code-0.5.0 → god_code-0.5.1}/CHANGELOG.md +0 -0
  24. {god_code-0.5.0 → god_code-0.5.1}/CONTRIBUTING.md +0 -0
  25. {god_code-0.5.0 → god_code-0.5.1}/LICENSE +0 -0
  26. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/__init__.py +0 -0
  27. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/agents/__init__.py +0 -0
  28. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/agents/configs.py +0 -0
  29. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/agents/results.py +0 -0
  30. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/godot/__init__.py +0 -0
  31. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/godot/collision_planner.py +0 -0
  32. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/godot/consistency_checker.py +0 -0
  33. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/godot/dependency_graph.py +0 -0
  34. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/godot/gdscript_linter.py +0 -0
  35. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/godot/impact_analysis.py +0 -0
  36. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/godot/pattern_advisor.py +0 -0
  37. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/godot/project.py +0 -0
  38. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/godot/resource_validator.py +0 -0
  39. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/godot/scene_parser.py +0 -0
  40. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/godot/scene_writer.py +0 -0
  41. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/godot/tscn_validator.py +0 -0
  42. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/llm/__init__.py +0 -0
  43. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/llm/adapters/__init__.py +0 -0
  44. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/llm/adapters/anthropic.py +0 -0
  45. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/llm/adapters/base.py +0 -0
  46. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/llm/adapters/openai.py +0 -0
  47. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/llm/client.py +0 -0
  48. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/llm/streaming.py +0 -0
  49. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/llm/types.py +0 -0
  50. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/llm/vision.py +0 -0
  51. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/prompts/__init__.py +0 -0
  52. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/prompts/build_discipline.py +0 -0
  53. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/prompts/godot_playbook.py +0 -0
  54. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/prompts/image_templates.py +0 -0
  55. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/prompts/knowledge_selector.py +0 -0
  56. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/prompts/system.py +0 -0
  57. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/py.typed +0 -0
  58. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/runtime/__init__.py +0 -0
  59. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/runtime/auth.py +0 -0
  60. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/runtime/config.py +0 -0
  61. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/runtime/context_manager.py +0 -0
  62. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/runtime/design_memory.py +0 -0
  63. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/runtime/error_loop.py +0 -0
  64. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/runtime/events.py +0 -0
  65. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/runtime/gameplay_reviewer.py +0 -0
  66. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/runtime/modes.py +0 -0
  67. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/runtime/oauth.py +0 -0
  68. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/runtime/playtest_harness.py +0 -0
  69. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/runtime/providers.py +0 -0
  70. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/runtime/quality_gate.py +0 -0
  71. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/runtime/reviewer.py +0 -0
  72. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/runtime/runtime_bridge.py +0 -0
  73. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/runtime/scenario_specs/hud_feedback.json +0 -0
  74. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/runtime/scenario_specs/player_movement.json +0 -0
  75. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/runtime/scenario_specs/scene_transition.json +0 -0
  76. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/runtime/session.py +0 -0
  77. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/security/__init__.py +0 -0
  78. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/security/classifier.py +0 -0
  79. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/security/hooks.py +0 -0
  80. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/security/policies.py +0 -0
  81. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/security/protected_paths.py +0 -0
  82. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/security/tool_pipeline.py +0 -0
  83. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/testing/__init__.py +0 -0
  84. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/testing/scenario_runner.py +0 -0
  85. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/tools/__init__.py +0 -0
  86. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/tools/analysis_tools.py +0 -0
  87. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/tools/base.py +0 -0
  88. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/tools/editor_bridge.py +0 -0
  89. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/tools/file_ops.py +0 -0
  90. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/tools/git.py +0 -0
  91. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/tools/godot_cli.py +0 -0
  92. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/tools/image_gen.py +0 -0
  93. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/tools/list_dir.py +0 -0
  94. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/tools/memory_tool.py +0 -0
  95. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/tools/registry.py +0 -0
  96. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/tools/scene_tools.py +0 -0
  97. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/tools/screenshot.py +0 -0
  98. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/tools/script_tools.py +0 -0
  99. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/tools/search.py +0 -0
  100. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/tools/shell.py +0 -0
  101. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/tools/web_search.py +0 -0
  102. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/tui/__init__.py +0 -0
  103. {god_code-0.5.0 → god_code-0.5.1}/godot_agent/tui/display.py +0 -0
  104. {god_code-0.5.0 → god_code-0.5.1}/tests/__init__.py +0 -0
  105. {god_code-0.5.0 → god_code-0.5.1}/tests/agents/test_dispatcher.py +0 -0
  106. {god_code-0.5.0 → god_code-0.5.1}/tests/agents/test_playtest_analyst.py +0 -0
  107. {god_code-0.5.0 → god_code-0.5.1}/tests/e2e/test_planner_worker_reviewer_flow.py +0 -0
  108. {god_code-0.5.0 → god_code-0.5.1}/tests/e2e/test_policy_enforcement.py +0 -0
  109. {god_code-0.5.0 → god_code-0.5.1}/tests/e2e/test_scenario_runner.py +0 -0
  110. {god_code-0.5.0 → god_code-0.5.1}/tests/godot/__init__.py +0 -0
  111. {god_code-0.5.0 → god_code-0.5.1}/tests/godot/test_collision_planner.py +0 -0
  112. {god_code-0.5.0 → god_code-0.5.1}/tests/godot/test_consistency.py +0 -0
  113. {god_code-0.5.0 → god_code-0.5.1}/tests/godot/test_dependency_graph.py +0 -0
  114. {god_code-0.5.0 → god_code-0.5.1}/tests/godot/test_impact_analysis.py +0 -0
  115. {god_code-0.5.0 → god_code-0.5.1}/tests/godot/test_linter.py +0 -0
  116. {god_code-0.5.0 → god_code-0.5.1}/tests/godot/test_pattern_advisor.py +0 -0
  117. {god_code-0.5.0 → god_code-0.5.1}/tests/godot/test_project.py +0 -0
  118. {god_code-0.5.0 → god_code-0.5.1}/tests/godot/test_resource_validator.py +0 -0
  119. {god_code-0.5.0 → god_code-0.5.1}/tests/godot/test_scene_parser.py +0 -0
  120. {god_code-0.5.0 → god_code-0.5.1}/tests/godot/test_scene_writer.py +0 -0
  121. {god_code-0.5.0 → god_code-0.5.1}/tests/godot/test_tscn_validator.py +0 -0
  122. {god_code-0.5.0 → god_code-0.5.1}/tests/llm/__init__.py +0 -0
  123. {god_code-0.5.0 → god_code-0.5.1}/tests/llm/test_adapters.py +0 -0
  124. {god_code-0.5.0 → god_code-0.5.1}/tests/llm/test_client.py +0 -0
  125. {god_code-0.5.0 → god_code-0.5.1}/tests/llm/test_vision.py +0 -0
  126. {god_code-0.5.0 → god_code-0.5.1}/tests/prompts/__init__.py +0 -0
  127. {god_code-0.5.0 → god_code-0.5.1}/tests/prompts/test_knowledge_selector.py +0 -0
  128. {god_code-0.5.0 → god_code-0.5.1}/tests/prompts/test_system_prompt.py +0 -0
  129. {god_code-0.5.0 → god_code-0.5.1}/tests/runtime/__init__.py +0 -0
  130. {god_code-0.5.0 → god_code-0.5.1}/tests/runtime/test_config.py +0 -0
  131. {god_code-0.5.0 → god_code-0.5.1}/tests/runtime/test_context_manager.py +0 -0
  132. {god_code-0.5.0 → god_code-0.5.1}/tests/runtime/test_design_memory.py +0 -0
  133. {god_code-0.5.0 → god_code-0.5.1}/tests/runtime/test_error_loop.py +0 -0
  134. {god_code-0.5.0 → god_code-0.5.1}/tests/runtime/test_gameplay_reviewer.py +0 -0
  135. {god_code-0.5.0 → god_code-0.5.1}/tests/runtime/test_mode_restrictions.py +0 -0
  136. {god_code-0.5.0 → god_code-0.5.1}/tests/runtime/test_multi_agent_flow.py +0 -0
  137. {god_code-0.5.0 → god_code-0.5.1}/tests/runtime/test_playtest_harness.py +0 -0
  138. {god_code-0.5.0 → god_code-0.5.1}/tests/runtime/test_quality_gate.py +0 -0
  139. {god_code-0.5.0 → god_code-0.5.1}/tests/runtime/test_reviewer.py +0 -0
  140. {god_code-0.5.0 → god_code-0.5.1}/tests/runtime/test_runtime_bridge.py +0 -0
  141. {god_code-0.5.0 → god_code-0.5.1}/tests/runtime/test_session.py +0 -0
  142. {god_code-0.5.0 → god_code-0.5.1}/tests/security/test_classifier.py +0 -0
  143. {god_code-0.5.0 → god_code-0.5.1}/tests/security/test_hooks.py +0 -0
  144. {god_code-0.5.0 → god_code-0.5.1}/tests/security/test_permissions.py +0 -0
  145. {god_code-0.5.0 → god_code-0.5.1}/tests/security/test_tool_pipeline.py +0 -0
  146. {god_code-0.5.0 → god_code-0.5.1}/tests/test_e2e.py +0 -0
  147. {god_code-0.5.0 → god_code-0.5.1}/tests/tools/__init__.py +0 -0
  148. {god_code-0.5.0 → god_code-0.5.1}/tests/tools/test_analysis_tools.py +0 -0
  149. {god_code-0.5.0 → god_code-0.5.1}/tests/tools/test_editor_bridge.py +0 -0
  150. {god_code-0.5.0 → god_code-0.5.1}/tests/tools/test_file_ops.py +0 -0
  151. {god_code-0.5.0 → god_code-0.5.1}/tests/tools/test_git.py +0 -0
  152. {god_code-0.5.0 → god_code-0.5.1}/tests/tools/test_godot_cli.py +0 -0
  153. {god_code-0.5.0 → god_code-0.5.1}/tests/tools/test_list_dir.py +0 -0
  154. {god_code-0.5.0 → god_code-0.5.1}/tests/tools/test_memory_tool.py +0 -0
  155. {god_code-0.5.0 → god_code-0.5.1}/tests/tools/test_registry.py +0 -0
  156. {god_code-0.5.0 → god_code-0.5.1}/tests/tools/test_scene_tools.py +0 -0
  157. {god_code-0.5.0 → god_code-0.5.1}/tests/tools/test_script_tools.py +0 -0
  158. {god_code-0.5.0 → god_code-0.5.1}/tests/tools/test_search.py +0 -0
  159. {god_code-0.5.0 → god_code-0.5.1}/tests/tools/test_shell.py +0 -0
@@ -0,0 +1,196 @@
1
+ # CLAUDE.md — God Code Development Guide
2
+
3
+ > AI coding agent specialized for Godot 4.4 game development. This file guides Claude Code when working on the god-code codebase itself.
4
+
5
+ ## Project Identity
6
+
7
+ **god-code** — a Python CLI agent that understands GDScript, .tscn scenes, collision layers, and Godot architecture patterns. Multi-provider LLM support with 29 Godot-specific tools, AI sprite generation, and incremental build-and-verify discipline.
8
+
9
+ **PyPI**: `pip install god-code`
10
+ **GitHub**: https://github.com/888wing/god-code
11
+ **License**: GPL-3.0
12
+
13
+ ## Tech Stack
14
+
15
+ - **Language**: Python 3.9+ (uses `from __future__ import annotations` for 3.10+ syntax)
16
+ - **CLI**: click
17
+ - **HTTP**: httpx (async)
18
+ - **Models**: pydantic v2 (tool schemas, config, structured outputs)
19
+ - **Image**: Pillow (screenshot + sprite post-processing)
20
+ - **TUI**: rich (panels, markdown, diff, tables, spinner) + prompt_toolkit (history, autocomplete)
21
+ - **Test**: pytest + pytest-asyncio (383 tests)
22
+ - **Build**: hatchling
23
+ - **CI**: GitHub Actions (auto-publish to PyPI on tag)
24
+
25
+ ## Current Version: 0.5.1
26
+
27
+ **Stats**: 70 source files, 55 test files, ~11K lines, 383 tests, 29 tools.
28
+
29
+ ## Architecture
30
+
31
+ ```
32
+ godot_agent/
33
+ ├── cli.py # Click CLI + setup wizard + chat loop
34
+ ├── agents/ # Multi-agent system
35
+ │ ├── configs.py # Agent role configurations
36
+ │ ├── dispatcher.py # Planner/worker/reviewer dispatch
37
+ │ └── results.py # Agent result types
38
+ ├── runtime/
39
+ │ ├── engine.py # Conversation loop (tools, streaming, quality gates, review)
40
+ │ ├── config.py # AgentConfig (pydantic) + env overrides
41
+ │ ├── session.py # Session persistence with metadata
42
+ │ ├── oauth.py # Codex refresh token flow
43
+ │ ├── error_loop.py # Godot output parsing + fix suggestions
44
+ │ ├── context_manager.py # Smart compression with working memory (1.05M context)
45
+ │ ├── events.py # Engine event system for TUI
46
+ │ ├── modes.py # Interaction modes (apply/plan/explain/review/fix)
47
+ │ ├── providers.py # Provider detection (openai/anthropic/gemini/xai/openrouter)
48
+ │ ├── quality_gate.py # Post-tool validation pipeline
49
+ │ ├── reviewer.py # Automated code review
50
+ │ ├── playtest_harness.py # Automated gameplay testing
51
+ │ ├── gameplay_reviewer.py # Gameplay quality analysis
52
+ │ ├── runtime_bridge.py # Runtime state snapshots
53
+ │ ├── design_memory.py # Persistent design decisions
54
+ │ └── auth.py # Auth context
55
+ ├── llm/
56
+ │ ├── client.py # LLMClient with retry, content filter handling
57
+ │ ├── types.py # Message, ToolCall, TokenUsage, ChatResponse, LLMConfig
58
+ │ ├── streaming.py # SSE streaming with tool call assembly
59
+ │ ├── vision.py # Image encoding
60
+ │ └── adapters/ # Provider-specific adapters
61
+ │ ├── base.py # Adapter interface
62
+ │ ├── openai.py # OpenAI/OpenRouter adapter
63
+ │ └── anthropic.py # Anthropic adapter
64
+ ├── tools/ # 29 function-calling tools
65
+ │ ├── base.py # BaseTool ABC (strict mode support)
66
+ │ ├── registry.py # ToolRegistry with security pipeline
67
+ │ ├── file_ops.py # read_file, write_file, edit_file (path-contained)
68
+ │ ├── script_tools.py # read_script, edit_script, lint_script
69
+ │ ├── scene_tools.py # read_scene, scene_tree, add/write/remove scene nodes
70
+ │ ├── analysis_tools.py # validate_project, check_consistency, dependency_graph, impact
71
+ │ ├── search.py # grep, glob
72
+ │ ├── list_dir.py # list_dir
73
+ │ ├── git.py # git (shlex-parsed)
74
+ │ ├── shell.py # run_shell (3 safety levels)
75
+ │ ├── godot_cli.py # run_godot (GUT, validate, output parser)
76
+ │ ├── screenshot.py # screenshot_scene (headless)
77
+ │ ├── image_gen.py # generate_sprite (AI pixel art + post-processing)
78
+ │ ├── web_search.py # web_search (Godot docs, web)
79
+ │ ├── memory_tool.py # design memory read/write
80
+ │ └── editor_bridge.py # runtime snapshot, playtest
81
+ ├── godot/ # Godot-specific analysis
82
+ │ ├── project.py # project.godot parser
83
+ │ ├── scene_parser.py # .tscn → TscnScene
84
+ │ ├── scene_writer.py # Structured .tscn modification
85
+ │ ├── tscn_validator.py # Format validation + auto-fix
86
+ │ ├── gdscript_linter.py # Style, naming, type annotations
87
+ │ ├── collision_planner.py # Standard 8-layer scheme
88
+ │ ├── consistency_checker.py # Cross-file checks
89
+ │ ├── dependency_graph.py # Project-wide file graph
90
+ │ ├── pattern_advisor.py # Design pattern suggestions
91
+ │ ├── impact_analysis.py # Change impact analysis
92
+ │ └── resource_validator.py # res:// path checks
93
+ ├── prompts/
94
+ │ ├── system.py # Compatibility wrapper
95
+ │ ├── assembler.py # Full prompt assembly pipeline
96
+ │ ├── godot_playbook.py # 17 knowledge sections
97
+ │ ├── knowledge_selector.py # Context-aware section scoring
98
+ │ ├── skill_library.py # Skill definitions
99
+ │ ├── skill_selector.py # Dynamic skill activation
100
+ │ ├── build_discipline.py # Build-and-verify rules
101
+ │ └── image_templates.py # Pixel art prompt templates
102
+ ├── security/
103
+ │ ├── classifier.py # Tool risk classification
104
+ │ ├── hooks.py # Pre/post execution hooks
105
+ │ ├── policies.py # Execution context + policies
106
+ │ ├── protected_paths.py # Path protection rules
107
+ │ └── tool_pipeline.py # Tool execution pipeline
108
+ ├── testing/
109
+ │ └── scenario_runner.py # Automated test scenarios
110
+ └── tui/
111
+ ├── display.py # Rich TUI components
112
+ └── input_handler.py # prompt_toolkit input + autocomplete
113
+ ```
114
+
115
+ ## Key Patterns
116
+
117
+ ### Tool System
118
+ Every tool inherits `BaseTool`. Supports `strict` mode for GPT-5+ structured outputs. Security pipeline validates path containment and safety level before execution.
119
+
120
+ ### Provider Adapters
121
+ `llm/adapters/` handles provider-specific request/response formats:
122
+ - OpenAI: `max_completion_tokens` for gpt-5+, `max_tokens` for others
123
+ - Anthropic: `thinking` budget for reasoning models
124
+ - Gemini: `reasoning_effort` parameter
125
+
126
+ ### Engine Loop Phases
127
+ ```
128
+ PREPARE_CONTEXT → CALL_MODEL → EXECUTE_TOOLS → RUN_QUALITY_GATE → RUN_REVIEWER → NEXT_ROUND → DONE
129
+ ```
130
+
131
+ ### Context Management (1.05M window)
132
+ Smart compression at 75% (787K tokens):
133
+ 1. Extract working memory (modified files, decisions, errors)
134
+ 2. Keep 20 recent messages intact
135
+ 3. Replace old turns with memory summary
136
+ 4. Tell LLM to re-read files if needed
137
+
138
+ ### Interaction Modes
139
+ - `apply`: Full tool access, write code
140
+ - `plan`: Read-only tools, design first
141
+ - `explain`: Read-only, educational
142
+ - `review`: Read-only, quality analysis
143
+ - `fix`: Full tools, error-focused
144
+
145
+ ## Development Rules
146
+
147
+ ### Adding a New Tool
148
+ 1. Create in `godot_agent/tools/your_tool.py` inheriting `BaseTool`
149
+ 2. Define `Input`/`Output` as pydantic `BaseModel` (all fields must have defaults for strict mode)
150
+ 3. Implement `async execute()`, `is_read_only()`, `is_destructive()`
151
+ 4. Register in `cli.py:build_registry()`
152
+ 5. Add to `prompts/system.py` active_tools list
153
+ 6. Add tests in `tests/tools/`
154
+
155
+ ### Adding a Provider
156
+ 1. Create adapter in `godot_agent/llm/adapters/`
157
+ 2. Register in `runtime/providers.py`
158
+ 3. Add detection rules (model prefix, base_url pattern)
159
+
160
+ ## Config Reference
161
+
162
+ `~/.config/god-code/config.json`:
163
+
164
+ | Field | Default | Description |
165
+ |-------|---------|-------------|
166
+ | api_key | "" | LLM API key |
167
+ | provider | "openai" | openai, anthropic, gemini, xai, openrouter |
168
+ | model | "gpt-5.4" | Model ID |
169
+ | reasoning_effort | "high" | low, medium, high |
170
+ | mode | "apply" | apply, plan, explain, review, fix |
171
+ | language | "en" | en, zh-TW, ja, ko |
172
+ | verbosity | "normal" | concise, normal, detailed |
173
+ | auto_validate | true | Run Godot after file changes |
174
+ | auto_commit | false | Suggest git commit |
175
+ | token_budget | 0 | Max tokens (0=unlimited) |
176
+ | safety | "normal" | strict, normal, permissive |
177
+ | streaming | true | Stream responses |
178
+ | extra_prompt | "" | Custom instructions |
179
+
180
+ ## Testing
181
+
182
+ ```bash
183
+ python -m pytest tests/ -v # Full suite (383 tests)
184
+ python -m pytest tests/tools/ -v # Tool tests only
185
+ python -m pytest tests/e2e/ -v # E2E integration tests
186
+ ```
187
+
188
+ ## Release
189
+
190
+ ```bash
191
+ # Bump version in pyproject.toml + cli.py _VERSION
192
+ git commit -am "release: v0.5.1"
193
+ git tag v0.5.1
194
+ git push && git push --tags
195
+ # → GitHub Actions auto-publishes to PyPI
196
+ ```
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: god-code
3
- Version: 0.5.0
3
+ Version: 0.5.1
4
4
  Summary: AI coding agent specialized for Godot game development
5
5
  Project-URL: Homepage, https://github.com/chuisiufai/god-code
6
6
  Project-URL: Repository, https://github.com/chuisiufai/god-code
@@ -4,15 +4,20 @@ AI coding agent specialized for Godot 4.4 game development. Unlike generic codin
4
4
 
5
5
  ## Features
6
6
 
7
- - **10 tools**: read/write/edit files, grep/glob search, git, shell, Godot headless runner, screenshot capture
8
- - **Workspace-style chat TUI**: session snapshot, recent activity timeline, live tool feedback, and validation visibility
9
- - **Interaction modes**: `apply`, `plan`, `explain`, `review`, and `fix` with mode-aware prompts and tool access
10
- - **Session recovery**: autosave, `/sessions`, `/resume`, and project-aware session metadata
7
+ - **29 tools**: file ops, scene manipulation, script editing, search, git, shell, Godot headless runner, screenshot, AI sprite generation, web search
8
+ - **AI sprite generation**: pixel art pipeline with chroma key removal, auto-crop, nearest-neighbor resize, style presets
9
+ - **Structured Outputs**: strict JSON schemas for gpt-5+ models zero tool call parse errors
10
+ - **Web search**: query Godot docs and web when built-in knowledge isn't enough
11
+ - **Workspace-style chat TUI**: session snapshot, activity timeline, live tool feedback, streaming output
12
+ - **Interaction modes**: `apply`, `plan`, `explain`, `review`, `fix` with mode-aware prompts and tool access
13
+ - **Multi-provider**: OpenAI (gpt-5.4), Anthropic (claude-sonnet-4.6), Google (gemini), xAI (grok), OpenRouter, local models
14
+ - **Session recovery**: autosave, `/sessions`, `/resume`, project-aware session metadata
11
15
  - **Godot-native understanding**: project.godot parser, .tscn scene parser/writer/validator, collision layer planner
12
- - **Code quality**: GDScript linter (naming, ordering, type annotations), cross-file consistency checker, design pattern advisor
13
- - **Smart knowledge injection**: 17 Godot Playbook sections auto-selected by task context
14
- - **Build discipline**: incremental build-and-verify rules enforced via system prompt
15
- - **Vision support**: send screenshots + reference images to multimodal LLMs
16
+ - **Code quality**: GDScript linter, cross-file consistency checker, design pattern advisor, impact analysis
17
+ - **Smart knowledge injection**: 17 Godot Playbook sections auto-selected by task context + skill system
18
+ - **Build discipline**: incremental build-and-verify with quality gates and automated review
19
+ - **Security**: path containment, shell command blocking (3 safety levels), tool execution pipeline
20
+ - **1.05M context window**: smart compression with working memory extraction at 75% threshold
16
21
 
17
22
  ## Install
18
23
 
@@ -85,7 +85,8 @@ class AgentDispatcher:
85
85
  prompt_assembler=prompt_assembler,
86
86
  mode=config.mode,
87
87
  )
88
- engine.allowed_tools = allowed_tools
88
+ engine.base_allowed_tools = set(allowed_tools)
89
+ engine.allowed_tools = set(allowed_tools)
89
90
  return engine
90
91
 
91
92
  async def run_planner(self, task: str) -> AgentTaskResult:
@@ -104,6 +104,50 @@ def _has_meaningful_input(value: str) -> bool:
104
104
  return any(ch.isprintable() and not ch.isspace() for ch in value)
105
105
 
106
106
 
107
+ def _command_argument(value: str, command: str) -> str | None:
108
+ stripped = value.strip()
109
+ if stripped == command:
110
+ return ""
111
+ prefix = f"{command} "
112
+ if not stripped.startswith(prefix):
113
+ return None
114
+ parts = stripped.split(None, 1)
115
+ return parts[1].strip() if len(parts) > 1 else ""
116
+
117
+
118
+ def _set_arguments(value: str) -> tuple[str, str] | None:
119
+ stripped = value.strip()
120
+ if stripped == "/set":
121
+ return None
122
+ if not stripped.startswith("/set "):
123
+ return None
124
+ parts = stripped.split(None, 2)
125
+ if len(parts) != 3:
126
+ return None
127
+ return parts[1], parts[2]
128
+
129
+
130
+ def _cd_argument(value: str) -> str | None:
131
+ for command in ("/cd", "cd"):
132
+ arg = _command_argument(value, command)
133
+ if arg is not None:
134
+ return arg
135
+ return None
136
+
137
+
138
+ def _starts_multiline_input(value: str) -> bool:
139
+ return value.strip().startswith('"""')
140
+
141
+
142
+ def _multiline_initial_fragment(value: str) -> str:
143
+ stripped = value.strip()
144
+ return stripped[3:] if stripped.startswith('"""') else ""
145
+
146
+
147
+ def _is_multiline_terminator(value: str | None) -> bool:
148
+ return value is None or value.strip() == '"""'
149
+
150
+
107
151
  def build_registry() -> ToolRegistry:
108
152
  registry = ToolRegistry()
109
153
  for tool_cls in [
@@ -174,7 +218,8 @@ def build_engine(config: AgentConfig, project_root: Path) -> ConversationEngine:
174
218
  mode=config.mode,
175
219
  dispatcher=dispatcher,
176
220
  )
177
- engine.allowed_tools = allowed_tools
221
+ engine.base_allowed_tools = set(allowed_tools)
222
+ engine.allowed_tools = set(allowed_tools)
178
223
  return engine
179
224
 
180
225
 
@@ -407,7 +452,7 @@ def _run_setup_wizard() -> None:
407
452
  click.echo()
408
453
 
409
454
 
410
- _VERSION = "0.5.0"
455
+ _VERSION = "0.5.1"
411
456
 
412
457
 
413
458
  def _check_update() -> None:
@@ -690,7 +735,7 @@ def chat(project: str = ".", config: str | None = None):
690
735
  if in_multiline:
691
736
  from godot_agent.tui.input_handler import get_multiline_continuation_async
692
737
  line = await get_multiline_continuation_async(input_session)
693
- if line is None or line.strip() == '"""':
738
+ if _is_multiline_terminator(line):
694
739
  in_multiline = False
695
740
  user_input = "\n".join(multiline_buffer)
696
741
  multiline_buffer = []
@@ -702,9 +747,9 @@ def chat(project: str = ".", config: str | None = None):
702
747
  user_input = await get_input_async(input_session, completer, bottom_toolbar=_toolbar())
703
748
  if user_input is None:
704
749
  break
705
- if user_input.strip().startswith('"""'):
750
+ if _starts_multiline_input(user_input):
706
751
  in_multiline = True
707
- rest = user_input.strip()[3:]
752
+ rest = _multiline_initial_fragment(user_input)
708
753
  if rest:
709
754
  multiline_buffer.append(rest)
710
755
  continue
@@ -727,10 +772,9 @@ def chat(project: str = ".", config: str | None = None):
727
772
 
728
773
  if cmd in ("/load", "/resume", "resume"):
729
774
  target = "latest"
730
- elif stripped.startswith("/resume "):
731
- target = stripped.split(None, 1)[1].strip()
732
775
  else:
733
- target = None
776
+ resume_arg = _command_argument(user_input, "/resume")
777
+ target = resume_arg or "latest" if resume_arg is not None else None
734
778
 
735
779
  if target is not None:
736
780
  record = (
@@ -803,13 +847,14 @@ def chat(project: str = ".", config: str | None = None):
803
847
  display.error(f"No project.godot in {project_root}")
804
848
  continue
805
849
 
806
- if cmd == "/mode":
850
+ mode_arg = _command_argument(user_input, "/mode")
851
+ if cmd == "/mode" or mode_arg == "":
807
852
  display.mode_panel(cfg.mode)
808
853
  continue
809
854
 
810
- if stripped.startswith("/mode "):
855
+ if mode_arg:
811
856
  try:
812
- cfg.mode = normalize_mode(stripped.split(None, 1)[1])
857
+ cfg.mode = normalize_mode(mode_arg)
813
858
  except ValueError as e:
814
859
  display.error(str(e))
815
860
  continue
@@ -819,7 +864,8 @@ def chat(project: str = ".", config: str | None = None):
819
864
  _refresh_workspace()
820
865
  continue
821
866
 
822
- if cmd == "/provider":
867
+ provider_arg = _command_argument(user_input, "/provider")
868
+ if cmd == "/provider" or provider_arg == "":
823
869
  display.info_panel({
824
870
  "Provider": cfg.provider,
825
871
  "Base URL": cfg.base_url,
@@ -828,9 +874,9 @@ def chat(project: str = ".", config: str | None = None):
828
874
  })
829
875
  continue
830
876
 
831
- if stripped.startswith("/provider "):
877
+ if provider_arg:
832
878
  try:
833
- provider = _apply_provider_preset(cfg, stripped.split(None, 1)[1])
879
+ provider = _apply_provider_preset(cfg, provider_arg)
834
880
  except ValueError as e:
835
881
  display.error(str(e))
836
882
  continue
@@ -839,7 +885,8 @@ def chat(project: str = ".", config: str | None = None):
839
885
  _refresh_workspace()
840
886
  continue
841
887
 
842
- if cmd == "/model":
888
+ model_arg = _command_argument(user_input, "/model")
889
+ if cmd == "/model" or model_arg == "":
843
890
  display.info_panel({
844
891
  "Provider": cfg.provider,
845
892
  "Model": cfg.model,
@@ -847,10 +894,10 @@ def chat(project: str = ".", config: str | None = None):
847
894
  })
848
895
  continue
849
896
 
850
- if stripped.startswith("/model "):
897
+ if model_arg is not None:
851
898
  previous_provider = cfg.provider
852
899
  previous_base_url = cfg.base_url
853
- cfg.model = stripped.split(None, 1)[1].strip()
900
+ cfg.model = model_arg
854
901
  if not cfg.model:
855
902
  display.error("Usage: /model <name>")
856
903
  continue
@@ -860,7 +907,8 @@ def chat(project: str = ".", config: str | None = None):
860
907
  _refresh_workspace()
861
908
  continue
862
909
 
863
- if cmd == "/effort":
910
+ effort_arg = _command_argument(user_input, "/effort")
911
+ if cmd == "/effort" or effort_arg == "":
864
912
  display.info_panel({
865
913
  "Effort": cfg.reasoning_effort,
866
914
  "Allowed": ", ".join(REASONING_EFFORT_LEVELS),
@@ -868,9 +916,11 @@ def chat(project: str = ".", config: str | None = None):
868
916
  })
869
917
  continue
870
918
 
871
- if stripped.startswith("/effort "):
919
+ if effort_arg is not None:
872
920
  try:
873
- cfg.reasoning_effort = _normalize_reasoning_effort(stripped.split(None, 1)[1])
921
+ if not effort_arg:
922
+ raise ValueError("Usage: /effort <level>")
923
+ cfg.reasoning_effort = _normalize_reasoning_effort(effort_arg)
874
924
  except ValueError as e:
875
925
  display.error(str(e))
876
926
  continue
@@ -910,82 +960,79 @@ def chat(project: str = ".", config: str | None = None):
910
960
  display.settings_panel(cfg)
911
961
  continue
912
962
 
913
- if stripped.startswith("/set "):
914
- parts = stripped.split(None, 2)
915
- if len(parts) == 3:
916
- key, val = parts[1], parts[2]
917
- if hasattr(cfg, key):
918
- old_val = getattr(cfg, key)
919
- try:
920
- if key == "mode":
921
- setattr(cfg, key, normalize_mode(val))
922
- elif key == "provider":
923
- _apply_provider_preset(cfg, val)
924
- elif key == "reasoning_effort":
925
- setattr(cfg, key, _normalize_reasoning_effort(val))
926
- elif key == "model":
927
- previous_provider = cfg.provider
928
- previous_base_url = cfg.base_url
929
- setattr(cfg, key, val)
930
- _sync_provider_from_model(cfg, previous_provider, previous_base_url)
931
- elif isinstance(old_val, bool):
932
- setattr(cfg, key, val.lower() in ("true", "1", "yes", "on"))
933
- elif isinstance(old_val, int):
934
- setattr(cfg, key, int(val))
935
- elif isinstance(old_val, float):
936
- setattr(cfg, key, float(val))
937
- else:
938
- setattr(cfg, key, val)
939
- except ValueError as e:
940
- display.error(str(e))
941
- continue
942
-
943
- display.success(f"{key} = {getattr(cfg, key)}")
944
- if key == "base_url":
945
- cfg.provider = infer_provider(
946
- base_url=cfg.base_url,
947
- model=cfg.model,
948
- provider="",
949
- )
950
-
951
- if key in (
952
- "language",
953
- "verbosity",
954
- "extra_prompt",
955
- "auto_validate",
956
- "mode",
957
- "safety",
958
- "godot_path",
959
- "provider",
960
- "model",
961
- "base_url",
962
- "reasoning_effort",
963
- "api_key",
964
- "oauth_token",
965
- "max_tokens",
966
- "temperature",
967
- ):
968
- await _replace_engine(project_root, preserve_messages=True, rescan_project=False)
969
- display.info("Engine rebuilt with updated settings")
963
+ set_args = _set_arguments(user_input)
964
+ if stripped == "/set" or set_args is not None:
965
+ if set_args is None:
966
+ display.error("Usage: /set <key> <value>")
967
+ continue
968
+ key, val = set_args
969
+ if hasattr(cfg, key):
970
+ old_val = getattr(cfg, key)
971
+ try:
972
+ if key == "mode":
973
+ setattr(cfg, key, normalize_mode(val))
974
+ elif key == "provider":
975
+ _apply_provider_preset(cfg, val)
976
+ elif key == "reasoning_effort":
977
+ setattr(cfg, key, _normalize_reasoning_effort(val))
978
+ elif key == "model":
979
+ previous_provider = cfg.provider
980
+ previous_base_url = cfg.base_url
981
+ setattr(cfg, key, val)
982
+ _sync_provider_from_model(cfg, previous_provider, previous_base_url)
983
+ elif isinstance(old_val, bool):
984
+ setattr(cfg, key, val.lower() in ("true", "1", "yes", "on"))
985
+ elif isinstance(old_val, int):
986
+ setattr(cfg, key, int(val))
987
+ elif isinstance(old_val, float):
988
+ setattr(cfg, key, float(val))
970
989
  else:
971
- _wire_engine_callbacks(engine, display, cfg)
972
- _refresh_workspace()
990
+ setattr(cfg, key, val)
991
+ except ValueError as e:
992
+ display.error(str(e))
993
+ continue
994
+
995
+ display.success(f"{key} = {getattr(cfg, key)}")
996
+ if key == "base_url":
997
+ cfg.provider = infer_provider(
998
+ base_url=cfg.base_url,
999
+ model=cfg.model,
1000
+ provider="",
1001
+ )
1002
+
1003
+ if key in (
1004
+ "language",
1005
+ "verbosity",
1006
+ "extra_prompt",
1007
+ "auto_validate",
1008
+ "mode",
1009
+ "safety",
1010
+ "godot_path",
1011
+ "provider",
1012
+ "model",
1013
+ "base_url",
1014
+ "reasoning_effort",
1015
+ "api_key",
1016
+ "oauth_token",
1017
+ "max_tokens",
1018
+ "temperature",
1019
+ ):
1020
+ await _replace_engine(project_root, preserve_messages=True, rescan_project=False)
1021
+ display.info("Engine rebuilt with updated settings")
973
1022
  else:
974
- display.error(f"Unknown setting: {key}")
1023
+ _wire_engine_callbacks(engine, display, cfg)
1024
+ _refresh_workspace()
975
1025
  else:
976
- display.error("Usage: /set <key> <value>")
1026
+ display.error(f"Unknown setting: {key}")
977
1027
  continue
978
1028
 
979
1029
  # Support both /cd and cd
980
- cd_input = stripped
981
- if cd_input.startswith("/cd "):
982
- cd_input = cd_input[4:]
983
- elif cd_input.startswith("cd "):
984
- cd_input = cd_input[3:]
985
- else:
986
- cd_input = None
1030
+ cd_input = _cd_argument(user_input)
987
1031
 
988
1032
  if cd_input is not None:
1033
+ if not cd_input:
1034
+ display.error("Usage: /cd <path>")
1035
+ continue
989
1036
  new_path = Path(cd_input).expanduser().resolve()
990
1037
  if not new_path.exists():
991
1038
  display.error(f"Path not found: {new_path}")
@@ -9,6 +9,7 @@ from godot_agent.godot.impact_analysis import ImpactAnalysisReport, format_impac
9
9
  from godot_agent.godot.project import parse_project_godot
10
10
  from godot_agent.prompts.build_discipline import BUILD_DISCIPLINE_PROMPT
11
11
  from godot_agent.prompts.knowledge_selector import format_knowledge_injection, select_sections
12
+ from godot_agent.prompts.skill_selector import format_skill_injection, select_skills
12
13
  from godot_agent.runtime.design_memory import DesignMemory, format_design_memory
13
14
  from godot_agent.runtime.modes import mode_prompt
14
15
  from godot_agent.runtime.playtest_harness import PlaytestReport, format_playtest_report
@@ -75,6 +76,10 @@ class PromptAssembler:
75
76
  ) -> str:
76
77
  sections = [self._static_prompt]
77
78
 
79
+ skills = select_skills(user_hint, file_paths, max_skills=2)
80
+ if skills:
81
+ sections.append(format_skill_injection(skills))
82
+
78
83
  knowledge_sections = select_sections(user_hint, file_paths, max_sections=4)
79
84
  if knowledge_sections:
80
85
  sections.append(format_knowledge_injection(knowledge_sections))