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.
Files changed (36) hide show
  1. {bob_dev-0.3.0 → bob_dev-0.4.4}/PKG-INFO +68 -15
  2. bob_dev-0.3.0/src/bob_dev.egg-info/PKG-INFO → bob_dev-0.4.4/README.md +52 -26
  3. bob_dev-0.4.4/pyproject.toml +40 -0
  4. {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/cli.py +190 -29
  5. bob_dev-0.4.4/src/bob_dev/services/langchain_service.py +129 -0
  6. {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/services/llm.py +24 -0
  7. bob_dev-0.4.4/src/bob_dev/services/rag.py +155 -0
  8. {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/services/terminal.py +13 -0
  9. bob_dev-0.4.4/src/bob_dev.egg-info/PKG-INFO +177 -0
  10. {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev.egg-info/SOURCES.txt +2 -0
  11. bob_dev-0.4.4/src/bob_dev.egg-info/requires.txt +18 -0
  12. {bob_dev-0.3.0 → bob_dev-0.4.4}/tests/test_cli.py +76 -12
  13. {bob_dev-0.3.0 → bob_dev-0.4.4}/tests/test_llm.py +28 -0
  14. bob_dev-0.3.0/README.md +0 -111
  15. bob_dev-0.3.0/pyproject.toml +0 -22
  16. bob_dev-0.3.0/src/bob_dev.egg-info/requires.txt +0 -4
  17. {bob_dev-0.3.0 → bob_dev-0.4.4}/LICENSE +0 -0
  18. {bob_dev-0.3.0 → bob_dev-0.4.4}/setup.cfg +0 -0
  19. {bob_dev-0.3.0 → bob_dev-0.4.4}/src/__init__.py +0 -0
  20. {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/__init__.py +0 -0
  21. {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/constants/__init__.py +0 -0
  22. {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/constants/frameworks.py +0 -0
  23. {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/services/__init__.py +0 -0
  24. {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/services/claude.py +0 -0
  25. {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/services/config.py +0 -0
  26. {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/services/gitlab.py +0 -0
  27. {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/services/jira.py +0 -0
  28. {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/services/project.py +0 -0
  29. {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev/settings.py +0 -0
  30. {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev.egg-info/dependency_links.txt +0 -0
  31. {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev.egg-info/entry_points.txt +0 -0
  32. {bob_dev-0.3.0 → bob_dev-0.4.4}/src/bob_dev.egg-info/top_level.txt +0 -0
  33. {bob_dev-0.3.0 → bob_dev-0.4.4}/tests/test_config.py +0 -0
  34. {bob_dev-0.3.0 → bob_dev-0.4.4}/tests/test_jira.py +0 -0
  35. {bob_dev-0.3.0 → bob_dev-0.4.4}/tests/test_project.py +0 -0
  36. {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.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.12.10
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 a task requirements, your codebase, and Claude Code.
30
+ **BOB Dev** is an AI-powered developer workflow CLI that bridges task requirements, your codebase, and Claude Code.
17
31
 
18
32
  ![BOB-Dev Banner](https://github.com/samuelsantosdev/bob-dev/blob/main/assets/banner.png)
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. **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. **Select** (optionally) a Claude Code agent to run the implementation.
27
- 6. **Execute** the prompt with the Claude Code CLI (optional – you can review first).
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.12+
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 will prompt for your LLM API key, task manager (Jira or GitLab) credentials, and Claude Code API key.
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 to process — Jira key (`PROJ-123`) or GitLab issue IID (`42`) |
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` (default: value of `AGENT` in `.env`) |
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 a task requirements, your codebase, and Claude Code.
3
+ **BOB Dev** is an AI-powered developer workflow CLI that bridges task requirements, your codebase, and Claude Code.
17
4
 
18
5
  ![BOB-Dev Banner](https://github.com/samuelsantosdev/bob-dev/blob/main/assets/banner.png)
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. **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. **Select** (optionally) a Claude Code agent to run the implementation.
27
- 6. **Execute** the prompt with the Claude Code CLI (optional – you can review first).
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.12+
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 will prompt for your LLM API key, task manager (Jira or GitLab) credentials, and Claude Code API key.
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 to process — Jira key (`PROJ-123`) or GitLab issue IID (`42`) |
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` (default: value of `AGENT` in `.env`) |
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, llm_model, prompt_claude_code, review_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 and orchestrate the four-step workflow."""
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="Task ID to process (e.g. PROJ-123 for Jira or 42 for GitLab).",
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
- # ── Require task_id for normal workflow ──────────────────────────────────
126
+ # ── No task_id on the command line: drop into the interactive REPL ──────
121
127
  if not args.task_id:
122
- print_error("Task ID is required. Use --task_id PROJ-123 for Jira or 42 for GitLab.")
123
- sys.exit(1)
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
- task_id = args.task_id.strip().upper()
126
- agent = args.agent.upper()
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(args.path).resolve()
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/4]", f"Fetching {TASK_MANAGER} task {task_id} …")
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 & generate prompt ─────────────────────────
186
- print_step("[2/4]", f"Generating Claude Code prompt via {agent} ({llm_model(agent)}) …")
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
- prompt_claude_code,
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 3 – Analyse the prompt ──────────────────────────────────────────
220
- print_step("[3/4]", "Analysing the prompt for issues …")
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
- for line in analysis.splitlines():
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("[4/4]", "Passing prompt to Claude Code …")
270
+ print_step("[5/5]", "Passing prompt to Claude Code …")
249
271
  print_success("Prompt preview:")
250
272
  print("-" * 68)
251
- for line in prompt_md.splitlines():
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 4 – Pass prompt to Claude Code ──────────────────────────────────
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
  # ---------------------------------------------------------------------------