bob-dev 0.3.0__tar.gz → 0.4.4__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.3.0 → bob_dev-0.4.4}/PKG-INFO +68 -15
- bob_dev-0.3.0/src/bob_dev.egg-info/PKG-INFO → bob_dev-0.4.4/README.md +52 -26
- bob_dev-0.4.4/pyproject.toml +40 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/cli.py +190 -29
- bob_dev-0.4.4/src/bob_dev/services/langchain_service.py +129 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/services/llm.py +24 -0
- bob_dev-0.4.4/src/bob_dev/services/rag.py +155 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/services/terminal.py +13 -0
- bob_dev-0.4.4/src/bob_dev.egg-info/PKG-INFO +177 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev.egg-info/SOURCES.txt +2 -0
- bob_dev-0.4.4/src/bob_dev.egg-info/requires.txt +18 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/tests/test_cli.py +76 -12
- {bob_dev-0.3.0 → bob_dev-0.4.4}/tests/test_llm.py +28 -0
- bob_dev-0.3.0/README.md +0 -111
- bob_dev-0.3.0/pyproject.toml +0 -22
- bob_dev-0.3.0/src/bob_dev.egg-info/requires.txt +0 -4
- {bob_dev-0.3.0 → bob_dev-0.4.4}/LICENSE +0 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/setup.cfg +0 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/src/__init__.py +0 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/__init__.py +0 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/constants/__init__.py +0 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/constants/frameworks.py +0 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/services/__init__.py +0 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/services/claude.py +0 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/services/config.py +0 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/services/gitlab.py +0 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/services/jira.py +0 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/services/project.py +0 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/settings.py +0 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev.egg-info/dependency_links.txt +0 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev.egg-info/entry_points.txt +0 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev.egg-info/top_level.txt +0 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/tests/test_config.py +0 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/tests/test_jira.py +0 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/tests/test_project.py +0 -0
- {bob_dev-0.3.0 → bob_dev-0.4.4}/tests/test_terminal.py +0 -0
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bob-dev
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.4
|
|
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.11
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
License-File: LICENSE
|
|
9
|
+
Requires-Dist: faiss-cpu>=1.9.0
|
|
10
|
+
Requires-Dist: langchain>=0.3.0
|
|
11
|
+
Requires-Dist: langchain-community>=0.3.0
|
|
12
|
+
Requires-Dist: langchain-openai>=0.2.0
|
|
13
|
+
Requires-Dist: langchain-text-splitters>=0.3.0
|
|
14
|
+
Requires-Dist: openai>=1.50.0
|
|
15
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
16
|
+
Requires-Dist: InquirerPy>=0.3.4
|
|
17
|
+
Requires-Dist: pydantic>=2.0
|
|
18
|
+
Requires-Dist: atlassian-python-api>=3.41.0
|
|
19
|
+
Requires-Dist: python-gitlab>=4.4.0
|
|
20
|
+
Requires-Dist: summa>=1.2.0
|
|
21
|
+
Requires-Dist: beautifulsoup4>=4.12.0
|
|
22
|
+
Requires-Dist: rich>=13.0.0
|
|
9
23
|
Provides-Extra: dev
|
|
10
24
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
11
25
|
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
@@ -13,7 +27,7 @@ Dynamic: license-file
|
|
|
13
27
|
|
|
14
28
|
# BOB Dev
|
|
15
29
|
|
|
16
|
-
**BOB Dev** is an AI-powered developer workflow CLI that bridges
|
|
30
|
+
**BOB Dev** is an AI-powered developer workflow CLI that bridges task requirements, your codebase, and Claude Code.
|
|
17
31
|
|
|
18
32
|

