essot-mcp-server 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.
@@ -0,0 +1,31 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ name: Run tests on Python ${{ matrix.python-version }}
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ matrix:
15
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Set up Python
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: ${{ matrix.python-version }}
24
+
25
+ - name: Install dependencies
26
+ run: |
27
+ python -m pip install --upgrade pip
28
+ pip install .[dev]
29
+
30
+ - name: Run pytest
31
+ run: pytest
@@ -0,0 +1,35 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ build-and-publish:
10
+ name: Build distribution and publish to PyPI
11
+ runs-on: ubuntu-latest
12
+ permissions:
13
+ id-token: write # Mandatory for PyPI Trusted Publishing (OIDC)
14
+ contents: read
15
+
16
+ steps:
17
+ - name: Check out repository
18
+ uses: actions/checkout@v4
19
+
20
+ - name: Set up Python
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: "3.11"
24
+
25
+ - name: Install build tool
26
+ run: pip install build
27
+
28
+ - name: Build sdist and wheel
29
+ run: python -m build
30
+
31
+ - name: Publish to PyPI
32
+ uses: pypa/gh-action-pypi-publish@release/v1
33
+ with:
34
+ password: ${{ secrets.PYPI_API_TOKEN }}
35
+ skip-existing: true
@@ -0,0 +1,39 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Environments
7
+ .env
8
+ .venv
9
+ env/
10
+ venv/
11
+ ENV/
12
+ env.bak/
13
+ venv.bak/
14
+
15
+ # Distribution / packaging
16
+ .Python
17
+ build/
18
+ develop-eggs/
19
+ dist/
20
+ downloads/
21
+ eggs/
22
+ .eggs/
23
+ lib/
24
+ lib64/
25
+ parts/
26
+ sdist/
27
+ var/
28
+ wheels/
29
+ share/python-wheels/
30
+ *.egg-info/
31
+ .installed.cfg
32
+ *.egg
33
+ MANIFEST
34
+
35
+ # IDEs
36
+ .idea/
37
+ .vscode/
38
+ *.swp
39
+ *.swo
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.5
2
+ Name: essot-mcp-server
3
+ Version: 0.1.0
4
+ Summary: ESSOT MCP Server for connecting AI IDEs with the Engineering SSOT Platform
5
+ Project-URL: Homepage, https://essot.space
6
+ Project-URL: Repository, https://github.com/eecheonwu/essot-mcp-server
7
+ Project-URL: Issues, https://github.com/eecheonwu/essot-mcp-server/issues
8
+ Author-email: Emmanuel Echeonwu <eecheonwu@aimsammi.org>
9
+ License-Expression: MIT
10
+ Keywords: ai-agents,developer-tools,essot,mcp,model-context-protocol,ssot
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Build Tools
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: fastapi>=0.110.0
22
+ Requires-Dist: fastmcp>=0.4.0
23
+ Requires-Dist: httpx>=0.27.0
24
+ Requires-Dist: mcp>=1.2.0
25
+ Requires-Dist: openai>=1.50.0
26
+ Requires-Dist: pydantic>=2.0.0
27
+ Requires-Dist: python-dotenv>=1.0.0
28
+ Requires-Dist: requests>=2.31.0
29
+ Requires-Dist: uvicorn>=0.29.0
30
+ Requires-Dist: websockets>=12.0
31
+ Provides-Extra: dev
32
+ Requires-Dist: build>=1.2.0; extra == 'dev'
33
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
34
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
35
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
36
+ Requires-Dist: twine>=5.0.0; extra == 'dev'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # ESSOT MCP Server
40
+
41
+ [![Python Version](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
42
+ [![MCP Transport](https://img.shields.io/badge/mcp-stdio%20%7C%20http-green.svg)](https://modelcontextprotocol.io/)
43
+ [![Framework](https://img.shields.io/badge/framework-FastMCP-purple.svg)](https://github.com/jlowin/fastmcp)
44
+
45
+ The **ESSOT MCP Server** is the bridge between your local development environment and the **Engineering SSOT Platform (ESSOT)**. It exposes local workspace orchestration capabilities to your AI-powered IDEs via the Model Context Protocol (MCP).
46
+
47
+ ---
48
+
49
+ ## 🚀 About the Engineering SSOT Platform (ESSOT)
50
+
51
+ The **Engineering SSOT Platform (ESSOT)** is a modern governance and planning workspace designed for AI-driven software engineering. ESSOT automatically maintains a Single Source of Truth (SSOT), visualizes architectural knowledge graphs, and governs architectural drift.
52
+
53
+ The **ESSOT MCP Server** empowers AI Coding Agents (such as Antigravity, Claude Desktop, Cursor, or Windsurf) to natively interact with your ESSOT project.
54
+
55
+ ### How it Connects to ESSOT
56
+
57
+ This MCP server operates in a **dual-mode architecture**:
58
+
59
+ 1. **Stdio MCP Interface**: Seamlessly exposes AI tools to your local IDE Agent, allowing it to pull architecture guidelines, parse SSOT requirements, and automatically synchronize your code drift with the remote ESSOT Knowledge Graph.
60
+ 2. **Enterprise Outbound Worker Tunnel**: Automatically establishes an encrypted outbound WebSocket connection over standard TLS (Port 443) to the ESSOT Web App. This allows the cloud-based **ESSOT Web App** to dispatch deep architectural tasks (scaffolding SSOT, generating plans, running Forge verifications) directly to your local workspace without opening any inbound firewall ports. (An internal `127.0.0.1:8123` listener is also maintained as a fallback for offline/local development).
61
+
62
+ ---
63
+
64
+ ## 🛠️ Available Tools
65
+
66
+ When connected to your IDE, the AI agent gains access to the following tools:
67
+
68
+ - `initialize_local_ssot`: Connects to the ESSOT Web App to scaffold your local `ssot/` directory using the project's Software Requirements Document (SRD).
69
+ - `generate_implementation_plan`: Acts as a Staff Architect. Analyzes local SSOT context and outputs an exhaustive, step-by-step implementation plan (with embedded security architecture).
70
+ - `generate_task_plan`: Decompiles the implementation plan into highly specific executable tasks for local agents (including integrated security guardrails).
71
+ - `generate_test_plan`: Creates comprehensive testing strategies (STQE, RTM) mapped to feature requirements.
72
+ - `synchronize_ssot`: Pushes local documentation and architectural updates back to the ESSOT Web App's Knowledge Graph to resolve drift.
73
+
74
+ ---
75
+
76
+ ## 📦 Installation & Quickstart
77
+
78
+ No manual repository downloads or cloning required. Once published to PyPI, your AI IDE can run `essot-mcp-server` on demand using `uvx` (or `pipx`).
79
+
80
+ ### 1-Step Setup (Recommended)
81
+
82
+ Add `essot-mcp-server` directly to your IDE's MCP configuration:
83
+
84
+ ```json
85
+ {
86
+ "mcpServers": {
87
+ "essot-mcp-server": {
88
+ "command": "uvx",
89
+ "args": [
90
+ "essot-mcp-server"
91
+ ],
92
+ "env": {
93
+ "ESSOT_API_KEY": "essot_your_api_key_here"
94
+ }
95
+ }
96
+ }
97
+ }
98
+ ```
99
+
100
+ > **Note:** If your ESSOT instance is self-hosted or uses a custom domain, optionally set `"ESSOT_API_URL": "https://your-domain.com"`. By default, it connects to `https://essot.space`.
101
+
102
+ ### Alternative Runners
103
+
104
+ #### Using pipx:
105
+ ```json
106
+ {
107
+ "mcpServers": {
108
+ "essot-mcp-server": {
109
+ "command": "pipx",
110
+ "args": [
111
+ "run",
112
+ "essot-mcp-server"
113
+ ],
114
+ "env": {
115
+ "ESSOT_API_KEY": "essot_your_api_key_here"
116
+ }
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
122
+ #### Traditional pip installation:
123
+ ```bash
124
+ pip install essot-mcp-server
125
+ ```
126
+ ```json
127
+ {
128
+ "mcpServers": {
129
+ "essot-mcp-server": {
130
+ "command": "essot-mcp-server",
131
+ "env": {
132
+ "ESSOT_API_KEY": "essot_your_api_key_here"
133
+ }
134
+ }
135
+ }
136
+ }
137
+ ```
138
+
139
+ ### Supported IDEs & Configuration Locations
140
+
141
+ - **Claude Desktop**: `%APPDATA%\Claude\claude_desktop_config.json` (Windows) or `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS).
142
+ - **Antigravity CLI / IDE**: `~/.gemini/config/mcp_config.json`.
143
+ - **Cursor**: Settings > Features > MCP.
144
+ - **VSCode**: `.vscode/mcp_config.json`.
145
+
146
+
147
+ ---
148
+
149
+ ## 🌐 Permanent Secure Outbound Worker Tunnel (Native IDE Bridge)
150
+
151
+ When connecting to the cloud-hosted ESSOT Web App (`https://essot.space`):
152
+
153
+ 1. **Enterprise Security & Zero Open Ports:**
154
+ The MCP server uses an **outbound-only WebSocket architecture** over standard encrypted TLS (Port 443). Corporate firewalls and NAT permit outbound HTTPS/WSS traffic freely. Once authenticated, a bidirectional full-duplex session is established directly with ESSOT Cloud. No public ingress ports or third-party tunnel relays are required.
155
+
156
+ 2. **Native IDE Setup (1 Step):**
157
+ Generate your `ESSOT_API_KEY` from **User Settings ⚙️** in the ESSOT Web App, and configure it directly in your IDE's MCP config:
158
+
159
+ ```json
160
+ {
161
+ "mcpServers": {
162
+ "essot-mcp-server": {
163
+ "command": "uvx",
164
+ "args": [
165
+ "essot-mcp-server"
166
+ ],
167
+ "env": {
168
+ "ESSOT_API_KEY": "essot_your_api_key_here"
169
+ }
170
+ }
171
+ }
172
+ }
173
+ ```
174
+
175
+ When your IDE opens, the server starts automatically and logs:
176
+
177
+ ```text
178
+ 🟢 Outbound WebSocket tunnel established with ESSOT Cloud. Permanent tunnel active.
179
+ ```
180
+
181
+ Now, clicking **Scaffold Local SSOT** or **Generate Plan** in the Web App, or running Forge verification tests, immediately dispatches tasks to your local workspace without any terminal commands or firewall adjustments.
182
+
183
+ ---
184
+
185
+ ## 🔧 Architecture Requirements
186
+
187
+ - **Python**: `3.10` or higher
188
+ - **Package Manager**: [uv](https://github.com/astral-sh/uv) by Astral
189
+ - **Inbound Ports**: **Zero incoming ports required**. Remote cloud dispatch operates 100% via outbound encrypted WebSocket (TLS Port 443).
190
+
191
+ ## 🤝 Contributing
192
+
193
+ Contributions, issues, and feature requests are welcome! Feel free to check the [issues page](../../issues).
@@ -0,0 +1,155 @@
1
+ # ESSOT MCP Server
2
+
3
+ [![Python Version](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
4
+ [![MCP Transport](https://img.shields.io/badge/mcp-stdio%20%7C%20http-green.svg)](https://modelcontextprotocol.io/)
5
+ [![Framework](https://img.shields.io/badge/framework-FastMCP-purple.svg)](https://github.com/jlowin/fastmcp)
6
+
7
+ The **ESSOT MCP Server** is the bridge between your local development environment and the **Engineering SSOT Platform (ESSOT)**. It exposes local workspace orchestration capabilities to your AI-powered IDEs via the Model Context Protocol (MCP).
8
+
9
+ ---
10
+
11
+ ## 🚀 About the Engineering SSOT Platform (ESSOT)
12
+
13
+ The **Engineering SSOT Platform (ESSOT)** is a modern governance and planning workspace designed for AI-driven software engineering. ESSOT automatically maintains a Single Source of Truth (SSOT), visualizes architectural knowledge graphs, and governs architectural drift.
14
+
15
+ The **ESSOT MCP Server** empowers AI Coding Agents (such as Antigravity, Claude Desktop, Cursor, or Windsurf) to natively interact with your ESSOT project.
16
+
17
+ ### How it Connects to ESSOT
18
+
19
+ This MCP server operates in a **dual-mode architecture**:
20
+
21
+ 1. **Stdio MCP Interface**: Seamlessly exposes AI tools to your local IDE Agent, allowing it to pull architecture guidelines, parse SSOT requirements, and automatically synchronize your code drift with the remote ESSOT Knowledge Graph.
22
+ 2. **Enterprise Outbound Worker Tunnel**: Automatically establishes an encrypted outbound WebSocket connection over standard TLS (Port 443) to the ESSOT Web App. This allows the cloud-based **ESSOT Web App** to dispatch deep architectural tasks (scaffolding SSOT, generating plans, running Forge verifications) directly to your local workspace without opening any inbound firewall ports. (An internal `127.0.0.1:8123` listener is also maintained as a fallback for offline/local development).
23
+
24
+ ---
25
+
26
+ ## 🛠️ Available Tools
27
+
28
+ When connected to your IDE, the AI agent gains access to the following tools:
29
+
30
+ - `initialize_local_ssot`: Connects to the ESSOT Web App to scaffold your local `ssot/` directory using the project's Software Requirements Document (SRD).
31
+ - `generate_implementation_plan`: Acts as a Staff Architect. Analyzes local SSOT context and outputs an exhaustive, step-by-step implementation plan (with embedded security architecture).
32
+ - `generate_task_plan`: Decompiles the implementation plan into highly specific executable tasks for local agents (including integrated security guardrails).
33
+ - `generate_test_plan`: Creates comprehensive testing strategies (STQE, RTM) mapped to feature requirements.
34
+ - `synchronize_ssot`: Pushes local documentation and architectural updates back to the ESSOT Web App's Knowledge Graph to resolve drift.
35
+
36
+ ---
37
+
38
+ ## 📦 Installation & Quickstart
39
+
40
+ No manual repository downloads or cloning required. Once published to PyPI, your AI IDE can run `essot-mcp-server` on demand using `uvx` (or `pipx`).
41
+
42
+ ### 1-Step Setup (Recommended)
43
+
44
+ Add `essot-mcp-server` directly to your IDE's MCP configuration:
45
+
46
+ ```json
47
+ {
48
+ "mcpServers": {
49
+ "essot-mcp-server": {
50
+ "command": "uvx",
51
+ "args": [
52
+ "essot-mcp-server"
53
+ ],
54
+ "env": {
55
+ "ESSOT_API_KEY": "essot_your_api_key_here"
56
+ }
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ > **Note:** If your ESSOT instance is self-hosted or uses a custom domain, optionally set `"ESSOT_API_URL": "https://your-domain.com"`. By default, it connects to `https://essot.space`.
63
+
64
+ ### Alternative Runners
65
+
66
+ #### Using pipx:
67
+ ```json
68
+ {
69
+ "mcpServers": {
70
+ "essot-mcp-server": {
71
+ "command": "pipx",
72
+ "args": [
73
+ "run",
74
+ "essot-mcp-server"
75
+ ],
76
+ "env": {
77
+ "ESSOT_API_KEY": "essot_your_api_key_here"
78
+ }
79
+ }
80
+ }
81
+ }
82
+ ```
83
+
84
+ #### Traditional pip installation:
85
+ ```bash
86
+ pip install essot-mcp-server
87
+ ```
88
+ ```json
89
+ {
90
+ "mcpServers": {
91
+ "essot-mcp-server": {
92
+ "command": "essot-mcp-server",
93
+ "env": {
94
+ "ESSOT_API_KEY": "essot_your_api_key_here"
95
+ }
96
+ }
97
+ }
98
+ }
99
+ ```
100
+
101
+ ### Supported IDEs & Configuration Locations
102
+
103
+ - **Claude Desktop**: `%APPDATA%\Claude\claude_desktop_config.json` (Windows) or `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS).
104
+ - **Antigravity CLI / IDE**: `~/.gemini/config/mcp_config.json`.
105
+ - **Cursor**: Settings > Features > MCP.
106
+ - **VSCode**: `.vscode/mcp_config.json`.
107
+
108
+
109
+ ---
110
+
111
+ ## 🌐 Permanent Secure Outbound Worker Tunnel (Native IDE Bridge)
112
+
113
+ When connecting to the cloud-hosted ESSOT Web App (`https://essot.space`):
114
+
115
+ 1. **Enterprise Security & Zero Open Ports:**
116
+ The MCP server uses an **outbound-only WebSocket architecture** over standard encrypted TLS (Port 443). Corporate firewalls and NAT permit outbound HTTPS/WSS traffic freely. Once authenticated, a bidirectional full-duplex session is established directly with ESSOT Cloud. No public ingress ports or third-party tunnel relays are required.
117
+
118
+ 2. **Native IDE Setup (1 Step):**
119
+ Generate your `ESSOT_API_KEY` from **User Settings ⚙️** in the ESSOT Web App, and configure it directly in your IDE's MCP config:
120
+
121
+ ```json
122
+ {
123
+ "mcpServers": {
124
+ "essot-mcp-server": {
125
+ "command": "uvx",
126
+ "args": [
127
+ "essot-mcp-server"
128
+ ],
129
+ "env": {
130
+ "ESSOT_API_KEY": "essot_your_api_key_here"
131
+ }
132
+ }
133
+ }
134
+ }
135
+ ```
136
+
137
+ When your IDE opens, the server starts automatically and logs:
138
+
139
+ ```text
140
+ 🟢 Outbound WebSocket tunnel established with ESSOT Cloud. Permanent tunnel active.
141
+ ```
142
+
143
+ Now, clicking **Scaffold Local SSOT** or **Generate Plan** in the Web App, or running Forge verification tests, immediately dispatches tasks to your local workspace without any terminal commands or firewall adjustments.
144
+
145
+ ---
146
+
147
+ ## 🔧 Architecture Requirements
148
+
149
+ - **Python**: `3.10` or higher
150
+ - **Package Manager**: [uv](https://github.com/astral-sh/uv) by Astral
151
+ - **Inbound Ports**: **Zero incoming ports required**. Remote cloud dispatch operates 100% via outbound encrypted WebSocket (TLS Port 443).
152
+
153
+ ## 🤝 Contributing
154
+
155
+ Contributions, issues, and feature requests are welcome! Feel free to check the [issues page](../../issues).
@@ -0,0 +1,6 @@
1
+ """
2
+ ESSOT MCP Server.
3
+ Connecting AI Coding Agents with the Engineering SSOT Platform.
4
+ """
5
+
6
+ __version__ = "0.1.0"
@@ -0,0 +1,100 @@
1
+ import os
2
+ import logging
3
+ from openai import OpenAI
4
+
5
+ logger = logging.getLogger("essot_mcp_server.generator")
6
+
7
+ class PlanGenerator:
8
+ def __init__(self, provider: str = "openrouter", api_key: str = None):
9
+ self.raw_provider = provider
10
+ if provider.startswith("openrouter:"):
11
+ self.provider = "openrouter"
12
+ else:
13
+ self.provider = provider
14
+
15
+ if self.provider == "openrouter":
16
+ self.api_key = api_key or os.environ.get("OPENROUTER_API_KEY")
17
+ if not self.api_key:
18
+ logger.info("OPENROUTER_API_KEY not set locally; plans are generated via the ESSOT Web App PKCE connection.")
19
+ self.openrouter_client = None
20
+ else:
21
+ self.openrouter_client = OpenAI(
22
+ base_url="https://openrouter.ai/api/v1",
23
+ api_key=self.api_key,
24
+ timeout=300.0,
25
+ max_retries=3,
26
+ )
27
+ else:
28
+ raise ValueError(f"Unknown provider: {self.provider}")
29
+
30
+ def generate_markdown_plan(self, system_prompt: str, context: str, model_name: str = None) -> str:
31
+ """
32
+ Generates a comprehensive markdown plan.
33
+ Extracts specific model from provider string if passed like openrouter:anthropic/...
34
+ Guarantees zero cut-off via high max_tokens and automated multi-chunk continuation.
35
+ """
36
+ try:
37
+ if not self.openrouter_client:
38
+ return (
39
+ "# Plan Generation Note\n\n"
40
+ "Plan generation is coordinated through the ESSOT Web App using your linked OpenRouter PKCE connection. "
41
+ "Trigger generation from the ESSOT Studio or configure OPENROUTER_API_KEY locally for offline agent execution."
42
+ )
43
+
44
+ if ":" in self.raw_provider and not model_name:
45
+ model_name = self.raw_provider.split(":", 1)[1]
46
+
47
+ if not model_name:
48
+ raise ValueError("Model must be explicitly selected by the user. No default models are allowed.")
49
+
50
+ if self.provider == "openrouter":
51
+ logger.info(f"Generating plan using OpenRouter: {model_name}...")
52
+
53
+ messages = [
54
+ {
55
+ "role": "system",
56
+ "content": system_prompt + "\n\nCRITICAL COMPLETENESS REQUIREMENT:\nYou MUST provide a complete, unbroken, professional-grade specification. Do not trail off, leave placeholders, or terminate prematurely before all sections, diagrams, data schemas, and tasks are fully written."
57
+ },
58
+ {"role": "user", "content": context}
59
+ ]
60
+
61
+ response = self.openrouter_client.chat.completions.create(
62
+ model=model_name,
63
+ messages=messages,
64
+ temperature=0.2,
65
+ max_tokens=16384,
66
+ )
67
+
68
+ choice = response.choices[0]
69
+ full_content = choice.message.content or ""
70
+
71
+ # Auto-continuation loop: if the model hit token length limit, seamlessly continue
72
+ continuation_count = 0
73
+ max_continuations = 4
74
+
75
+ while getattr(choice, "finish_reason", None) == "length" and continuation_count < max_continuations:
76
+ continuation_count += 1
77
+ logger.info(f"Plan generation reached token length limit (finish_reason='length'). Requesting continuation chunk {continuation_count}/{max_continuations}...")
78
+
79
+ messages.append({"role": "assistant", "content": full_content})
80
+ messages.append({
81
+ "role": "user",
82
+ "content": "Your previous response reached the token limit and was cut off. Please continue writing the plan immediately from where you stopped. Do not repeat what you already wrote. Resume seamlessly from the exact last line/sentence and finish all remaining sections."
83
+ })
84
+
85
+ cont_resp = self.openrouter_client.chat.completions.create(
86
+ model=model_name,
87
+ messages=messages,
88
+ temperature=0.2,
89
+ max_tokens=16384,
90
+ )
91
+ choice = cont_resp.choices[0]
92
+ chunk = choice.message.content or ""
93
+ full_content += "\n\n" + chunk
94
+
95
+ logger.info(f"Successfully generated complete plan ({len(full_content)} chars, {continuation_count} continuations).")
96
+ return full_content
97
+
98
+ except Exception as e:
99
+ logger.error(f"Failed to generate plan via {self.provider}: {str(e)}. Falling back to STUB.")
100
+ return f"# STUB Plan\n\nGeneration failed with error: {str(e)}\n\nThis is a fallback stub to ensure the pipeline continues."