bob-dev 0.2.3__tar.gz → 0.3.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.
- {bob_dev-0.2.3/src/bob_dev.egg-info → bob_dev-0.3.0}/PKG-INFO +3 -3
- {bob_dev-0.2.3 → bob_dev-0.3.0}/README.md +1 -1
- {bob_dev-0.2.3 → bob_dev-0.3.0}/pyproject.toml +2 -2
- {bob_dev-0.2.3 → bob_dev-0.3.0}/src/bob_dev/cli.py +74 -56
- {bob_dev-0.2.3 → bob_dev-0.3.0}/src/bob_dev/services/llm.py +95 -24
- {bob_dev-0.2.3 → bob_dev-0.3.0}/src/bob_dev/services/project.py +8 -3
- bob_dev-0.3.0/src/bob_dev/settings.py +45 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0/src/bob_dev.egg-info}/PKG-INFO +3 -3
- {bob_dev-0.2.3 → bob_dev-0.3.0}/src/bob_dev.egg-info/SOURCES.txt +1 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0}/tests/test_cli.py +1 -2
- {bob_dev-0.2.3 → bob_dev-0.3.0}/LICENSE +0 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0}/setup.cfg +0 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0}/src/__init__.py +0 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0}/src/bob_dev/__init__.py +0 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0}/src/bob_dev/constants/__init__.py +0 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0}/src/bob_dev/constants/frameworks.py +0 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0}/src/bob_dev/services/__init__.py +0 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0}/src/bob_dev/services/claude.py +0 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0}/src/bob_dev/services/config.py +0 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0}/src/bob_dev/services/gitlab.py +0 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0}/src/bob_dev/services/jira.py +0 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0}/src/bob_dev/services/terminal.py +0 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0}/src/bob_dev.egg-info/dependency_links.txt +0 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0}/src/bob_dev.egg-info/entry_points.txt +0 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0}/src/bob_dev.egg-info/requires.txt +0 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0}/src/bob_dev.egg-info/top_level.txt +0 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0}/tests/test_config.py +0 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0}/tests/test_jira.py +0 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0}/tests/test_llm.py +0 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0}/tests/test_project.py +0 -0
- {bob_dev-0.2.3 → bob_dev-0.3.0}/tests/test_terminal.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bob-dev
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: An AI developer to write code for you
|
|
5
5
|
Author-email: Samuel Pereira dos Santos <samuelsantosdev@gmail.com>
|
|
6
|
-
Requires-Python: >=3.
|
|
6
|
+
Requires-Python: >=3.12.10
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
License-File: LICENSE
|
|
9
9
|
Provides-Extra: dev
|
|
@@ -30,7 +30,7 @@ Given a task ID (Jira or GitLab) it will:
|
|
|
30
30
|
|
|
31
31
|
## Requirements
|
|
32
32
|
|
|
33
|
-
- Python 3.
|
|
33
|
+
- Python 3.12+
|
|
34
34
|
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and available on `$PATH` as `claude`
|
|
35
35
|
- A **Jira Cloud** account with an [API token](https://id.atlassian.com/manage-profile/security/api-tokens) **or** a **GitLab** account with a [personal access token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html)
|
|
36
36
|
- An [xAI / GROK](https://console.x.ai/) **or** [OpenAI](https://platform.openai.com/) API key
|
|
@@ -17,7 +17,7 @@ Given a task ID (Jira or GitLab) it will:
|
|
|
17
17
|
|
|
18
18
|
## Requirements
|
|
19
19
|
|
|
20
|
-
- Python 3.
|
|
20
|
+
- Python 3.12+
|
|
21
21
|
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and available on `$PATH` as `claude`
|
|
22
22
|
- A **Jira Cloud** account with an [API token](https://id.atlassian.com/manage-profile/security/api-tokens) **or** a **GitLab** account with a [personal access token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html)
|
|
23
23
|
- An [xAI / GROK](https://console.x.ai/) **or** [OpenAI](https://platform.openai.com/) API key
|
|
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "bob-dev"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "An AI developer to write code for you"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name="Samuel Pereira dos Santos", email="samuelsantosdev@gmail.com" }]
|
|
11
|
-
requires-python = ">=3.
|
|
11
|
+
requires-python = ">=3.12.10"
|
|
12
12
|
dependencies = []
|
|
13
13
|
|
|
14
14
|
[project.scripts]
|
|
@@ -33,7 +33,7 @@ import sys
|
|
|
33
33
|
import argparse
|
|
34
34
|
import asyncio
|
|
35
35
|
from pathlib import Path
|
|
36
|
-
from time import sleep
|
|
36
|
+
from time import sleep
|
|
37
37
|
|
|
38
38
|
from InquirerPy import inquirer
|
|
39
39
|
from dotenv import load_dotenv
|
|
@@ -51,8 +51,7 @@ from .services.terminal import (
|
|
|
51
51
|
)
|
|
52
52
|
from .services.jira import get_jira_task
|
|
53
53
|
from .services.gitlab import get_gitlab_task
|
|
54
|
-
from .services.
|
|
55
|
-
from .services.llm import analyse_prompt, llm_model, prompt_claude_code
|
|
54
|
+
from .services.llm import analyse_prompt, llm_model, prompt_claude_code, review_prompt
|
|
56
55
|
from .services.project import build_md_context, identify_framework
|
|
57
56
|
from .services.config import check_configuration, update_env_file
|
|
58
57
|
|
|
@@ -64,8 +63,6 @@ SCRIPT_DIR = Path(__file__).resolve().parent
|
|
|
64
63
|
ENV_PATH = Path.home() / ".bob_dev" / ".env"
|
|
65
64
|
load_dotenv(ENV_PATH) # Load .env if it exists, but don't require it
|
|
66
65
|
|
|
67
|
-
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
|
|
68
|
-
GROK_API_KEY = os.environ.get("GROK_API_KEY", "")
|
|
69
66
|
AGENT = os.environ.get("AGENT", "GROK").upper() # "GROK" or "OPENAI"
|
|
70
67
|
TASK_MANAGER = os.environ.get("TASK_MANAGER", "JIRA").upper() # "JIRA" or "GITLAB"
|
|
71
68
|
|
|
@@ -73,12 +70,18 @@ JIRA_URL = os.environ.get("JIRA_URL", "")
|
|
|
73
70
|
JIRA_EMAIL = os.environ.get("JIRA_EMAIL", "")
|
|
74
71
|
JIRA_API_TOKEN = os.environ.get("JIRA_API_TOKEN", "")
|
|
75
72
|
|
|
73
|
+
GROK_API_KEY = os.environ.get("GROK_API_KEY", "")
|
|
74
|
+
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
|
|
75
|
+
|
|
76
76
|
GITLAB_URL = os.environ.get("GITLAB_URL", "")
|
|
77
77
|
GITLAB_API_TOKEN = os.environ.get("GITLAB_API_TOKEN", "")
|
|
78
78
|
|
|
79
79
|
REPO_BASE_PATH = Path("./") # Overridden by --path at runtime.
|
|
80
80
|
CLAUDE_CODE_CMD = "claude" # Must be on $PATH.
|
|
81
81
|
|
|
82
|
+
MAX_FILES = int(os.environ.get("MAX_FILES", 30))
|
|
83
|
+
MAX_CHARS = int(os.environ.get("MAX_CHARS", 40_000))
|
|
84
|
+
MAX_SUMMARY_WORDS = int(os.environ.get("MAX_SUMMARY_WORDS", 2000))
|
|
82
85
|
|
|
83
86
|
# ---------------------------------------------------------------------------
|
|
84
87
|
# Main entry point
|
|
@@ -132,11 +135,11 @@ def main() -> None:
|
|
|
132
135
|
sys.exit(1)
|
|
133
136
|
|
|
134
137
|
if agent == "GROK" and not GROK_API_KEY:
|
|
135
|
-
print_error("
|
|
138
|
+
print_error("GROK API key is not configured. Run `bob-dev --configure`.")
|
|
136
139
|
sys.exit(1)
|
|
137
140
|
|
|
138
141
|
if agent == "OPENAI" and not OPENAI_API_KEY:
|
|
139
|
-
print_error("
|
|
142
|
+
print_error("OpenAI API key is not configured. Run `bob-dev --configure`.")
|
|
140
143
|
sys.exit(1)
|
|
141
144
|
|
|
142
145
|
# ── Resolve and validate the repository path ─────────────────────────────
|
|
@@ -147,9 +150,9 @@ def main() -> None:
|
|
|
147
150
|
print_error(f"Invalid project path: {REPO_BASE_PATH}")
|
|
148
151
|
sys.exit(1)
|
|
149
152
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
+
print_success(f"Project path : {REPO_BASE_PATH}")
|
|
154
|
+
print_success(f"{TASK_MANAGER} task ID : {task_id}")
|
|
155
|
+
print_success(f"LLM backend : {agent} ({llm_model(agent)})")
|
|
153
156
|
print()
|
|
154
157
|
|
|
155
158
|
# ── Step 1 – Fetch task ─────────────────────────────────────────────
|
|
@@ -184,7 +187,7 @@ def main() -> None:
|
|
|
184
187
|
|
|
185
188
|
# Collect Markdown context (blocking I/O) inside the spinner thread.
|
|
186
189
|
md_context = asyncio.run(run_with_spinner(
|
|
187
|
-
build_md_context, REPO_BASE_PATH,
|
|
190
|
+
build_md_context, REPO_BASE_PATH, MAX_SUMMARY_WORDS,
|
|
188
191
|
label="Reading project docs",
|
|
189
192
|
))
|
|
190
193
|
|
|
@@ -196,58 +199,73 @@ def main() -> None:
|
|
|
196
199
|
print_warn(str(exc))
|
|
197
200
|
framework = "the project"
|
|
198
201
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
print_error("Failed to generate a prompt for Claude Code.")
|
|
209
|
-
sys.exit(1)
|
|
202
|
+
execute_prompt = False
|
|
203
|
+
while execute_prompt is False:
|
|
204
|
+
prompt_md = asyncio.run(run_with_spinner(
|
|
205
|
+
prompt_claude_code,
|
|
206
|
+
acceptance_criteria, md_context, framework,
|
|
207
|
+
agent, GROK_API_KEY, OPENAI_API_KEY,
|
|
208
|
+
label="Generating prompt",
|
|
209
|
+
task_meta=task,
|
|
210
|
+
))
|
|
210
211
|
|
|
211
|
-
|
|
212
|
-
|
|
212
|
+
if not prompt_md.strip():
|
|
213
|
+
print_error("Failed to generate a prompt for Claude Code.")
|
|
214
|
+
sys.exit(1)
|
|
213
215
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
+
print_success("Prompt generated.")
|
|
217
|
+
print()
|
|
216
218
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
prompt_md, acceptance_criteria,
|
|
220
|
-
agent, GROK_API_KEY, OPENAI_API_KEY,
|
|
221
|
-
label="Analysing prompt",
|
|
222
|
-
))
|
|
219
|
+
# ── Step 3 – Analyse the prompt ──────────────────────────────────────────
|
|
220
|
+
print_step("[3/4]", "Analysing the prompt for issues …")
|
|
223
221
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
# ── Confirm before handing off to Claude Code ────────────────────────────
|
|
231
|
-
answer = input("Proceed to prompt preview? [y/N] ").strip().lower()
|
|
232
|
-
if answer != "y":
|
|
233
|
-
prompt_file = SCRIPT_DIR / f"claude_prompt-{task_id}.md"
|
|
234
|
-
prompt_file.write_text(prompt_md, encoding="utf-8")
|
|
235
|
-
print_info(f"Aborted. Prompt saved to {prompt_file}")
|
|
236
|
-
sys.exit(0)
|
|
222
|
+
analysis = asyncio.run(run_with_spinner(
|
|
223
|
+
analyse_prompt,
|
|
224
|
+
prompt_md, acceptance_criteria,
|
|
225
|
+
agent, GROK_API_KEY, OPENAI_API_KEY,
|
|
226
|
+
label="Analysing prompt",
|
|
227
|
+
))
|
|
237
228
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
229
|
+
print(f"\n{BOLD}── Prompt Analysis {'─' * 50}{RESET}")
|
|
230
|
+
for line in analysis.splitlines():
|
|
231
|
+
print(line)
|
|
232
|
+
sleep(0.02) # Simulate a "typing" effect for better readability
|
|
233
|
+
print("─" * 68 + "\n")
|
|
234
|
+
|
|
235
|
+
# ── Confirm before handing off to Claude Code ────────────────────────────
|
|
236
|
+
answer = input("Proceed to prompt preview? [y/N/c] ").strip().lower()
|
|
237
|
+
if answer == "n":
|
|
238
|
+
prompt_file = SCRIPT_DIR / f"claude_prompt-{task_id}.md"
|
|
239
|
+
prompt_file.write_text(prompt_md, encoding="utf-8")
|
|
240
|
+
print_info(f"Aborted. Prompt saved to {prompt_file}")
|
|
241
|
+
sys.exit(0)
|
|
242
|
+
elif answer == "c":
|
|
243
|
+
answer = input("Write your considerations to change the prompt: ").strip().lower()
|
|
244
|
+
md_context = review_prompt(acceptance_criteria, md_context, answer, agent, GROK_API_KEY, OPENAI_API_KEY)
|
|
245
|
+
continue # Regenerate the prompt with the new context and considerations.
|
|
246
|
+
|
|
247
|
+
print("\n\n")
|
|
248
|
+
print_step("[4/4]", "Passing prompt to Claude Code …")
|
|
249
|
+
print_success("Prompt preview:")
|
|
250
|
+
print("-" * 68)
|
|
251
|
+
for line in prompt_md.splitlines():
|
|
252
|
+
print(line)
|
|
253
|
+
sleep(0.02) # Simulate a "typing" effect for better readability
|
|
254
|
+
print("-" * 68)
|
|
255
|
+
answer = input("\nAre you sure? This will run the Claude Code CLI with the generated prompt. [y/N/c] ").strip().lower()
|
|
256
|
+
if answer == "n":
|
|
257
|
+
print_info("Aborted by user.")
|
|
258
|
+
sys.exit(0)
|
|
259
|
+
elif answer == "c":
|
|
260
|
+
answer = input("Write your considerations to change the prompt: ").strip().lower()
|
|
261
|
+
md_context = review_prompt(acceptance_criteria, md_context, answer, agent, GROK_API_KEY, OPENAI_API_KEY)
|
|
262
|
+
continue # Regenerate the prompt with the new context and considerations.
|
|
263
|
+
|
|
264
|
+
execute_prompt = True
|
|
265
|
+
break
|
|
266
|
+
|
|
248
267
|
|
|
249
268
|
# ── Step 4 – Pass prompt to Claude Code ──────────────────────────────────
|
|
250
|
-
print_step("[4/4]", "Passing prompt to Claude Code …")
|
|
251
269
|
print()
|
|
252
270
|
asyncio.run(_pass_to_claude_code(prompt_md, task_id, None))
|
|
253
271
|
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
LLM integration for bob_dev:
|
|
4
4
|
- Build an OpenAI-compatible client for GROK or OpenAI backends.
|
|
5
5
|
- Return the appropriate model name for a given backend.
|
|
6
|
-
- Generate a Claude Code prompt from
|
|
6
|
+
- Generate a Claude Code prompt from task acceptance criteria.
|
|
7
7
|
- Analyse a generated prompt for gaps and security concerns.
|
|
8
|
+
- Regenerate a prompt based on user feedback after analysis.
|
|
8
9
|
"""
|
|
9
10
|
|
|
10
11
|
from __future__ import annotations
|
|
@@ -12,26 +13,27 @@ from __future__ import annotations
|
|
|
12
13
|
import textwrap
|
|
13
14
|
|
|
14
15
|
from openai import OpenAI
|
|
16
|
+
from openai import OpenAIError
|
|
15
17
|
|
|
16
18
|
|
|
17
|
-
# ---------------------------------------------------------------------------
|
|
18
|
-
# Client & model helpers
|
|
19
|
-
# ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
19
|
def build_llm_client(agent: str, grok_api_key: str, openai_api_key: str) -> OpenAI:
|
|
22
20
|
"""Return an OpenAI-compatible client for *agent* ("GROK" or "OPENAI").
|
|
23
21
|
|
|
24
22
|
Raises EnvironmentError if the required API key is missing.
|
|
23
|
+
Raises ConnectionError if the LLM client cannot be created.
|
|
25
24
|
"""
|
|
26
|
-
|
|
27
|
-
if
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
try:
|
|
26
|
+
if agent == "GROK":
|
|
27
|
+
if not grok_api_key:
|
|
28
|
+
raise EnvironmentError("GROK_API_KEY is not set in .env")
|
|
29
|
+
return OpenAI(api_key=grok_api_key, base_url="https://api.x.ai/v1")
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
# Default: OpenAI
|
|
32
|
+
if not openai_api_key:
|
|
33
|
+
raise EnvironmentError("OPENAI_API_KEY is not set in .env")
|
|
34
|
+
return OpenAI(api_key=openai_api_key)
|
|
35
|
+
except OpenAIError as exc:
|
|
36
|
+
raise ConnectionError(f"Failed to create LLM client for {agent}: {exc}") from exc
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
def llm_model(agent: str) -> str:
|
|
@@ -39,10 +41,6 @@ def llm_model(agent: str) -> str:
|
|
|
39
41
|
return "grok-3" if agent == "GROK" else "gpt-4o"
|
|
40
42
|
|
|
41
43
|
|
|
42
|
-
# ---------------------------------------------------------------------------
|
|
43
|
-
# Prompt generation
|
|
44
|
-
# ---------------------------------------------------------------------------
|
|
45
|
-
|
|
46
44
|
def prompt_claude_code(
|
|
47
45
|
acceptance_criteria: str,
|
|
48
46
|
md_context: str,
|
|
@@ -64,8 +62,6 @@ def prompt_claude_code(
|
|
|
64
62
|
Detected framework name (e.g. "Django REST Framework").
|
|
65
63
|
agent:
|
|
66
64
|
"GROK" or "OPENAI".
|
|
67
|
-
grok_api_key / openai_api_key:
|
|
68
|
-
API keys; the unused one may be an empty string.
|
|
69
65
|
task_meta:
|
|
70
66
|
Optional dict with 'task_id', 'title', 'fix_versions' for context.
|
|
71
67
|
|
|
@@ -141,10 +137,6 @@ def prompt_claude_code(
|
|
|
141
137
|
return response.choices[0].message.content or ""
|
|
142
138
|
|
|
143
139
|
|
|
144
|
-
# ---------------------------------------------------------------------------
|
|
145
|
-
# Prompt analysis
|
|
146
|
-
# ---------------------------------------------------------------------------
|
|
147
|
-
|
|
148
140
|
def analyse_prompt(
|
|
149
141
|
prompt_md: str,
|
|
150
142
|
acceptance_criteria: str,
|
|
@@ -154,7 +146,18 @@ def analyse_prompt(
|
|
|
154
146
|
) -> str:
|
|
155
147
|
"""Ask the LLM to review *prompt_md* for ambiguities and security concerns.
|
|
156
148
|
|
|
157
|
-
|
|
149
|
+
Parameters
|
|
150
|
+
----------
|
|
151
|
+
prompt_md:
|
|
152
|
+
The generated Claude Code prompt in Markdown format.
|
|
153
|
+
acceptance_criteria:
|
|
154
|
+
The original acceptance criteria text from Jira.
|
|
155
|
+
agent:
|
|
156
|
+
"GROK" or "OPENAI".
|
|
157
|
+
Returns
|
|
158
|
+
-------
|
|
159
|
+
str
|
|
160
|
+
A concise analysis of the prompt's gaps, ambiguities, and security issues.
|
|
158
161
|
"""
|
|
159
162
|
client = build_llm_client(agent, grok_api_key, openai_api_key)
|
|
160
163
|
model = llm_model(agent)
|
|
@@ -185,3 +188,71 @@ def analyse_prompt(
|
|
|
185
188
|
)
|
|
186
189
|
|
|
187
190
|
return response.choices[0].message.content or ""
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def review_prompt(
|
|
194
|
+
acceptance_criteria: str,
|
|
195
|
+
md_context: str,
|
|
196
|
+
considerations: str,
|
|
197
|
+
agent: str,
|
|
198
|
+
grok_api_key: str,
|
|
199
|
+
openai_api_key: str,
|
|
200
|
+
) -> str:
|
|
201
|
+
"""Ask the LLM to regenerate the prompt based on *considerations*.
|
|
202
|
+
|
|
203
|
+
This is used when the user wants to refine the prompt after an initial review.
|
|
204
|
+
|
|
205
|
+
Parameters
|
|
206
|
+
----------
|
|
207
|
+
acceptance_criteria:
|
|
208
|
+
Raw description text from the Jira task.
|
|
209
|
+
md_context:
|
|
210
|
+
Pre-built string of project Markdown files + summarised README.
|
|
211
|
+
considerations:
|
|
212
|
+
User-provided feedback on what to improve in the prompt.
|
|
213
|
+
agent:
|
|
214
|
+
"GROK" or "OPENAI".
|
|
215
|
+
Returns
|
|
216
|
+
-------
|
|
217
|
+
str
|
|
218
|
+
A new version of the Claude Code prompt, improved according to the feedback.
|
|
219
|
+
"""
|
|
220
|
+
client = build_llm_client(agent, grok_api_key, openai_api_key)
|
|
221
|
+
model = llm_model(agent)
|
|
222
|
+
|
|
223
|
+
system_prompt = textwrap.dedent(f"""
|
|
224
|
+
You are a senior software engineer refining a Claude Code prompt based on feedback.
|
|
225
|
+
The original prompt had the following issues:
|
|
226
|
+
{considerations}
|
|
227
|
+
|
|
228
|
+
Regenerate the Claude Code prompt, improving it according to the feedback above.
|
|
229
|
+
Maintain all original requirements and formatting instructions, but address the
|
|
230
|
+
identified issues to make it more actionable and secure.
|
|
231
|
+
""").strip()
|
|
232
|
+
|
|
233
|
+
user_message = textwrap.dedent(f"""
|
|
234
|
+
## Project Documentation
|
|
235
|
+
|
|
236
|
+
{md_context}
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Original Acceptance Criteria
|
|
241
|
+
|
|
242
|
+
{acceptance_criteria}
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
Regenerate the Claude Code prompt based on the feedback provided.
|
|
247
|
+
""").strip()
|
|
248
|
+
|
|
249
|
+
response = client.chat.completions.create(
|
|
250
|
+
model=model,
|
|
251
|
+
messages=[
|
|
252
|
+
{"role": "system", "content": system_prompt},
|
|
253
|
+
{"role": "user", "content": user_message},
|
|
254
|
+
],
|
|
255
|
+
temperature=0.3,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
return response.choices[0].message.content or ""
|
|
@@ -23,6 +23,7 @@ def collect_md_context(
|
|
|
23
23
|
root: Path,
|
|
24
24
|
max_files: int = 30,
|
|
25
25
|
max_chars: int = 40_000,
|
|
26
|
+
max_words: int = 2000,
|
|
26
27
|
) -> str:
|
|
27
28
|
"""Walk *root* and return concatenated content of Markdown files.
|
|
28
29
|
|
|
@@ -33,6 +34,7 @@ def collect_md_context(
|
|
|
33
34
|
parts: list[str] = []
|
|
34
35
|
total = 0
|
|
35
36
|
|
|
37
|
+
summarize_context = sum(md.stat().st_size for md in md_files) > max_chars
|
|
36
38
|
for md in md_files:
|
|
37
39
|
try:
|
|
38
40
|
text = md.read_text(encoding="utf-8", errors="ignore")
|
|
@@ -44,6 +46,9 @@ def collect_md_context(
|
|
|
44
46
|
|
|
45
47
|
if total + len(chunk) > max_chars:
|
|
46
48
|
break
|
|
49
|
+
|
|
50
|
+
if summarize_context:
|
|
51
|
+
chunk = f"### {relative}\n\n" + summarize(text, words=max_words) + "\n\n"
|
|
47
52
|
|
|
48
53
|
parts.append(chunk)
|
|
49
54
|
total += len(chunk)
|
|
@@ -66,14 +71,14 @@ def read_readme(repo_path: Path) -> str:
|
|
|
66
71
|
return ""
|
|
67
72
|
|
|
68
73
|
|
|
69
|
-
def build_md_context(repo_path: Path) -> str:
|
|
74
|
+
def build_md_context(repo_path: Path, max_summary_words: int = 2000) -> str:
|
|
70
75
|
"""Build a combined context string for LLM consumption.
|
|
71
76
|
|
|
72
|
-
Combines a summarised README (up to
|
|
77
|
+
Combines a summarised README (up to max_summary_words) with the full
|
|
73
78
|
Markdown context collected from the repository.
|
|
74
79
|
"""
|
|
75
80
|
readme_raw = read_readme(repo_path)
|
|
76
|
-
readme_summary = summarize(readme_raw, words=
|
|
81
|
+
readme_summary = summarize(readme_raw, words=max_summary_words) if readme_raw.strip() else ""
|
|
77
82
|
md_context = collect_md_context(repo_path)
|
|
78
83
|
|
|
79
84
|
return "# README:\n" + readme_summary + "\n\n# Context:\n" + md_context
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dotenv import load_dotenv
|
|
3
|
+
|
|
4
|
+
load_dotenv()
|
|
5
|
+
|
|
6
|
+
def validate_variables():
|
|
7
|
+
"""
|
|
8
|
+
Validates the environment variables for the application.
|
|
9
|
+
Raises a ValueError if any required variable is missing or invalid.
|
|
10
|
+
|
|
11
|
+
Required variables:
|
|
12
|
+
- AGENT: Must be either "GROK" or "OPENAI".
|
|
13
|
+
- TASK_MANAGER: Must be either "GITLAB" or "JIRA".
|
|
14
|
+
- For GitLab: GITLAB_URL and GITLAB_API_TOKEN must be set.
|
|
15
|
+
- For Jira: JIRA_URL, JIRA_EMAIL, and JIRA_API_TOKEN must be set.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
if AGENT not in ("GROK", "OPENAI"):
|
|
19
|
+
raise ValueError("AGENT must be either 'GROK' or 'OPENAI'")
|
|
20
|
+
if TASK_MANAGER not in ("GITLAB", "JIRA"):
|
|
21
|
+
raise ValueError("TASK_MANAGER must be either 'GITLAB' or 'JIRA'")
|
|
22
|
+
|
|
23
|
+
if TASK_MANAGER == "GITLAB" and (not GITLAB_URL or not GITLAB_API_TOKEN):
|
|
24
|
+
raise ValueError("GITLAB_URL and GITLAB_API_TOKEN must be set for GitLab task management")
|
|
25
|
+
if TASK_MANAGER == "JIRA" and (not JIRA_URL or not JIRA_EMAIL or not JIRA_API_TOKEN):
|
|
26
|
+
raise ValueError("JIRA_URL, JIRA_EMAIL, and JIRA_API_TOKEN must be set for Jira task management")
|
|
27
|
+
|
|
28
|
+
# LLM backend: GROK (default) or OPENAI
|
|
29
|
+
AGENT = os.environ.get("AGENT", "GROK").upper()
|
|
30
|
+
TASK_MANAGER = os.environ.get("TASK_MANAGER", "JIRA").upper()
|
|
31
|
+
|
|
32
|
+
# Grok (xAI)
|
|
33
|
+
GROK_API_KEY = os.environ.get("GROK_API_KEY", "")
|
|
34
|
+
|
|
35
|
+
# OpenAI (only needed when AGENT=OPENAI)
|
|
36
|
+
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
|
|
37
|
+
|
|
38
|
+
# Jira / Atlassian
|
|
39
|
+
JIRA_URL = os.environ.get("JIRA_URL", "")
|
|
40
|
+
JIRA_EMAIL = os.environ.get("JIRA_EMAIL", "")
|
|
41
|
+
JIRA_API_TOKEN = os.environ.get("JIRA_API_TOKEN", "")
|
|
42
|
+
|
|
43
|
+
# GitLab
|
|
44
|
+
GITLAB_URL = os.environ.get("GITLAB_URL", "")
|
|
45
|
+
GITLAB_API_TOKEN = os.environ.get("GITLAB_API_TOKEN", "")
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bob-dev
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: An AI developer to write code for you
|
|
5
5
|
Author-email: Samuel Pereira dos Santos <samuelsantosdev@gmail.com>
|
|
6
|
-
Requires-Python: >=3.
|
|
6
|
+
Requires-Python: >=3.12.10
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
License-File: LICENSE
|
|
9
9
|
Provides-Extra: dev
|
|
@@ -30,7 +30,7 @@ Given a task ID (Jira or GitLab) it will:
|
|
|
30
30
|
|
|
31
31
|
## Requirements
|
|
32
32
|
|
|
33
|
-
- Python 3.
|
|
33
|
+
- Python 3.12+
|
|
34
34
|
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and available on `$PATH` as `claude`
|
|
35
35
|
- A **Jira Cloud** account with an [API token](https://id.atlassian.com/manage-profile/security/api-tokens) **or** a **GitLab** account with a [personal access token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html)
|
|
36
36
|
- An [xAI / GROK](https://console.x.ai/) **or** [OpenAI](https://platform.openai.com/) API key
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|