|
|
19
33
|
|
|
@@ -21,16 +35,17 @@ Given a task ID (Jira or GitLab) it will:
|
|
|
21
35
|
|
|
22
36
|
1. **Fetch** the task title, description, and fix versions from Jira or GitLab.
|
|
23
37
|
2. **Read** your project's Markdown documentation to build rich LLM context.
|
|
24
|
-
3. **
|
|
25
|
-
4. **
|
|
26
|
-
5. **
|
|
27
|
-
6. **
|
|
38
|
+
3. **Detect** the project framework (Django, React, FastAPI, Spring Boot, and more).
|
|
39
|
+
4. **Generate** a precise Claude Code prompt (via GROK or OpenAI), including framework context, implementation steps, and test scenarios.
|
|
40
|
+
5. **Analyse** the prompt for ambiguities and security concerns.
|
|
41
|
+
6. **Select** (optionally) a Claude Code agent to run the implementation.
|
|
42
|
+
7. **Execute** the prompt with the Claude Code CLI (optional — you can review and edit first).
|
|
28
43
|
|
|
29
44
|
---
|
|
30
45
|
|
|
31
46
|
## Requirements
|
|
32
47
|
|
|
33
|
-
- Python 3.
|
|
48
|
+
- Python 3.11+
|
|
34
49
|
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and available on `$PATH` as `claude`
|
|
35
50
|
- 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
51
|
- An [xAI / GROK](https://console.x.ai/) **or** [OpenAI](https://platform.openai.com/) API key
|
|
@@ -53,22 +68,42 @@ Run the interactive setup wizard the first time:
|
|
|
53
68
|
bob-dev --configure
|
|
54
69
|
```
|
|
55
70
|
|
|
56
|
-
This
|
|
71
|
+
This prompts for your LLM backend, API key, task manager (Jira or GitLab), and their credentials. All values are saved to `~/.bob_dev/.env`.
|
|
57
72
|
|
|
58
73
|
### Environment variables
|
|
59
74
|
|
|
75
|
+
**Required**
|
|
76
|
+
|
|
77
|
+
| Variable | Options | Description |
|
|
78
|
+
|----------|---------|-------------|
|
|
79
|
+
| `AGENT` | `GROK` (default) / `OPENAI` | LLM backend |
|
|
80
|
+
| `GROK_API_KEY` | — | xAI / GROK secret key (required if `AGENT=GROK`) |
|
|
81
|
+
| `OPENAI_API_KEY` | — | OpenAI secret key (required if `AGENT=OPENAI`) |
|
|
82
|
+
| `TASK_MANAGER` | `JIRA` (default) / `GITLAB` | Task management system |
|
|
83
|
+
|
|
84
|
+
**Jira** (`TASK_MANAGER=JIRA`)
|
|
85
|
+
|
|
60
86
|
| Variable | Description |
|
|
61
87
|
|----------|-------------|
|
|
62
|
-
| `AGENT` | LLM backend: `GROK` (default) or `OPENAI` |
|
|
63
|
-
| `GROK_API_KEY` | xAI / GROK secret key |
|
|
64
|
-
| `OPENAI_API_KEY` | OpenAI secret key |
|
|
65
|
-
| `TASK_MANAGER` | Task manager: `JIRA` (default) or `GITLAB` |
|
|
66
88
|
| `JIRA_URL` | e.g. `https://your-org.atlassian.net` |
|
|
67
89
|
| `JIRA_EMAIL` | Atlassian account e-mail |
|
|
68
90
|
| `JIRA_API_TOKEN` | Atlassian API token |
|
|
91
|
+
|
|
92
|
+
**GitLab** (`TASK_MANAGER=GITLAB`)
|
|
93
|
+
|
|
94
|
+
| Variable | Description |
|
|
95
|
+
|----------|-------------|
|
|
69
96
|
| `GITLAB_URL` | e.g. `https://gitlab.com` |
|
|
70
97
|
| `GITLAB_API_TOKEN` | GitLab personal access token |
|
|
71
98
|
|
|
99
|
+
**Optional**
|
|
100
|
+
|
|
101
|
+
| Variable | Default | Description |
|
|
102
|
+
|----------|---------|-------------|
|
|
103
|
+
| `MAX_FILES` | `30` | Max number of Markdown files read for project context |
|
|
104
|
+
| `MAX_CHARS` | `40000` | Max characters of Markdown content fed to the LLM |
|
|
105
|
+
| `MAX_SUMMARY_WORDS` | `2000` | Max words when auto-summarising the project README |
|
|
106
|
+
|
|
72
107
|
---
|
|
73
108
|
|
|
74
109
|
## Usage
|
|
@@ -83,9 +118,9 @@ bob-dev --task_id 42 --path /path/to/your/repo
|
|
|
83
118
|
|
|
84
119
|
| Flag | Description |
|
|
85
120
|
|------|-------------|
|
|
86
|
-
| `--task_id` | Task ID
|
|
121
|
+
| `--task_id` | Task ID — Jira key (`PROJ-123`) or GitLab issue IID (`42`) |
|
|
87
122
|
| `--path` | Path to the target repository (default: current directory) |
|
|
88
|
-
| `--agent` | LLM backend: `GROK` or `OPENAI` (
|
|
123
|
+
| `--agent` | LLM backend: `GROK` or `OPENAI` (overrides the `AGENT` env var) |
|
|
89
124
|
| `--configure` | Run the interactive configuration wizard |
|
|
90
125
|
|
|
91
126
|
---
|
|
@@ -95,6 +130,7 @@ bob-dev --task_id 42 --path /path/to/your/repo
|
|
|
95
130
|
```
|
|
96
131
|
src/bob_dev/
|
|
97
132
|
├── cli.py # Entry point & main workflow orchestration
|
|
133
|
+
├── settings.py # Environment variable loading and validation
|
|
98
134
|
├── services/
|
|
99
135
|
│ ├── terminal.py # ANSI colours, print helpers, spinner animation
|
|
100
136
|
│ ├── jira.py # Jira API connection + ADF-to-text parsing
|
|
@@ -105,6 +141,22 @@ src/bob_dev/
|
|
|
105
141
|
│ └── config.py # .env management + credential validation
|
|
106
142
|
└── constants/
|
|
107
143
|
└── frameworks.py # Known framework names used for auto-detection
|
|
144
|
+
|
|
145
|
+
tests/
|
|
146
|
+
├── test_cli.py
|
|
147
|
+
├── test_config.py
|
|
148
|
+
├── test_jira.py
|
|
149
|
+
├── test_llm.py
|
|
150
|
+
├── test_project.py
|
|
151
|
+
└── test_terminal.py
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Running tests
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
pytest tests/
|
|
108
160
|
```
|
|
109
161
|
|
|
110
162
|
---
|
|
@@ -115,6 +167,7 @@ src/bob_dev/
|
|
|
115
167
|
|--------|---------|
|
|
116
168
|
| Red | Errors that stop execution |
|
|
117
169
|
| Green | Success messages |
|
|
170
|
+
| Yellow | Warnings |
|
|
118
171
|
| Plain | Informational / default output |
|
|
119
172
|
|
|
120
173
|
---
|
|
@@ -1,19 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: bob-dev
|
|
3
|
-
Version: 0.3.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.12.10
|
|
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
1
|
# BOB Dev
|
|
15
2
|
|
|
16
|
-
**BOB Dev** is an AI-powered developer workflow CLI that bridges
|
|
3
|
+
**BOB Dev** is an AI-powered developer workflow CLI that bridges task requirements, your codebase, and Claude Code.
|
|
17
4
|
|
|
18
5
|

|
|
19
6
|
|
|
@@ -21,16 +8,17 @@ Given a task ID (Jira or GitLab) it will:
|
|
|
21
8
|
|
|
22
9
|
1. **Fetch** the task title, description, and fix versions from Jira or GitLab.
|
|
23
10
|
2. **Read** your project's Markdown documentation to build rich LLM context.
|
|
24
|
-
3. **
|
|
25
|
-
4. **
|
|
26
|
-
5. **
|
|
27
|
-
6. **
|
|
11
|
+
3. **Detect** the project framework (Django, React, FastAPI, Spring Boot, and more).
|
|
12
|
+
4. **Generate** a precise Claude Code prompt (via GROK or OpenAI), including framework context, implementation steps, and test scenarios.
|
|
13
|
+
5. **Analyse** the prompt for ambiguities and security concerns.
|
|
14
|
+
6. **Select** (optionally) a Claude Code agent to run the implementation.
|
|
15
|
+
7. **Execute** the prompt with the Claude Code CLI (optional — you can review and edit first).
|
|
28
16
|
|
|
29
17
|
---
|
|
30
18
|
|
|
31
19
|
## Requirements
|
|
32
20
|
|
|
33
|
-
- Python 3.
|
|
21
|
+
- Python 3.11+
|
|
34
22
|
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and available on `$PATH` as `claude`
|
|
35
23
|
- 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
24
|
- An [xAI / GROK](https://console.x.ai/) **or** [OpenAI](https://platform.openai.com/) API key
|
|
@@ -53,22 +41,42 @@ Run the interactive setup wizard the first time:
|
|
|
53
41
|
bob-dev --configure
|
|
54
42
|
```
|
|
55
43
|
|
|
56
|
-
This
|
|
44
|
+
This prompts for your LLM backend, API key, task manager (Jira or GitLab), and their credentials. All values are saved to `~/.bob_dev/.env`.
|
|
57
45
|
|
|
58
46
|
### Environment variables
|
|
59
47
|
|
|
48
|
+
**Required**
|
|
49
|
+
|
|
50
|
+
| Variable | Options | Description |
|
|
51
|
+
|----------|---------|-------------|
|
|
52
|
+
| `AGENT` | `GROK` (default) / `OPENAI` | LLM backend |
|
|
53
|
+
| `GROK_API_KEY` | — | xAI / GROK secret key (required if `AGENT=GROK`) |
|
|
54
|
+
| `OPENAI_API_KEY` | — | OpenAI secret key (required if `AGENT=OPENAI`) |
|
|
55
|
+
| `TASK_MANAGER` | `JIRA` (default) / `GITLAB` | Task management system |
|
|
56
|
+
|
|
57
|
+
**Jira** (`TASK_MANAGER=JIRA`)
|
|
58
|
+
|
|
60
59
|
| Variable | Description |
|
|
61
60
|
|----------|-------------|
|
|
62
|
-
| `AGENT` | LLM backend: `GROK` (default) or `OPENAI` |
|
|
63
|
-
| `GROK_API_KEY` | xAI / GROK secret key |
|
|
64
|
-
| `OPENAI_API_KEY` | OpenAI secret key |
|
|
65
|
-
| `TASK_MANAGER` | Task manager: `JIRA` (default) or `GITLAB` |
|
|
66
61
|
| `JIRA_URL` | e.g. `https://your-org.atlassian.net` |
|
|
67
62
|
| `JIRA_EMAIL` | Atlassian account e-mail |
|
|
68
63
|
| `JIRA_API_TOKEN` | Atlassian API token |
|
|
64
|
+
|
|
65
|
+
**GitLab** (`TASK_MANAGER=GITLAB`)
|
|
66
|
+
|
|
67
|
+
| Variable | Description |
|
|
68
|
+
|----------|-------------|
|
|
69
69
|
| `GITLAB_URL` | e.g. `https://gitlab.com` |
|
|
70
70
|
| `GITLAB_API_TOKEN` | GitLab personal access token |
|
|
71
71
|
|
|
72
|
+
**Optional**
|
|
73
|
+
|
|
74
|
+
| Variable | Default | Description |
|
|
75
|
+
|----------|---------|-------------|
|
|
76
|
+
| `MAX_FILES` | `30` | Max number of Markdown files read for project context |
|
|
77
|
+
| `MAX_CHARS` | `40000` | Max characters of Markdown content fed to the LLM |
|
|
78
|
+
| `MAX_SUMMARY_WORDS` | `2000` | Max words when auto-summarising the project README |
|
|
79
|
+
|
|
72
80
|
---
|
|
73
81
|
|
|
74
82
|
## Usage
|
|
@@ -83,9 +91,9 @@ bob-dev --task_id 42 --path /path/to/your/repo
|
|
|
83
91
|
|
|
84
92
|
| Flag | Description |
|
|
85
93
|
|------|-------------|
|
|
86
|
-
| `--task_id` | Task ID
|
|
94
|
+
| `--task_id` | Task ID — Jira key (`PROJ-123`) or GitLab issue IID (`42`) |
|
|
87
95
|
| `--path` | Path to the target repository (default: current directory) |
|
|
88
|
-
| `--agent` | LLM backend: `GROK` or `OPENAI` (
|
|
96
|
+
| `--agent` | LLM backend: `GROK` or `OPENAI` (overrides the `AGENT` env var) |
|
|
89
97
|
| `--configure` | Run the interactive configuration wizard |
|
|
90
98
|
|
|
91
99
|
---
|
|
@@ -95,6 +103,7 @@ bob-dev --task_id 42 --path /path/to/your/repo
|
|
|
95
103
|
```
|
|
96
104
|
src/bob_dev/
|
|
97
105
|
├── cli.py # Entry point & main workflow orchestration
|
|
106
|
+
├── settings.py # Environment variable loading and validation
|
|
98
107
|
├── services/
|
|
99
108
|
│ ├── terminal.py # ANSI colours, print helpers, spinner animation
|
|
100
109
|
│ ├── jira.py # Jira API connection + ADF-to-text parsing
|
|
@@ -105,6 +114,22 @@ src/bob_dev/
|
|
|
105
114
|
│ └── config.py # .env management + credential validation
|
|
106
115
|
└── constants/
|
|
107
116
|
└── frameworks.py # Known framework names used for auto-detection
|
|
117
|
+
|
|
118
|
+
tests/
|
|
119
|
+
├── test_cli.py
|
|
120
|
+
├── test_config.py
|
|
121
|
+
├── test_jira.py
|
|
122
|
+
├── test_llm.py
|
|
123
|
+
├── test_project.py
|
|
124
|
+
└── test_terminal.py
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Running tests
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
pytest tests/
|
|
108
133
|
```
|
|
109
134
|
|
|
110
135
|
---
|
|
@@ -115,6 +140,7 @@ src/bob_dev/
|
|
|
115
140
|
|--------|---------|
|
|
116
141
|
| Red | Errors that stop execution |
|
|
117
142
|
| Green | Success messages |
|
|
143
|
+
| Yellow | Warnings |
|
|
118
144
|
| Plain | Informational / default output |
|
|
119
145
|
|
|
120
146
|
---
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "bob-dev"
|
|
7
|
+
version = "0.4.4"
|
|
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.11"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"faiss-cpu>=1.9.0",
|
|
14
|
+
"langchain>=0.3.0",
|
|
15
|
+
"langchain-community>=0.3.0",
|
|
16
|
+
"langchain-openai>=0.2.0",
|
|
17
|
+
"langchain-text-splitters>=0.3.0",
|
|
18
|
+
"openai>=1.50.0",
|
|
19
|
+
"python-dotenv>=1.0.0",
|
|
20
|
+
"InquirerPy>=0.3.4",
|
|
21
|
+
"pydantic>=2.0",
|
|
22
|
+
"atlassian-python-api>=3.41.0",
|
|
23
|
+
"python-gitlab>=4.4.0",
|
|
24
|
+
"summa>=1.2.0",
|
|
25
|
+
"beautifulsoup4>=4.12.0",
|
|
26
|
+
"rich>=13.0.0",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
bob-dev = "bob_dev.cli:main"
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = ["pytest>=8.0", "pytest-asyncio>=0.23"]
|
|
34
|
+
|
|
35
|
+
[tool.pytest.ini_options]
|
|
36
|
+
testpaths = ["tests"]
|
|
37
|
+
asyncio_mode = "auto"
|
|
38
|
+
filterwarnings = [
|
|
39
|
+
"ignore:.*langchain-community.*:DeprecationWarning",
|
|
40
|
+
]
|
|
@@ -13,6 +13,7 @@ Workflow
|
|
|
13
13
|
|
|
14
14
|
Usage
|
|
15
15
|
-----
|
|
16
|
+
bob-dev # interactive shell (REPL)
|
|
16
17
|
bob-dev --task_id PROJ-123 --path /path/to/repo
|
|
17
18
|
bob-dev --configure
|
|
18
19
|
|
|
@@ -33,7 +34,6 @@ import sys
|
|
|
33
34
|
import argparse
|
|
34
35
|
import asyncio
|
|
35
36
|
from pathlib import Path
|
|
36
|
-
from time import sleep
|
|
37
37
|
|
|
38
38
|
from InquirerPy import inquirer
|
|
39
39
|
from dotenv import load_dotenv
|
|
@@ -43,6 +43,7 @@ from .services.terminal import (
|
|
|
43
43
|
RESET,
|
|
44
44
|
print_error,
|
|
45
45
|
print_info,
|
|
46
|
+
print_markdown,
|
|
46
47
|
print_step,
|
|
47
48
|
print_success,
|
|
48
49
|
print_warn,
|
|
@@ -51,9 +52,11 @@ from .services.terminal import (
|
|
|
51
52
|
)
|
|
52
53
|
from .services.jira import get_jira_task
|
|
53
54
|
from .services.gitlab import get_gitlab_task
|
|
54
|
-
from .services.llm import analyse_prompt,
|
|
55
|
+
from .services.llm import analyse_prompt, chat_completion, llm_model, review_prompt
|
|
55
56
|
from .services.project import build_md_context, identify_framework
|
|
56
57
|
from .services.config import check_configuration, update_env_file
|
|
58
|
+
from .services.langchain_service import run_langchain_chain
|
|
59
|
+
from .services.rag import retrieve_rag_context
|
|
57
60
|
|
|
58
61
|
# ---------------------------------------------------------------------------
|
|
59
62
|
# Module-level configuration
|
|
@@ -88,7 +91,7 @@ MAX_SUMMARY_WORDS = int(os.environ.get("MAX_SUMMARY_WORDS", 2000))
|
|
|
88
91
|
# ---------------------------------------------------------------------------
|
|
89
92
|
|
|
90
93
|
def main() -> None:
|
|
91
|
-
"""Parse CLI arguments
|
|
94
|
+
"""Parse CLI arguments; run one-shot task workflow, --configure, or the REPL."""
|
|
92
95
|
|
|
93
96
|
parser = argparse.ArgumentParser(
|
|
94
97
|
prog="bob-dev",
|
|
@@ -96,7 +99,10 @@ def main() -> None:
|
|
|
96
99
|
)
|
|
97
100
|
parser.add_argument(
|
|
98
101
|
"--task_id", type=str,
|
|
99
|
-
help=
|
|
102
|
+
help=(
|
|
103
|
+
"Task ID to process (e.g. PROJ-123 for Jira or 42 for GitLab). "
|
|
104
|
+
"Omit to start the interactive shell instead."
|
|
105
|
+
),
|
|
100
106
|
)
|
|
101
107
|
parser.add_argument(
|
|
102
108
|
"--path", type=str, default="./",
|
|
@@ -117,13 +123,18 @@ def main() -> None:
|
|
|
117
123
|
_run_configure()
|
|
118
124
|
sys.exit(0)
|
|
119
125
|
|
|
120
|
-
# ──
|
|
126
|
+
# ── No task_id on the command line: drop into the interactive REPL ──────
|
|
121
127
|
if not args.task_id:
|
|
122
|
-
|
|
123
|
-
sys.exit(
|
|
128
|
+
_run_repl(args.agent.upper(), args.path)
|
|
129
|
+
sys.exit(0)
|
|
130
|
+
|
|
131
|
+
_run_one_shot(args.task_id, args.path, args.agent.upper())
|
|
124
132
|
|
|
125
|
-
|
|
126
|
-
|
|
133
|
+
|
|
134
|
+
def _run_one_shot(task_id_raw: str, path: str, agent: str) -> None:
|
|
135
|
+
"""Run the full fetch → context → RAG → prompt → execute workflow once."""
|
|
136
|
+
|
|
137
|
+
task_id = task_id_raw.strip().upper()
|
|
127
138
|
|
|
128
139
|
# ── Validate credentials before making any API calls ────────────────────
|
|
129
140
|
if TASK_MANAGER == "JIRA" and not all([JIRA_URL, JIRA_EMAIL, JIRA_API_TOKEN]):
|
|
@@ -144,7 +155,7 @@ def main() -> None:
|
|
|
144
155
|
|
|
145
156
|
# ── Resolve and validate the repository path ─────────────────────────────
|
|
146
157
|
global REPO_BASE_PATH
|
|
147
|
-
REPO_BASE_PATH = Path(
|
|
158
|
+
REPO_BASE_PATH = Path(path).resolve()
|
|
148
159
|
|
|
149
160
|
if not REPO_BASE_PATH.exists() or not REPO_BASE_PATH.is_dir():
|
|
150
161
|
print_error(f"Invalid project path: {REPO_BASE_PATH}")
|
|
@@ -156,7 +167,7 @@ def main() -> None:
|
|
|
156
167
|
print()
|
|
157
168
|
|
|
158
169
|
# ── Step 1 – Fetch task ─────────────────────────────────────────────
|
|
159
|
-
print_step("[1/
|
|
170
|
+
print_step("[1/5]", f"Fetching {TASK_MANAGER} task {task_id} …")
|
|
160
171
|
|
|
161
172
|
if TASK_MANAGER == "JIRA":
|
|
162
173
|
task = asyncio.run(run_with_spinner(
|
|
@@ -182,16 +193,14 @@ def main() -> None:
|
|
|
182
193
|
sys.exit(1)
|
|
183
194
|
|
|
184
195
|
|
|
185
|
-
# ── Step 2 – Read project docs
|
|
186
|
-
print_step("[2/
|
|
196
|
+
# ── Step 2 – Read project docs ───────────────────────────────────────────
|
|
197
|
+
print_step("[2/5]", "Reading project documentation …")
|
|
187
198
|
|
|
188
|
-
# Collect Markdown context (blocking I/O) inside the spinner thread.
|
|
189
199
|
md_context = asyncio.run(run_with_spinner(
|
|
190
200
|
build_md_context, REPO_BASE_PATH, MAX_SUMMARY_WORDS,
|
|
191
201
|
label="Reading project docs",
|
|
192
202
|
))
|
|
193
203
|
|
|
194
|
-
# Framework detection is fast – no spinner needed.
|
|
195
204
|
try:
|
|
196
205
|
framework = identify_framework(md_context)
|
|
197
206
|
print_success(f"Detected framework : {framework}")
|
|
@@ -199,14 +208,29 @@ def main() -> None:
|
|
|
199
208
|
print_warn(str(exc))
|
|
200
209
|
framework = "the project"
|
|
201
210
|
|
|
211
|
+
# ── Step 3 – Build RAG index from framework documentation ────────────────
|
|
212
|
+
print_step("[3/5]", f"Building RAG index for {framework} …")
|
|
213
|
+
|
|
214
|
+
rag_context = asyncio.run(run_with_spinner(
|
|
215
|
+
retrieve_rag_context,
|
|
216
|
+
framework, acceptance_criteria,
|
|
217
|
+
agent, GROK_API_KEY, OPENAI_API_KEY,
|
|
218
|
+
label="Building RAG context",
|
|
219
|
+
))
|
|
220
|
+
|
|
221
|
+
if rag_context:
|
|
222
|
+
print_success("RAG context ready.")
|
|
223
|
+
else:
|
|
224
|
+
print_warn(f"No documentation mapped for '{framework}' — RAG context skipped.")
|
|
225
|
+
print()
|
|
226
|
+
|
|
202
227
|
execute_prompt = False
|
|
203
228
|
while execute_prompt is False:
|
|
204
229
|
prompt_md = asyncio.run(run_with_spinner(
|
|
205
|
-
|
|
206
|
-
acceptance_criteria, md_context, framework,
|
|
207
|
-
agent, GROK_API_KEY, OPENAI_API_KEY,
|
|
230
|
+
run_langchain_chain,
|
|
231
|
+
acceptance_criteria, md_context, task_id, framework,
|
|
232
|
+
agent, GROK_API_KEY, OPENAI_API_KEY, rag_context,
|
|
208
233
|
label="Generating prompt",
|
|
209
|
-
task_meta=task,
|
|
210
234
|
))
|
|
211
235
|
|
|
212
236
|
if not prompt_md.strip():
|
|
@@ -216,8 +240,8 @@ def main() -> None:
|
|
|
216
240
|
print_success("Prompt generated.")
|
|
217
241
|
print()
|
|
218
242
|
|
|
219
|
-
# ── Step
|
|
220
|
-
print_step("[
|
|
243
|
+
# ── Step 4 – Analyse the prompt ──────────────────────────────────────────
|
|
244
|
+
print_step("[4/5]", "Analysing the prompt for issues …")
|
|
221
245
|
|
|
222
246
|
analysis = asyncio.run(run_with_spinner(
|
|
223
247
|
analyse_prompt,
|
|
@@ -227,9 +251,7 @@ def main() -> None:
|
|
|
227
251
|
))
|
|
228
252
|
|
|
229
253
|
print(f"\n{BOLD}── Prompt Analysis {'─' * 50}{RESET}")
|
|
230
|
-
|
|
231
|
-
print(line)
|
|
232
|
-
sleep(0.02) # Simulate a "typing" effect for better readability
|
|
254
|
+
print_markdown(analysis)
|
|
233
255
|
print("─" * 68 + "\n")
|
|
234
256
|
|
|
235
257
|
# ── Confirm before handing off to Claude Code ────────────────────────────
|
|
@@ -245,12 +267,10 @@ def main() -> None:
|
|
|
245
267
|
continue # Regenerate the prompt with the new context and considerations.
|
|
246
268
|
|
|
247
269
|
print("\n\n")
|
|
248
|
-
print_step("[
|
|
270
|
+
print_step("[5/5]", "Passing prompt to Claude Code …")
|
|
249
271
|
print_success("Prompt preview:")
|
|
250
272
|
print("-" * 68)
|
|
251
|
-
|
|
252
|
-
print(line)
|
|
253
|
-
sleep(0.02) # Simulate a "typing" effect for better readability
|
|
273
|
+
print_markdown(prompt_md)
|
|
254
274
|
print("-" * 68)
|
|
255
275
|
answer = input("\nAre you sure? This will run the Claude Code CLI with the generated prompt. [y/N/c] ").strip().lower()
|
|
256
276
|
if answer == "n":
|
|
@@ -265,11 +285,152 @@ def main() -> None:
|
|
|
265
285
|
break
|
|
266
286
|
|
|
267
287
|
|
|
268
|
-
# ── Step
|
|
288
|
+
# ── Step 5 – Pass prompt to Claude Code ──────────────────────────────────
|
|
269
289
|
print()
|
|
270
290
|
asyncio.run(_pass_to_claude_code(prompt_md, task_id, None))
|
|
271
291
|
|
|
272
292
|
|
|
293
|
+
REPL_HELP = f"""\
|
|
294
|
+
{BOLD}BOB Dev interactive shell{RESET}
|
|
295
|
+
/task <ID> [--path P] Run the fetch → prompt → Claude Code workflow for a task.
|
|
296
|
+
/agent [GROK|OPENAI] Show or switch the LLM backend for this session.
|
|
297
|
+
/path [P] Show or set the target project path for this session.
|
|
298
|
+
/configure Re-run the setup wizard.
|
|
299
|
+
/reset Clear the chat history for this session.
|
|
300
|
+
/help Show this message.
|
|
301
|
+
/exit, /quit Leave the shell.
|
|
302
|
+
Anything else is sent as a chat message to the configured AI backend.
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _run_repl(agent: str, path: str) -> None:
|
|
307
|
+
"""Interactive shell: chat with the configured AI, or run task workflows on demand."""
|
|
308
|
+
|
|
309
|
+
session_agent = agent
|
|
310
|
+
session_path = path
|
|
311
|
+
history: list[dict] = [{
|
|
312
|
+
"role": "system",
|
|
313
|
+
"content": (
|
|
314
|
+
"You are BOB, a helpful assistant embedded in a developer's terminal. "
|
|
315
|
+
"You may be asked general questions or asked to reason about a software task. "
|
|
316
|
+
"Be concise and practical."
|
|
317
|
+
),
|
|
318
|
+
}]
|
|
319
|
+
|
|
320
|
+
print_step("[BOB]", f"Interactive mode — backend: {session_agent} ({llm_model(session_agent)})")
|
|
321
|
+
print_info("Type /help for commands, or just start chatting.")
|
|
322
|
+
print()
|
|
323
|
+
|
|
324
|
+
while True:
|
|
325
|
+
try:
|
|
326
|
+
line = input(f"{BOLD}bob-dev›{RESET} ").strip()
|
|
327
|
+
except (EOFError, KeyboardInterrupt):
|
|
328
|
+
print()
|
|
329
|
+
break
|
|
330
|
+
|
|
331
|
+
if not line:
|
|
332
|
+
continue
|
|
333
|
+
|
|
334
|
+
if line in ("/exit", "/quit"):
|
|
335
|
+
break
|
|
336
|
+
|
|
337
|
+
if line == "/help":
|
|
338
|
+
print(REPL_HELP)
|
|
339
|
+
continue
|
|
340
|
+
|
|
341
|
+
if line == "/reset":
|
|
342
|
+
history = history[:1]
|
|
343
|
+
print_success("Chat history cleared.")
|
|
344
|
+
continue
|
|
345
|
+
|
|
346
|
+
if line == "/configure":
|
|
347
|
+
_run_configure()
|
|
348
|
+
continue
|
|
349
|
+
|
|
350
|
+
if line.startswith("/agent"):
|
|
351
|
+
parts = line.split(maxsplit=1)
|
|
352
|
+
if len(parts) == 1:
|
|
353
|
+
print_info(f"Current backend: {session_agent} ({llm_model(session_agent)})")
|
|
354
|
+
continue
|
|
355
|
+
choice = parts[1].strip().upper()
|
|
356
|
+
if choice not in ("GROK", "OPENAI"):
|
|
357
|
+
print_error("Usage: /agent [GROK|OPENAI]")
|
|
358
|
+
continue
|
|
359
|
+
if choice == "GROK" and not GROK_API_KEY:
|
|
360
|
+
print_error("GROK API key is not configured. Run /configure.")
|
|
361
|
+
continue
|
|
362
|
+
if choice == "OPENAI" and not OPENAI_API_KEY:
|
|
363
|
+
print_error("OpenAI API key is not configured. Run /configure.")
|
|
364
|
+
continue
|
|
365
|
+
session_agent = choice
|
|
366
|
+
print_success(f"Backend switched to {session_agent} ({llm_model(session_agent)}).")
|
|
367
|
+
continue
|
|
368
|
+
|
|
369
|
+
if line.startswith("/path"):
|
|
370
|
+
parts = line.split(maxsplit=1)
|
|
371
|
+
if len(parts) == 1:
|
|
372
|
+
print_info(f"Current path: {Path(session_path).resolve()}")
|
|
373
|
+
continue
|
|
374
|
+
candidate = Path(parts[1].strip()).expanduser()
|
|
375
|
+
if not candidate.exists() or not candidate.is_dir():
|
|
376
|
+
print_error(f"Invalid project path: {candidate}")
|
|
377
|
+
continue
|
|
378
|
+
session_path = str(candidate)
|
|
379
|
+
print_success(f"Path set to {Path(session_path).resolve()}.")
|
|
380
|
+
continue
|
|
381
|
+
|
|
382
|
+
if line.startswith("/task"):
|
|
383
|
+
parts = line.split(maxsplit=1)
|
|
384
|
+
if len(parts) == 1 or not parts[1].strip():
|
|
385
|
+
print_error("Usage: /task <ID> [--path P]")
|
|
386
|
+
continue
|
|
387
|
+
|
|
388
|
+
task_args = parts[1].split()
|
|
389
|
+
task_id_raw = task_args[0]
|
|
390
|
+
task_path = session_path
|
|
391
|
+
if "--path" in task_args:
|
|
392
|
+
idx = task_args.index("--path")
|
|
393
|
+
if idx + 1 < len(task_args):
|
|
394
|
+
task_path = task_args[idx + 1]
|
|
395
|
+
|
|
396
|
+
try:
|
|
397
|
+
_run_one_shot(task_id_raw, task_path, session_agent)
|
|
398
|
+
except SystemExit as exc:
|
|
399
|
+
if exc.code not in (0, None):
|
|
400
|
+
print_error(f"Task workflow exited with code {exc.code}.")
|
|
401
|
+
print()
|
|
402
|
+
continue
|
|
403
|
+
|
|
404
|
+
if line.startswith("/"):
|
|
405
|
+
print_error(f"Unknown command: {line}. Type /help for a list of commands.")
|
|
406
|
+
continue
|
|
407
|
+
|
|
408
|
+
# ── Freeform chat with the configured AI backend ─────────────────────
|
|
409
|
+
if session_agent == "GROK" and not GROK_API_KEY:
|
|
410
|
+
print_error("GROK API key is not configured. Run /configure.")
|
|
411
|
+
continue
|
|
412
|
+
if session_agent == "OPENAI" and not OPENAI_API_KEY:
|
|
413
|
+
print_error("OpenAI API key is not configured. Run /configure.")
|
|
414
|
+
continue
|
|
415
|
+
|
|
416
|
+
history.append({"role": "user", "content": line})
|
|
417
|
+
try:
|
|
418
|
+
reply = asyncio.run(run_with_spinner(
|
|
419
|
+
chat_completion,
|
|
420
|
+
history, session_agent, GROK_API_KEY, OPENAI_API_KEY,
|
|
421
|
+
label="Thinking",
|
|
422
|
+
))
|
|
423
|
+
except Exception as exc: # noqa: BLE001 - surface any backend error to the user
|
|
424
|
+
print_error(f"Chat request failed: {exc}")
|
|
425
|
+
history.pop()
|
|
426
|
+
continue
|
|
427
|
+
|
|
428
|
+
history.append({"role": "assistant", "content": reply})
|
|
429
|
+
print(f"{BOLD}bob{RESET}:")
|
|
430
|
+
print_markdown(reply)
|
|
431
|
+
print()
|
|
432
|
+
|
|
433
|
+
|
|
273
434
|
# ---------------------------------------------------------------------------
|
|
274
435
|
# Internal helpers
|
|
275
436
|
# ---------------------------------------------------------------------------
|