fitz-graveyard 0.1.0__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 (76) hide show
  1. fitz_graveyard-0.1.0/LICENSE +21 -0
  2. fitz_graveyard-0.1.0/PKG-INFO +184 -0
  3. fitz_graveyard-0.1.0/README.md +120 -0
  4. fitz_graveyard-0.1.0/fitz_graveyard/__init__.py +8 -0
  5. fitz_graveyard-0.1.0/fitz_graveyard/__main__.py +36 -0
  6. fitz_graveyard-0.1.0/fitz_graveyard/api_review/__init__.py +23 -0
  7. fitz_graveyard-0.1.0/fitz_graveyard/api_review/client.py +142 -0
  8. fitz_graveyard-0.1.0/fitz_graveyard/api_review/cost_calculator.py +165 -0
  9. fitz_graveyard-0.1.0/fitz_graveyard/api_review/schemas.py +118 -0
  10. fitz_graveyard-0.1.0/fitz_graveyard/background/__init__.py +15 -0
  11. fitz_graveyard-0.1.0/fitz_graveyard/background/lifecycle.py +125 -0
  12. fitz_graveyard-0.1.0/fitz_graveyard/background/signals.py +76 -0
  13. fitz_graveyard-0.1.0/fitz_graveyard/background/worker.py +551 -0
  14. fitz_graveyard-0.1.0/fitz_graveyard/cli.py +282 -0
  15. fitz_graveyard-0.1.0/fitz_graveyard/config/__init__.py +21 -0
  16. fitz_graveyard-0.1.0/fitz_graveyard/config/loader.py +53 -0
  17. fitz_graveyard-0.1.0/fitz_graveyard/config/schema.py +149 -0
  18. fitz_graveyard-0.1.0/fitz_graveyard/llm/__init__.py +19 -0
  19. fitz_graveyard-0.1.0/fitz_graveyard/llm/client.py +273 -0
  20. fitz_graveyard-0.1.0/fitz_graveyard/llm/factory.py +32 -0
  21. fitz_graveyard-0.1.0/fitz_graveyard/llm/lm_studio.py +301 -0
  22. fitz_graveyard-0.1.0/fitz_graveyard/llm/memory.py +74 -0
  23. fitz_graveyard-0.1.0/fitz_graveyard/llm/retry.py +55 -0
  24. fitz_graveyard-0.1.0/fitz_graveyard/llm/types.py +22 -0
  25. fitz_graveyard-0.1.0/fitz_graveyard/logging_config.py +58 -0
  26. fitz_graveyard-0.1.0/fitz_graveyard/models/__init__.py +34 -0
  27. fitz_graveyard-0.1.0/fitz_graveyard/models/jobs.py +153 -0
  28. fitz_graveyard-0.1.0/fitz_graveyard/models/responses.py +119 -0
  29. fitz_graveyard-0.1.0/fitz_graveyard/models/schema.py +218 -0
  30. fitz_graveyard-0.1.0/fitz_graveyard/models/sqlite_store.py +327 -0
  31. fitz_graveyard-0.1.0/fitz_graveyard/models/store.py +80 -0
  32. fitz_graveyard-0.1.0/fitz_graveyard/planning/__init__.py +7 -0
  33. fitz_graveyard-0.1.0/fitz_graveyard/planning/agent/__init__.py +6 -0
  34. fitz_graveyard-0.1.0/fitz_graveyard/planning/agent/gatherer.py +181 -0
  35. fitz_graveyard-0.1.0/fitz_graveyard/planning/agent/tools.py +177 -0
  36. fitz_graveyard-0.1.0/fitz_graveyard/planning/confidence/__init__.py +12 -0
  37. fitz_graveyard-0.1.0/fitz_graveyard/planning/confidence/flagging.py +105 -0
  38. fitz_graveyard-0.1.0/fitz_graveyard/planning/confidence/scorer.py +193 -0
  39. fitz_graveyard-0.1.0/fitz_graveyard/planning/pipeline/__init__.py +15 -0
  40. fitz_graveyard-0.1.0/fitz_graveyard/planning/pipeline/checkpoint.py +144 -0
  41. fitz_graveyard-0.1.0/fitz_graveyard/planning/pipeline/orchestrator.py +260 -0
  42. fitz_graveyard-0.1.0/fitz_graveyard/planning/pipeline/output.py +247 -0
  43. fitz_graveyard-0.1.0/fitz_graveyard/planning/pipeline/stages/__init__.py +53 -0
  44. fitz_graveyard-0.1.0/fitz_graveyard/planning/pipeline/stages/architecture.py +163 -0
  45. fitz_graveyard-0.1.0/fitz_graveyard/planning/pipeline/stages/base.py +231 -0
  46. fitz_graveyard-0.1.0/fitz_graveyard/planning/pipeline/stages/context.py +53 -0
  47. fitz_graveyard-0.1.0/fitz_graveyard/planning/pipeline/stages/design.py +74 -0
  48. fitz_graveyard-0.1.0/fitz_graveyard/planning/pipeline/stages/risk.py +90 -0
  49. fitz_graveyard-0.1.0/fitz_graveyard/planning/pipeline/stages/roadmap.py +80 -0
  50. fitz_graveyard-0.1.0/fitz_graveyard/planning/prompts/__init__.py +27 -0
  51. fitz_graveyard-0.1.0/fitz_graveyard/planning/schemas/__init__.py +23 -0
  52. fitz_graveyard-0.1.0/fitz_graveyard/planning/schemas/architecture.py +74 -0
  53. fitz_graveyard-0.1.0/fitz_graveyard/planning/schemas/context.py +44 -0
  54. fitz_graveyard-0.1.0/fitz_graveyard/planning/schemas/design.py +103 -0
  55. fitz_graveyard-0.1.0/fitz_graveyard/planning/schemas/plan_output.py +84 -0
  56. fitz_graveyard-0.1.0/fitz_graveyard/planning/schemas/risk.py +69 -0
  57. fitz_graveyard-0.1.0/fitz_graveyard/planning/schemas/roadmap.py +74 -0
  58. fitz_graveyard-0.1.0/fitz_graveyard/server.py +149 -0
  59. fitz_graveyard-0.1.0/fitz_graveyard/tools/__init__.py +6 -0
  60. fitz_graveyard-0.1.0/fitz_graveyard/tools/cancel_review.py +70 -0
  61. fitz_graveyard-0.1.0/fitz_graveyard/tools/check_status.py +97 -0
  62. fitz_graveyard-0.1.0/fitz_graveyard/tools/confirm_review.py +70 -0
  63. fitz_graveyard-0.1.0/fitz_graveyard/tools/create_plan.py +123 -0
  64. fitz_graveyard-0.1.0/fitz_graveyard/tools/get_plan.py +94 -0
  65. fitz_graveyard-0.1.0/fitz_graveyard/tools/list_plans.py +52 -0
  66. fitz_graveyard-0.1.0/fitz_graveyard/tools/retry_job.py +77 -0
  67. fitz_graveyard-0.1.0/fitz_graveyard/validation/__init__.py +10 -0
  68. fitz_graveyard-0.1.0/fitz_graveyard/validation/sanitize.py +134 -0
  69. fitz_graveyard-0.1.0/fitz_graveyard.egg-info/PKG-INFO +184 -0
  70. fitz_graveyard-0.1.0/fitz_graveyard.egg-info/SOURCES.txt +74 -0
  71. fitz_graveyard-0.1.0/fitz_graveyard.egg-info/dependency_links.txt +1 -0
  72. fitz_graveyard-0.1.0/fitz_graveyard.egg-info/entry_points.txt +2 -0
  73. fitz_graveyard-0.1.0/fitz_graveyard.egg-info/requires.txt +22 -0
  74. fitz_graveyard-0.1.0/fitz_graveyard.egg-info/top_level.txt +1 -0
  75. fitz_graveyard-0.1.0/pyproject.toml +63 -0
  76. fitz_graveyard-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yan Fitzer
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.
@@ -0,0 +1,184 @@
1
+ Metadata-Version: 2.4
2
+ Name: fitz-graveyard
3
+ Version: 0.1.0
4
+ Summary: MCP server for local-first AI architectural planning using local LLMs
5
+ Author-email: Yan Fitzer <yan@example.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Yan Fitzer
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/yafitzdev/fitz-graveyard
29
+ Project-URL: Repository, https://github.com/yafitzdev/fitz-graveyard
30
+ Project-URL: Issues, https://github.com/yafitzdev/fitz-graveyard/issues
31
+ Project-URL: Documentation, https://github.com/yafitzdev/fitz-graveyard#readme
32
+ Keywords: mcp,planning,ai,model-context-protocol,ollama,local-llm
33
+ Classifier: Development Status :: 3 - Alpha
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3.10
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
41
+ Requires-Python: >=3.10
42
+ Description-Content-Type: text/markdown
43
+ License-File: LICENSE
44
+ Requires-Dist: fastmcp>=2.0
45
+ Requires-Dist: pydantic>=2.0
46
+ Requires-Dist: platformdirs>=4.0
47
+ Requires-Dist: pyyaml>=6.0
48
+ Requires-Dist: aiosqlite>=0.20
49
+ Requires-Dist: ollama>=0.4.0
50
+ Requires-Dist: tenacity>=9.0.0
51
+ Requires-Dist: psutil>=6.1.0
52
+ Requires-Dist: typer>=0.9
53
+ Provides-Extra: api-review
54
+ Requires-Dist: anthropic>=0.40; extra == "api-review"
55
+ Requires-Dist: CurrencyConverter>=0.17; extra == "api-review"
56
+ Provides-Extra: lm-studio
57
+ Requires-Dist: openai>=1.0.0; extra == "lm-studio"
58
+ Provides-Extra: dev
59
+ Requires-Dist: pytest>=8.0; extra == "dev"
60
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
61
+ Requires-Dist: build>=1.0; extra == "dev"
62
+ Requires-Dist: twine>=5.0; extra == "dev"
63
+ Dynamic: license-file
64
+
65
+ # fitz-graveyard
66
+
67
+ [![PyPI version](https://img.shields.io/pypi/v/fitz-graveyard.svg)](https://pypi.org/project/fitz-graveyard/)
68
+ [![Python versions](https://img.shields.io/pypi/pyversions/fitz-graveyard.svg)](https://pypi.org/project/fitz-graveyard/)
69
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
70
+ [![Tests](https://github.com/yafitzdev/fitz-graveyard/workflows/tests/badge.svg)](https://github.com/yafitzdev/fitz-graveyard/actions)
71
+
72
+ MCP server for overnight AI architectural planning using local LLMs.
73
+
74
+ ## Features
75
+
76
+ - Local-first planning with Ollama (Qwen Coder Next 80B/32B)
77
+ - Five-stage pipeline: context, architecture, design, roadmap, risk
78
+ - SQLite job queue with crash recovery and checkpoint resume
79
+ - KRAG-powered codebase context via fitz-ai integration
80
+ - Per-section confidence scoring with optional API review
81
+ - Cross-platform: Windows, macOS, Linux
82
+
83
+ ## Prerequisites
84
+
85
+ - Python 3.10+
86
+ - Ollama installed and running ([https://ollama.com](https://ollama.com))
87
+ - Qwen model pulled:
88
+ ```bash
89
+ ollama pull qwen2.5-coder:32b
90
+ ```
91
+
92
+ ## Installation
93
+
94
+ ```bash
95
+ pip install fitz-graveyard
96
+ ```
97
+
98
+ For API review feature:
99
+
100
+ ```bash
101
+ pip install "fitz-graveyard[api-review]"
102
+ ```
103
+
104
+ ## Usage - MCP Server (Claude Code)
105
+
106
+ Add to your `claude_desktop_config.json`:
107
+
108
+ ```json
109
+ {
110
+ "mcpServers": {
111
+ "fitz-planner": {
112
+ "command": "fitz-graveyard"
113
+ }
114
+ }
115
+ }
116
+ ```
117
+
118
+ Restart Claude Desktop, then use planning tools directly in conversations.
119
+
120
+ ## Available Tools
121
+
122
+ | Tool | Description |
123
+ |------|-------------|
124
+ | `create_plan` | Queue a new planning job |
125
+ | `check_status` | Check job progress |
126
+ | `get_plan` | Retrieve completed plan |
127
+ | `list_plans` | List all planning jobs |
128
+ | `retry_job` | Retry a failed job |
129
+ | `confirm_review` | Approve API review after seeing cost |
130
+ | `cancel_review` | Skip API review, finalize plan |
131
+
132
+ ## Configuration
133
+
134
+ Default configuration location:
135
+
136
+ - **Windows**: `%APPDATA%\fitz-graveyard\config.yaml`
137
+ - **macOS**: `~/Library/Application Support/fitz-graveyard/config.yaml`
138
+ - **Linux**: `~/.config/fitz-graveyard/config.yaml`
139
+
140
+ Key settings (YAML):
141
+
142
+ ```yaml
143
+ llm:
144
+ provider: ollama
145
+ model: qwen2.5-coder:32b
146
+ base_url: http://localhost:11434
147
+
148
+ planning:
149
+ confidence_threshold: 0.75
150
+ max_retries: 3
151
+ checkpoint_interval: 60
152
+
153
+ api_review:
154
+ provider: anthropic # Optional: for confidence scoring
155
+ model: claude-opus-4
156
+ enabled: false
157
+ ```
158
+
159
+ ## Development
160
+
161
+ ```bash
162
+ git clone https://github.com/yafitzdev/fitz-graveyard.git
163
+ cd fitz-graveyard
164
+ pip install -e ".[dev]"
165
+ pytest
166
+ ```
167
+
168
+ ## Architecture
169
+
170
+ ```
171
+ fitz_graveyard/
172
+ ├── server.py # MCP server entry point
173
+ ├── engine/ # Planning orchestration
174
+ │ ├── stages/ # Five-stage pipeline
175
+ │ └── checkpoints/ # Crash recovery
176
+ ├── queue/ # SQLite job queue
177
+ ├── context/ # fitz-ai KRAG integration
178
+ ├── review/ # Optional API review
179
+ └── config/ # Configuration management
180
+ ```
181
+
182
+ ## License
183
+
184
+ [MIT](LICENSE)
@@ -0,0 +1,120 @@
1
+ # fitz-graveyard
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/fitz-graveyard.svg)](https://pypi.org/project/fitz-graveyard/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/fitz-graveyard.svg)](https://pypi.org/project/fitz-graveyard/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![Tests](https://github.com/yafitzdev/fitz-graveyard/workflows/tests/badge.svg)](https://github.com/yafitzdev/fitz-graveyard/actions)
7
+
8
+ MCP server for overnight AI architectural planning using local LLMs.
9
+
10
+ ## Features
11
+
12
+ - Local-first planning with Ollama (Qwen Coder Next 80B/32B)
13
+ - Five-stage pipeline: context, architecture, design, roadmap, risk
14
+ - SQLite job queue with crash recovery and checkpoint resume
15
+ - KRAG-powered codebase context via fitz-ai integration
16
+ - Per-section confidence scoring with optional API review
17
+ - Cross-platform: Windows, macOS, Linux
18
+
19
+ ## Prerequisites
20
+
21
+ - Python 3.10+
22
+ - Ollama installed and running ([https://ollama.com](https://ollama.com))
23
+ - Qwen model pulled:
24
+ ```bash
25
+ ollama pull qwen2.5-coder:32b
26
+ ```
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install fitz-graveyard
32
+ ```
33
+
34
+ For API review feature:
35
+
36
+ ```bash
37
+ pip install "fitz-graveyard[api-review]"
38
+ ```
39
+
40
+ ## Usage - MCP Server (Claude Code)
41
+
42
+ Add to your `claude_desktop_config.json`:
43
+
44
+ ```json
45
+ {
46
+ "mcpServers": {
47
+ "fitz-planner": {
48
+ "command": "fitz-graveyard"
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ Restart Claude Desktop, then use planning tools directly in conversations.
55
+
56
+ ## Available Tools
57
+
58
+ | Tool | Description |
59
+ |------|-------------|
60
+ | `create_plan` | Queue a new planning job |
61
+ | `check_status` | Check job progress |
62
+ | `get_plan` | Retrieve completed plan |
63
+ | `list_plans` | List all planning jobs |
64
+ | `retry_job` | Retry a failed job |
65
+ | `confirm_review` | Approve API review after seeing cost |
66
+ | `cancel_review` | Skip API review, finalize plan |
67
+
68
+ ## Configuration
69
+
70
+ Default configuration location:
71
+
72
+ - **Windows**: `%APPDATA%\fitz-graveyard\config.yaml`
73
+ - **macOS**: `~/Library/Application Support/fitz-graveyard/config.yaml`
74
+ - **Linux**: `~/.config/fitz-graveyard/config.yaml`
75
+
76
+ Key settings (YAML):
77
+
78
+ ```yaml
79
+ llm:
80
+ provider: ollama
81
+ model: qwen2.5-coder:32b
82
+ base_url: http://localhost:11434
83
+
84
+ planning:
85
+ confidence_threshold: 0.75
86
+ max_retries: 3
87
+ checkpoint_interval: 60
88
+
89
+ api_review:
90
+ provider: anthropic # Optional: for confidence scoring
91
+ model: claude-opus-4
92
+ enabled: false
93
+ ```
94
+
95
+ ## Development
96
+
97
+ ```bash
98
+ git clone https://github.com/yafitzdev/fitz-graveyard.git
99
+ cd fitz-graveyard
100
+ pip install -e ".[dev]"
101
+ pytest
102
+ ```
103
+
104
+ ## Architecture
105
+
106
+ ```
107
+ fitz_graveyard/
108
+ ├── server.py # MCP server entry point
109
+ ├── engine/ # Planning orchestration
110
+ │ ├── stages/ # Five-stage pipeline
111
+ │ └── checkpoints/ # Crash recovery
112
+ ├── queue/ # SQLite job queue
113
+ ├── context/ # fitz-ai KRAG integration
114
+ ├── review/ # Optional API review
115
+ └── config/ # Configuration management
116
+ ```
117
+
118
+ ## License
119
+
120
+ [MIT](LICENSE)
@@ -0,0 +1,8 @@
1
+ # fitz_graveyard/__init__.py
2
+ """
3
+ fitz-graveyard: MCP server for local-first AI planning with fitz-ai integration.
4
+
5
+ Provides tools for analyzing projects, generating plans, and managing async planning jobs.
6
+ """
7
+
8
+ __version__ = "0.1.0"
@@ -0,0 +1,36 @@
1
+ # fitz_graveyard/__main__.py
2
+ """
3
+ Entry point for fitz-graveyard MCP server.
4
+
5
+ CRITICAL: Server imports configure_logging() first to prevent stdout pollution.
6
+
7
+ This module coordinates lifecycle initialization before starting the MCP server.
8
+ FastMCP doesn't have built-in lifecycle hooks, so we handle it manually.
9
+ """
10
+
11
+ import asyncio
12
+ import logging
13
+
14
+ # Import server (which configures logging before anything else)
15
+ from fitz_graveyard.server import initialize_lifecycle, mcp
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ async def main() -> None:
21
+ """
22
+ Main entry point.
23
+
24
+ Initializes lifecycle (DB + worker + signals) and then runs MCP server.
25
+ """
26
+ # Initialize lifecycle first
27
+ await initialize_lifecycle()
28
+
29
+ # Now run the MCP server via stdio transport
30
+ # Note: mcp.run() is synchronous and blocks, so this must be the last call
31
+ logger.info("Starting MCP server on stdio transport")
32
+ await mcp.run_stdio_async()
33
+
34
+
35
+ if __name__ == "__main__":
36
+ asyncio.run(main())
@@ -0,0 +1,23 @@
1
+ # fitz_graveyard/api_review/__init__.py
2
+ """
3
+ API review subsystem for optional Anthropic-based plan section review.
4
+
5
+ Provides:
6
+ - Cost estimation using Anthropic's count_tokens API
7
+ - Async review execution with concurrent section processing
8
+ - Token usage tracking and USD/EUR cost breakdown
9
+ """
10
+
11
+ from .client import AnthropicReviewClient
12
+ from .cost_calculator import CostCalculator
13
+ from .schemas import CostBreakdown, CostEstimate, ReviewRequest, ReviewResult, build_review_prompt
14
+
15
+ __all__ = [
16
+ "AnthropicReviewClient",
17
+ "CostCalculator",
18
+ "CostEstimate",
19
+ "ReviewResult",
20
+ "ReviewRequest",
21
+ "CostBreakdown",
22
+ "build_review_prompt",
23
+ ]
@@ -0,0 +1,142 @@
1
+ # fitz_graveyard/api_review/client.py
2
+ """
3
+ Anthropic review client for async section review.
4
+
5
+ Uses AsyncAnthropic for concurrent review of multiple plan sections.
6
+ """
7
+
8
+ import asyncio
9
+ import logging
10
+ from typing import TYPE_CHECKING
11
+
12
+ from .cost_calculator import CostCalculator
13
+ from .schemas import ReviewRequest, ReviewResult, build_review_prompt
14
+
15
+ if TYPE_CHECKING:
16
+ import anthropic
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class AnthropicReviewClient:
22
+ """Async client for reviewing plan sections using Anthropic API."""
23
+
24
+ SYSTEM_PROMPT = (
25
+ "You are an expert technical reviewer for software project plans. "
26
+ "Provide actionable, substantive feedback that helps improve plan quality."
27
+ )
28
+
29
+ def __init__(self, api_key: str, model: str = "claude-sonnet-4-5-20250929"):
30
+ """
31
+ Initialize Anthropic review client.
32
+
33
+ Args:
34
+ api_key: Anthropic API key
35
+ model: Model to use for review (default: Claude Sonnet 4.5)
36
+ """
37
+ self._api_key = api_key
38
+ self._model = model
39
+ self._client: "anthropic.AsyncAnthropic | None" = None
40
+
41
+ @property
42
+ def client(self) -> "anthropic.AsyncAnthropic":
43
+ """Lazy-loaded Anthropic client."""
44
+ if self._client is None:
45
+ import anthropic
46
+
47
+ self._client = anthropic.AsyncAnthropic(api_key=self._api_key)
48
+ return self._client
49
+
50
+ async def review_sections(self, sections: list[ReviewRequest]) -> list[ReviewResult]:
51
+ """
52
+ Review multiple sections concurrently.
53
+
54
+ Args:
55
+ sections: Sections to review
56
+
57
+ Returns:
58
+ Review results (one per section, including failures)
59
+
60
+ Note:
61
+ Each section is reviewed independently. Failures in one section
62
+ do not affect others.
63
+ """
64
+ # Review all sections concurrently
65
+ review_tasks = [self._review_single_section(section) for section in sections]
66
+ results = await asyncio.gather(*review_tasks, return_exceptions=True)
67
+
68
+ # Convert exceptions to ReviewResult with error
69
+ processed_results = []
70
+ for i, result in enumerate(results):
71
+ if isinstance(result, Exception):
72
+ logger.error(
73
+ f"Review failed for section '{sections[i].section_name}': {result}",
74
+ exc_info=result,
75
+ )
76
+ processed_results.append(
77
+ ReviewResult(
78
+ section_name=sections[i].section_name,
79
+ success=False,
80
+ error=str(result),
81
+ )
82
+ )
83
+ else:
84
+ processed_results.append(result)
85
+
86
+ return processed_results
87
+
88
+ async def _review_single_section(self, section: ReviewRequest) -> ReviewResult:
89
+ """
90
+ Review a single section using Anthropic API.
91
+
92
+ Args:
93
+ section: Section to review
94
+
95
+ Returns:
96
+ Review result with feedback and token usage
97
+
98
+ Raises:
99
+ Exception: On API errors (handled by review_sections)
100
+ """
101
+ try:
102
+ # Build review prompt
103
+ prompt = build_review_prompt(section)
104
+
105
+ # Call Anthropic API
106
+ response = await self.client.messages.create(
107
+ model=self._model,
108
+ max_tokens=2048,
109
+ system=self.SYSTEM_PROMPT,
110
+ messages=[{"role": "user", "content": prompt}],
111
+ )
112
+
113
+ # Extract feedback from response
114
+ feedback = response.content[0].text if response.content else ""
115
+
116
+ return ReviewResult(
117
+ section_name=section.section_name,
118
+ success=True,
119
+ feedback=feedback,
120
+ input_tokens=response.usage.input_tokens,
121
+ output_tokens=response.usage.output_tokens,
122
+ )
123
+
124
+ except Exception as e:
125
+ # Log and re-raise for gather() to handle
126
+ logger.warning(f"API call failed for section '{section.section_name}': {e}")
127
+ raise
128
+
129
+ def get_cost_calculator(self) -> CostCalculator:
130
+ """
131
+ Get cost calculator initialized with same client.
132
+
133
+ Returns:
134
+ CostCalculator instance
135
+ """
136
+ return CostCalculator(client=self.client)
137
+
138
+ async def close(self):
139
+ """Close the async client if initialized."""
140
+ if self._client is not None:
141
+ await self._client.close()
142
+ self._client = None