bob-dev 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.
- bob_dev-0.1.0/LICENSE +17 -0
- bob_dev-0.1.0/PKG-INFO +103 -0
- bob_dev-0.1.0/README.md +90 -0
- bob_dev-0.1.0/pyproject.toml +22 -0
- bob_dev-0.1.0/setup.cfg +4 -0
- bob_dev-0.1.0/src/__init__.py +0 -0
- bob_dev-0.1.0/src/bob_dev/__init__.py +0 -0
- bob_dev-0.1.0/src/bob_dev/cli.py +293 -0
- bob_dev-0.1.0/src/bob_dev/constants/__init__.py +0 -0
- bob_dev-0.1.0/src/bob_dev/constants/frameworks.py +28 -0
- bob_dev-0.1.0/src/bob_dev/helpers/__init__.py +1 -0
- bob_dev-0.1.0/src/bob_dev/helpers/config_helper.py +107 -0
- bob_dev-0.1.0/src/bob_dev/helpers/jira_helper.py +75 -0
- bob_dev-0.1.0/src/bob_dev/helpers/llm_helper.py +187 -0
- bob_dev-0.1.0/src/bob_dev/helpers/project_helper.py +101 -0
- bob_dev-0.1.0/src/bob_dev/helpers/terminal.py +134 -0
- bob_dev-0.1.0/src/bob_dev.egg-info/PKG-INFO +103 -0
- bob_dev-0.1.0/src/bob_dev.egg-info/SOURCES.txt +26 -0
- bob_dev-0.1.0/src/bob_dev.egg-info/dependency_links.txt +1 -0
- bob_dev-0.1.0/src/bob_dev.egg-info/entry_points.txt +2 -0
- bob_dev-0.1.0/src/bob_dev.egg-info/requires.txt +4 -0
- bob_dev-0.1.0/src/bob_dev.egg-info/top_level.txt +2 -0
- bob_dev-0.1.0/tests/test_cli.py +262 -0
- bob_dev-0.1.0/tests/test_config_helper.py +71 -0
- bob_dev-0.1.0/tests/test_jira_helper.py +204 -0
- bob_dev-0.1.0/tests/test_llm_helper.py +163 -0
- bob_dev-0.1.0/tests/test_project_helper.py +155 -0
- bob_dev-0.1.0/tests/test_terminal.py +134 -0
bob_dev-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
PRIVATE LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This project and all its contents, including but not limited to source code, documentation,
|
|
6
|
+
and other materials, are proprietary and confidential. Unauthorized copying, distribution,
|
|
7
|
+
modification, or use of this material is strictly prohibited without express written permission
|
|
8
|
+
from the copyright holder.
|
|
9
|
+
|
|
10
|
+
RESTRICTIONS:
|
|
11
|
+
- This software is provided for authorized use only
|
|
12
|
+
- Copying, modifying, or distributing the software is prohibited
|
|
13
|
+
- Reverse engineering or decompiling is strictly forbidden
|
|
14
|
+
- Commercial use is not permitted without explicit authorization
|
|
15
|
+
- No warranties or guarantees are provided
|
|
16
|
+
|
|
17
|
+
For permission or licensing inquiries, please contact the copyright holder.
|
bob_dev-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bob-dev
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An AI developer to write code for you
|
|
5
|
+
Author-email: Samuel Pereira dos Santos <samuelsantosdev@gmail.com>
|
|
6
|
+
Requires-Python: >=3.14
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
11
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# BOB Dev
|
|
15
|
+
|
|
16
|
+
**BOB Dev** is an AI-powered developer workflow CLI that bridges a task requirements, your codebase, and Claude Code.
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
Given a Jira task ID it will:
|
|
21
|
+
|
|
22
|
+
1. **Fetch** the task title, description, and fix versions from Jira.
|
|
23
|
+
2. **Read** your project's Markdown documentation to build rich LLM context.
|
|
24
|
+
3. **Generate** a precise Claude Code prompt (via GROK or OpenAI), including project-framework context, implementation steps, and test scenarios.
|
|
25
|
+
4. **Analyse** the prompt for ambiguities and security concerns.
|
|
26
|
+
5. **Execute** the prompt with the Claude Code CLI (optional – you can review first).
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
- Python 3.11+
|
|
33
|
+
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and available on `$PATH` as `claude`
|
|
34
|
+
- A Jira Cloud account with an [API token](https://id.atlassian.com/manage-profile/security/api-tokens)
|
|
35
|
+
- An [xAI / GROK](https://console.x.ai/) **or** [OpenAI](https://platform.openai.com/) API key
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install bob-dev
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Configuration
|
|
48
|
+
|
|
49
|
+
Run the interactive setup wizard the first time:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
bob-dev --configure
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This will prompt for your API keys, Jira credentials, and Claude Code API key.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Usage
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
bob-dev --task_id PROJ-123 --path /path/to/your/repo
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
| Flag | Description |
|
|
66
|
+
|------|-------------|
|
|
67
|
+
| `--task_id` | Jira task ID to process (required for normal workflow) |
|
|
68
|
+
| `--path` | Path to the target repository (default: current directory) |
|
|
69
|
+
| `--agent` | LLM backend: `GROK` or `OPENAI` (default: value of `AGENT` in `.env`) |
|
|
70
|
+
| `--configure` | Run the interactive configuration wizard |
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Project structure
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
src/bob_dev/
|
|
78
|
+
├── cli.py # Entry point & main workflow orchestration
|
|
79
|
+
├── helpers/
|
|
80
|
+
│ ├── terminal.py # ANSI colours, print helpers, spinner animation
|
|
81
|
+
│ ├── jira_helper.py # Jira API connection + ADF-to-text parsing
|
|
82
|
+
│ ├── llm_helper.py # LLM client, model selection, prompt generation
|
|
83
|
+
│ ├── project_helper.py # Markdown context collection + framework detection
|
|
84
|
+
│ └── config_helper.py # .env management + credential validation
|
|
85
|
+
└── constants/
|
|
86
|
+
└── frameworks.py # Known framework names used for auto-detection
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Colour conventions
|
|
92
|
+
|
|
93
|
+
| Colour | Meaning |
|
|
94
|
+
|--------|---------|
|
|
95
|
+
| Red | Errors that stop execution |
|
|
96
|
+
| Green | Success messages |
|
|
97
|
+
| Plain | Informational / default output |
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
MIT
|
bob_dev-0.1.0/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# BOB Dev
|
|
2
|
+
|
|
3
|
+
**BOB Dev** is an AI-powered developer workflow CLI that bridges a task requirements, your codebase, and Claude Code.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
Given a Jira task ID it will:
|
|
8
|
+
|
|
9
|
+
1. **Fetch** the task title, description, and fix versions from Jira.
|
|
10
|
+
2. **Read** your project's Markdown documentation to build rich LLM context.
|
|
11
|
+
3. **Generate** a precise Claude Code prompt (via GROK or OpenAI), including project-framework context, implementation steps, and test scenarios.
|
|
12
|
+
4. **Analyse** the prompt for ambiguities and security concerns.
|
|
13
|
+
5. **Execute** the prompt with the Claude Code CLI (optional – you can review first).
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
- Python 3.11+
|
|
20
|
+
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and available on `$PATH` as `claude`
|
|
21
|
+
- A Jira Cloud account with an [API token](https://id.atlassian.com/manage-profile/security/api-tokens)
|
|
22
|
+
- An [xAI / GROK](https://console.x.ai/) **or** [OpenAI](https://platform.openai.com/) API key
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install bob-dev
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
Run the interactive setup wizard the first time:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
bob-dev --configure
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This will prompt for your API keys, Jira credentials, and Claude Code API key.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
bob-dev --task_id PROJ-123 --path /path/to/your/repo
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
| Flag | Description |
|
|
53
|
+
|------|-------------|
|
|
54
|
+
| `--task_id` | Jira task ID to process (required for normal workflow) |
|
|
55
|
+
| `--path` | Path to the target repository (default: current directory) |
|
|
56
|
+
| `--agent` | LLM backend: `GROK` or `OPENAI` (default: value of `AGENT` in `.env`) |
|
|
57
|
+
| `--configure` | Run the interactive configuration wizard |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Project structure
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
src/bob_dev/
|
|
65
|
+
├── cli.py # Entry point & main workflow orchestration
|
|
66
|
+
├── helpers/
|
|
67
|
+
│ ├── terminal.py # ANSI colours, print helpers, spinner animation
|
|
68
|
+
│ ├── jira_helper.py # Jira API connection + ADF-to-text parsing
|
|
69
|
+
│ ├── llm_helper.py # LLM client, model selection, prompt generation
|
|
70
|
+
│ ├── project_helper.py # Markdown context collection + framework detection
|
|
71
|
+
│ └── config_helper.py # .env management + credential validation
|
|
72
|
+
└── constants/
|
|
73
|
+
└── frameworks.py # Known framework names used for auto-detection
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Colour conventions
|
|
79
|
+
|
|
80
|
+
| Colour | Meaning |
|
|
81
|
+
|--------|---------|
|
|
82
|
+
| Red | Errors that stop execution |
|
|
83
|
+
| Green | Success messages |
|
|
84
|
+
| Plain | Informational / default output |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
MIT
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "bob-dev"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "An AI developer to write code for you"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [{ name="Samuel Pereira dos Santos", email="samuelsantosdev@gmail.com" }]
|
|
11
|
+
requires-python = ">=3.14"
|
|
12
|
+
dependencies = []
|
|
13
|
+
|
|
14
|
+
[project.scripts]
|
|
15
|
+
bob-dev = "bob_dev.cli:main"
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
dev = ["pytest>=8.0", "pytest-asyncio>=0.23"]
|
|
19
|
+
|
|
20
|
+
[tool.pytest.ini_options]
|
|
21
|
+
testpaths = ["tests"]
|
|
22
|
+
asyncio_mode = "auto"
|
bob_dev-0.1.0/setup.cfg
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""cli.py
|
|
2
|
+
|
|
3
|
+
Entry point for the BOB Dev CLI tool.
|
|
4
|
+
|
|
5
|
+
Workflow
|
|
6
|
+
--------
|
|
7
|
+
1. Fetch a Jira task by ID (title, description, fix versions).
|
|
8
|
+
2. Read the project's Markdown documentation and build an LLM context string.
|
|
9
|
+
3. Ask an LLM (GROK or OpenAI) to convert the acceptance criteria into a
|
|
10
|
+
Claude Code prompt, enriched with project context and framework info.
|
|
11
|
+
4. Analyse the generated prompt for ambiguities and security concerns.
|
|
12
|
+
5. Pass the final prompt to the Claude Code CLI for implementation.
|
|
13
|
+
|
|
14
|
+
Usage
|
|
15
|
+
-----
|
|
16
|
+
bob-dev --task_id PROJ-123 --path /path/to/repo
|
|
17
|
+
bob-dev --configure
|
|
18
|
+
|
|
19
|
+
Environment variables (.env next to this file or exported):
|
|
20
|
+
GROK_API_KEY – xAI / GROK secret key
|
|
21
|
+
OPENAI_API_KEY – OpenAI secret key
|
|
22
|
+
AGENT – "GROK" (default) or "OPENAI"
|
|
23
|
+
JIRA_URL – https://your-org.atlassian.net
|
|
24
|
+
JIRA_EMAIL – Atlassian account e-mail
|
|
25
|
+
JIRA_API_TOKEN – Atlassian API token
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import os
|
|
31
|
+
import sys
|
|
32
|
+
import argparse
|
|
33
|
+
import asyncio
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
|
|
36
|
+
from InquirerPy import inquirer
|
|
37
|
+
from dotenv import load_dotenv
|
|
38
|
+
|
|
39
|
+
from .helpers.terminal import (
|
|
40
|
+
BOLD,
|
|
41
|
+
RESET,
|
|
42
|
+
print_error,
|
|
43
|
+
print_info,
|
|
44
|
+
print_step,
|
|
45
|
+
print_success,
|
|
46
|
+
print_warn,
|
|
47
|
+
run_subprocess,
|
|
48
|
+
run_with_spinner,
|
|
49
|
+
)
|
|
50
|
+
from .helpers.jira_helper import get_jira_task
|
|
51
|
+
from .helpers.llm_helper import analyse_prompt, llm_model, prompt_claude_code
|
|
52
|
+
from .helpers.project_helper import build_md_context, identify_framework
|
|
53
|
+
from .helpers.config_helper import check_configuration, update_env_file
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
# Module-level configuration
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
60
|
+
load_dotenv(SCRIPT_DIR / ".env")
|
|
61
|
+
|
|
62
|
+
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
|
|
63
|
+
GROK_API_KEY = os.environ.get("GROK_API_KEY", "")
|
|
64
|
+
AGENT = os.environ.get("AGENT", "GROK").upper() # "GROK" or "OPENAI"
|
|
65
|
+
|
|
66
|
+
JIRA_URL = os.environ.get("JIRA_URL", "")
|
|
67
|
+
JIRA_EMAIL = os.environ.get("JIRA_EMAIL", "")
|
|
68
|
+
JIRA_API_TOKEN = os.environ.get("JIRA_API_TOKEN", "")
|
|
69
|
+
|
|
70
|
+
REPO_BASE_PATH = Path("./") # Overridden by --path at runtime.
|
|
71
|
+
CLAUDE_CODE_CMD = "claude" # Must be on $PATH.
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
# Main entry point
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
def main() -> None:
|
|
79
|
+
"""Parse CLI arguments and orchestrate the four-step workflow."""
|
|
80
|
+
|
|
81
|
+
parser = argparse.ArgumentParser(
|
|
82
|
+
prog="bob-dev",
|
|
83
|
+
description="BOB Dev – AI-assisted developer workflow tool.",
|
|
84
|
+
)
|
|
85
|
+
parser.add_argument(
|
|
86
|
+
"--task_id", type=str,
|
|
87
|
+
help="Jira task ID to process (e.g. PROJ-123).",
|
|
88
|
+
)
|
|
89
|
+
parser.add_argument(
|
|
90
|
+
"--path", type=str, default="./",
|
|
91
|
+
help="Path to the target project repository (default: current directory).",
|
|
92
|
+
)
|
|
93
|
+
parser.add_argument(
|
|
94
|
+
"--agent", type=str, choices=["GROK", "OPENAI"], default=AGENT,
|
|
95
|
+
help="LLM backend to use (default: GROK).",
|
|
96
|
+
)
|
|
97
|
+
parser.add_argument(
|
|
98
|
+
"--configure", action="store_true",
|
|
99
|
+
help="Run interactive setup to save API keys to .env.",
|
|
100
|
+
)
|
|
101
|
+
args = parser.parse_args()
|
|
102
|
+
|
|
103
|
+
# ── Interactive configuration wizard ────────────────────────────────────
|
|
104
|
+
if args.configure:
|
|
105
|
+
_run_configure()
|
|
106
|
+
sys.exit(0)
|
|
107
|
+
|
|
108
|
+
# ── Require task_id for normal workflow ──────────────────────────────────
|
|
109
|
+
if not args.task_id:
|
|
110
|
+
print_error("Jira task ID is required. Use --task_id PROJ-123.")
|
|
111
|
+
sys.exit(1)
|
|
112
|
+
|
|
113
|
+
task_id = args.task_id.strip().upper()
|
|
114
|
+
agent = args.agent.upper()
|
|
115
|
+
|
|
116
|
+
# ── Validate credentials before making any API calls ────────────────────
|
|
117
|
+
if not all([JIRA_URL, JIRA_EMAIL, JIRA_API_TOKEN]):
|
|
118
|
+
print_error("Jira credentials are not configured. Run `bob-dev --configure`.")
|
|
119
|
+
sys.exit(1)
|
|
120
|
+
|
|
121
|
+
if agent == "GROK" and not GROK_API_KEY:
|
|
122
|
+
print_error("GROK_API_KEY is not set. Run `bob-dev --configure`.")
|
|
123
|
+
sys.exit(1)
|
|
124
|
+
|
|
125
|
+
if agent == "OPENAI" and not OPENAI_API_KEY:
|
|
126
|
+
print_error("OPENAI_API_KEY is not set. Run `bob-dev --configure`.")
|
|
127
|
+
sys.exit(1)
|
|
128
|
+
|
|
129
|
+
# ── Resolve and validate the repository path ─────────────────────────────
|
|
130
|
+
global REPO_BASE_PATH
|
|
131
|
+
REPO_BASE_PATH = Path(args.path).resolve()
|
|
132
|
+
|
|
133
|
+
if not REPO_BASE_PATH.exists() or not REPO_BASE_PATH.is_dir():
|
|
134
|
+
print_error(f"Invalid project path: {REPO_BASE_PATH}")
|
|
135
|
+
sys.exit(1)
|
|
136
|
+
|
|
137
|
+
print_info(f"Project path : {REPO_BASE_PATH}")
|
|
138
|
+
print_info(f"Jira task ID : {task_id}")
|
|
139
|
+
print_info(f"LLM backend : {agent} ({llm_model(agent)})")
|
|
140
|
+
print()
|
|
141
|
+
|
|
142
|
+
# ── Step 1 – Fetch Jira task ─────────────────────────────────────────────
|
|
143
|
+
print_step("[1/4]", f"Fetching Jira task {task_id} …")
|
|
144
|
+
|
|
145
|
+
task = asyncio.run(run_with_spinner(
|
|
146
|
+
get_jira_task,
|
|
147
|
+
task_id, JIRA_URL, JIRA_EMAIL, JIRA_API_TOKEN,
|
|
148
|
+
label="Fetching Jira task",
|
|
149
|
+
))
|
|
150
|
+
|
|
151
|
+
print_success(f"Title : {task['title']}")
|
|
152
|
+
print_success(f"Fix versions : {', '.join(task['fix_versions']) or 'N/A'}")
|
|
153
|
+
print()
|
|
154
|
+
|
|
155
|
+
acceptance_criteria = task["description"]
|
|
156
|
+
if not acceptance_criteria.strip():
|
|
157
|
+
print_error("The Jira task has no description / acceptance criteria.")
|
|
158
|
+
sys.exit(1)
|
|
159
|
+
|
|
160
|
+
# ── Step 2 – Read project docs & generate prompt ─────────────────────────
|
|
161
|
+
print_step("[2/4]", f"Generating Claude Code prompt via {agent} ({llm_model(agent)}) …")
|
|
162
|
+
|
|
163
|
+
# Collect Markdown context (blocking I/O) inside the spinner thread.
|
|
164
|
+
md_context = asyncio.run(run_with_spinner(
|
|
165
|
+
build_md_context, REPO_BASE_PATH,
|
|
166
|
+
label="Reading project docs",
|
|
167
|
+
))
|
|
168
|
+
|
|
169
|
+
# Framework detection is fast – no spinner needed.
|
|
170
|
+
try:
|
|
171
|
+
framework = identify_framework(md_context)
|
|
172
|
+
print_success(f"Detected framework : {framework}")
|
|
173
|
+
except ValueError as exc:
|
|
174
|
+
print_warn(str(exc))
|
|
175
|
+
framework = "the project"
|
|
176
|
+
|
|
177
|
+
prompt_md = asyncio.run(run_with_spinner(
|
|
178
|
+
prompt_claude_code,
|
|
179
|
+
acceptance_criteria, md_context, framework,
|
|
180
|
+
agent, GROK_API_KEY, OPENAI_API_KEY,
|
|
181
|
+
label="Generating prompt",
|
|
182
|
+
task_meta=task,
|
|
183
|
+
))
|
|
184
|
+
|
|
185
|
+
if not prompt_md.strip():
|
|
186
|
+
print_error("Failed to generate a prompt for Claude Code.")
|
|
187
|
+
sys.exit(1)
|
|
188
|
+
|
|
189
|
+
print_success("Prompt generated.")
|
|
190
|
+
print()
|
|
191
|
+
|
|
192
|
+
# ── Step 3 – Analyse the prompt ──────────────────────────────────────────
|
|
193
|
+
print_step("[3/4]", "Analysing the prompt for issues …")
|
|
194
|
+
|
|
195
|
+
analysis = asyncio.run(run_with_spinner(
|
|
196
|
+
analyse_prompt,
|
|
197
|
+
prompt_md, acceptance_criteria,
|
|
198
|
+
agent, GROK_API_KEY, OPENAI_API_KEY,
|
|
199
|
+
label="Analysing prompt",
|
|
200
|
+
))
|
|
201
|
+
|
|
202
|
+
print(f"\n{BOLD}── Prompt Analysis {'─' * 50}{RESET}")
|
|
203
|
+
print(analysis)
|
|
204
|
+
print("─" * 68 + "\n")
|
|
205
|
+
|
|
206
|
+
# ── Confirm before handing off to Claude Code ────────────────────────────
|
|
207
|
+
answer = input("Proceed and send prompt to Claude Code? [y/N] ").strip().lower()
|
|
208
|
+
if answer != "y":
|
|
209
|
+
prompt_file = SCRIPT_DIR / f"claude_prompt-{task_id}.md"
|
|
210
|
+
prompt_file.write_text(prompt_md, encoding="utf-8")
|
|
211
|
+
print_info(f"Aborted. Prompt saved to {prompt_file}")
|
|
212
|
+
sys.exit(0)
|
|
213
|
+
|
|
214
|
+
# ── Step 4 – Pass prompt to Claude Code ──────────────────────────────────
|
|
215
|
+
print_step("[4/4]", "Passing prompt to Claude Code …")
|
|
216
|
+
print()
|
|
217
|
+
asyncio.run(_pass_to_claude_code(prompt_md, task_id))
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# ---------------------------------------------------------------------------
|
|
221
|
+
# Internal helpers
|
|
222
|
+
# ---------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
async def _pass_to_claude_code(prompt_md: str, task_id: str) -> None:
|
|
225
|
+
"""Write the prompt to a temp file, then run Claude Code non-interactively."""
|
|
226
|
+
|
|
227
|
+
# Persist the prompt so the user can review it regardless of outcome.
|
|
228
|
+
prompt_file = SCRIPT_DIR / "tmp" / f"claude_prompt_{task_id}.md"
|
|
229
|
+
prompt_file.parent.mkdir(parents=True, exist_ok=True)
|
|
230
|
+
prompt_file.write_text(prompt_md, encoding="utf-8")
|
|
231
|
+
print_info(f"Prompt saved to: {prompt_file}")
|
|
232
|
+
|
|
233
|
+
cmd = [CLAUDE_CODE_CMD, "--dangerously-skip-permissions", "--print", prompt_md]
|
|
234
|
+
print_info(f"Running: {' '.join(cmd[:2])} <prompt>")
|
|
235
|
+
print()
|
|
236
|
+
|
|
237
|
+
returncode, _, _ = await run_subprocess(cmd, REPO_BASE_PATH)
|
|
238
|
+
|
|
239
|
+
if returncode != 0:
|
|
240
|
+
print_warn(f"Claude Code exited with code {returncode}")
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _run_configure() -> None:
|
|
244
|
+
"""Interactive wizard to write API keys and Jira credentials to .env."""
|
|
245
|
+
env_path = SCRIPT_DIR / ".env"
|
|
246
|
+
print_step("[CONFIGURE]", "Running initial configuration …")
|
|
247
|
+
|
|
248
|
+
# Choose the LLM backend.
|
|
249
|
+
system_choice = inquirer.select(
|
|
250
|
+
message="Select the default LLM backend:",
|
|
251
|
+
choices=["GROK", "OPENAI"],
|
|
252
|
+
).execute()
|
|
253
|
+
|
|
254
|
+
# Collect the appropriate API key.
|
|
255
|
+
print(f"Enter your {system_choice} API key:")
|
|
256
|
+
api_key = input("> ").strip()
|
|
257
|
+
env_key = "GROK_API_KEY" if system_choice == "GROK" else "OPENAI_API_KEY"
|
|
258
|
+
update_env_file(env_key, api_key, env_path)
|
|
259
|
+
print_success(f"{system_choice} API key saved.")
|
|
260
|
+
|
|
261
|
+
# Jira credentials.
|
|
262
|
+
print("\nJira configuration:")
|
|
263
|
+
jira_url = input("JIRA_URL (e.g. https://your-org.atlassian.net): ").strip()
|
|
264
|
+
jira_email = input("JIRA_EMAIL (your Atlassian account e-mail) : ").strip()
|
|
265
|
+
jira_token = input("JIRA_API_TOKEN (Atlassian API token) : ").strip()
|
|
266
|
+
|
|
267
|
+
for key, val in [
|
|
268
|
+
("JIRA_URL", jira_url),
|
|
269
|
+
("JIRA_EMAIL", jira_email),
|
|
270
|
+
("JIRA_API_TOKEN", jira_token),
|
|
271
|
+
]:
|
|
272
|
+
update_env_file(key, val, env_path)
|
|
273
|
+
|
|
274
|
+
print_success("Jira configuration saved.")
|
|
275
|
+
|
|
276
|
+
# Claude Code API key.
|
|
277
|
+
print("\nClaude Code API key:")
|
|
278
|
+
claude_key = input("CLAUDE_API_KEY: ").strip()
|
|
279
|
+
update_env_file("CLAUDE_API_KEY", claude_key, env_path)
|
|
280
|
+
print_success("Claude Code configuration saved.")
|
|
281
|
+
|
|
282
|
+
# Optional: verify all keys immediately.
|
|
283
|
+
answer = input("\nVerify all keys now? [y/N] ").strip().lower()
|
|
284
|
+
if answer == "y":
|
|
285
|
+
check_configuration(
|
|
286
|
+
agent = os.environ.get("AGENT", "GROK").upper(),
|
|
287
|
+
grok_api_key = os.environ.get("GROK_API_KEY", ""),
|
|
288
|
+
openai_api_key = os.environ.get("OPENAI_API_KEY", ""),
|
|
289
|
+
jira_url = jira_url,
|
|
290
|
+
jira_email = jira_email,
|
|
291
|
+
jira_api_token = jira_token,
|
|
292
|
+
claude_cmd = CLAUDE_CODE_CMD,
|
|
293
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
AVAILABLE_FRAMEWORKS = [
|
|
2
|
+
"Django REST Framework",
|
|
3
|
+
"Flask",
|
|
4
|
+
"FastAPI",
|
|
5
|
+
"Express.js",
|
|
6
|
+
"Spring Boot",
|
|
7
|
+
"Ruby on Rails",
|
|
8
|
+
"Laravel",
|
|
9
|
+
"ASP.NET Core",
|
|
10
|
+
"Symfony",
|
|
11
|
+
"CakePHP",
|
|
12
|
+
"CodeIgniter",
|
|
13
|
+
"Angular",
|
|
14
|
+
"React",
|
|
15
|
+
"Vue.js",
|
|
16
|
+
"Svelte",
|
|
17
|
+
"Next.js",
|
|
18
|
+
"Nuxt.js",
|
|
19
|
+
"Flutter",
|
|
20
|
+
"React Native",
|
|
21
|
+
"Ionic",
|
|
22
|
+
"Xamarin",
|
|
23
|
+
"Electron",
|
|
24
|
+
"NestJS",
|
|
25
|
+
"AdonisJS",
|
|
26
|
+
"Hadoop",
|
|
27
|
+
"Spark",
|
|
28
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# helpers package – utility modules for bob_dev
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""config_helper.py
|
|
2
|
+
|
|
3
|
+
Configuration utilities for bob_dev:
|
|
4
|
+
- Write / update key-value pairs in the .env file.
|
|
5
|
+
- Validate all external service credentials and print coloured results.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import subprocess
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from dotenv import load_dotenv
|
|
15
|
+
|
|
16
|
+
from .terminal import print_error, print_success, print_info
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
# .env management
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
def update_env_file(key: str, value: str, env_path: Path) -> None:
|
|
24
|
+
"""Write or update *key=value* in *env_path* and apply it to os.environ.
|
|
25
|
+
|
|
26
|
+
If the key already exists in the file it is updated in place; otherwise
|
|
27
|
+
a new line is appended. The updated file is also re-loaded via dotenv.
|
|
28
|
+
"""
|
|
29
|
+
lines: list[str] = []
|
|
30
|
+
if env_path.exists():
|
|
31
|
+
lines = env_path.read_text(encoding="utf-8").splitlines()
|
|
32
|
+
|
|
33
|
+
updated = False
|
|
34
|
+
for i, line in enumerate(lines):
|
|
35
|
+
if line.startswith(f"{key}="):
|
|
36
|
+
lines[i] = f"{key}={value}"
|
|
37
|
+
updated = True
|
|
38
|
+
break
|
|
39
|
+
|
|
40
|
+
if not updated:
|
|
41
|
+
lines.append(f"{key}={value}")
|
|
42
|
+
|
|
43
|
+
env_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
44
|
+
os.environ[key] = value
|
|
45
|
+
load_dotenv(env_path)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# Configuration verification
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
def check_configuration(
|
|
53
|
+
agent: str,
|
|
54
|
+
grok_api_key: str,
|
|
55
|
+
openai_api_key: str,
|
|
56
|
+
jira_url: str,
|
|
57
|
+
jira_email: str,
|
|
58
|
+
jira_api_token: str,
|
|
59
|
+
claude_cmd: str,
|
|
60
|
+
) -> None:
|
|
61
|
+
"""Validate all API keys and the Claude Code CLI, printing coloured results.
|
|
62
|
+
|
|
63
|
+
Each check is independent: a failure in one does not stop the others.
|
|
64
|
+
"""
|
|
65
|
+
# ── LLM API key ───────────────────────────────────────────────────────
|
|
66
|
+
print_info(f"Checking {agent} API key …")
|
|
67
|
+
try:
|
|
68
|
+
from .llm_helper import build_llm_client, llm_model
|
|
69
|
+
|
|
70
|
+
client = build_llm_client(agent, grok_api_key, openai_api_key)
|
|
71
|
+
model = llm_model(agent)
|
|
72
|
+
response = client.chat.completions.create(
|
|
73
|
+
model=model,
|
|
74
|
+
messages=[{"role": "system", "content": "Say: API key OK"}],
|
|
75
|
+
temperature=0,
|
|
76
|
+
)
|
|
77
|
+
reply = response.choices[0].message.content or ""
|
|
78
|
+
print_success(f"{agent} API key working. Response: {reply}")
|
|
79
|
+
except Exception as exc:
|
|
80
|
+
print_error(f"Failed to connect to {agent} API: {exc}")
|
|
81
|
+
|
|
82
|
+
# ── Jira credentials ──────────────────────────────────────────────────
|
|
83
|
+
print_info("Checking Jira credentials …")
|
|
84
|
+
try:
|
|
85
|
+
from atlassian import Jira
|
|
86
|
+
|
|
87
|
+
jira = Jira(url=jira_url, username=jira_email, password=jira_api_token, cloud=True)
|
|
88
|
+
user = jira.myself()
|
|
89
|
+
print_success(f"Jira credentials OK. Authenticated as: {user.get('displayName')}")
|
|
90
|
+
except Exception as exc:
|
|
91
|
+
print_error(f"Failed to connect to Jira: {exc}")
|
|
92
|
+
|
|
93
|
+
# ── Claude Code CLI ───────────────────────────────────────────────────
|
|
94
|
+
print_info(f"Checking Claude Code CLI ({claude_cmd}) …")
|
|
95
|
+
try:
|
|
96
|
+
result = subprocess.run(
|
|
97
|
+
[claude_cmd, "--version"],
|
|
98
|
+
capture_output=True,
|
|
99
|
+
text=True,
|
|
100
|
+
timeout=10,
|
|
101
|
+
)
|
|
102
|
+
if result.returncode == 0:
|
|
103
|
+
print_success(f"Claude Code CLI OK. Version: {result.stdout.strip()}")
|
|
104
|
+
else:
|
|
105
|
+
print_error(f"Claude Code CLI error: {result.stderr.strip()}")
|
|
106
|
+
except Exception as exc:
|
|
107
|
+
print_error(f"Failed to run Claude Code CLI: {exc}")
|