titan-cli 0.1.0__py3-none-any.whl
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.
- titan_cli/__init__.py +3 -0
- titan_cli/__main__.py +4 -0
- titan_cli/ai/__init__.py +0 -0
- titan_cli/ai/agents/__init__.py +15 -0
- titan_cli/ai/agents/base.py +152 -0
- titan_cli/ai/client.py +170 -0
- titan_cli/ai/constants.py +56 -0
- titan_cli/ai/exceptions.py +48 -0
- titan_cli/ai/models.py +34 -0
- titan_cli/ai/oauth_helper.py +120 -0
- titan_cli/ai/providers/__init__.py +9 -0
- titan_cli/ai/providers/anthropic.py +117 -0
- titan_cli/ai/providers/base.py +75 -0
- titan_cli/ai/providers/gemini.py +278 -0
- titan_cli/cli.py +59 -0
- titan_cli/clients/__init__.py +1 -0
- titan_cli/clients/gcloud_client.py +52 -0
- titan_cli/core/__init__.py +3 -0
- titan_cli/core/config.py +274 -0
- titan_cli/core/discovery.py +51 -0
- titan_cli/core/errors.py +81 -0
- titan_cli/core/models.py +52 -0
- titan_cli/core/plugins/available.py +36 -0
- titan_cli/core/plugins/models.py +67 -0
- titan_cli/core/plugins/plugin_base.py +108 -0
- titan_cli/core/plugins/plugin_registry.py +163 -0
- titan_cli/core/secrets.py +141 -0
- titan_cli/core/workflows/__init__.py +22 -0
- titan_cli/core/workflows/models.py +88 -0
- titan_cli/core/workflows/project_step_source.py +86 -0
- titan_cli/core/workflows/workflow_exceptions.py +17 -0
- titan_cli/core/workflows/workflow_filter_service.py +137 -0
- titan_cli/core/workflows/workflow_registry.py +419 -0
- titan_cli/core/workflows/workflow_sources.py +307 -0
- titan_cli/engine/__init__.py +39 -0
- titan_cli/engine/builder.py +159 -0
- titan_cli/engine/context.py +82 -0
- titan_cli/engine/mock_context.py +176 -0
- titan_cli/engine/results.py +91 -0
- titan_cli/engine/steps/ai_assistant_step.py +185 -0
- titan_cli/engine/steps/command_step.py +93 -0
- titan_cli/engine/utils/__init__.py +3 -0
- titan_cli/engine/utils/venv.py +31 -0
- titan_cli/engine/workflow_executor.py +187 -0
- titan_cli/external_cli/__init__.py +0 -0
- titan_cli/external_cli/configs.py +17 -0
- titan_cli/external_cli/launcher.py +65 -0
- titan_cli/messages.py +121 -0
- titan_cli/ui/tui/__init__.py +205 -0
- titan_cli/ui/tui/__previews__/statusbar_preview.py +88 -0
- titan_cli/ui/tui/app.py +113 -0
- titan_cli/ui/tui/icons.py +70 -0
- titan_cli/ui/tui/screens/__init__.py +24 -0
- titan_cli/ui/tui/screens/ai_config.py +498 -0
- titan_cli/ui/tui/screens/ai_config_wizard.py +882 -0
- titan_cli/ui/tui/screens/base.py +110 -0
- titan_cli/ui/tui/screens/cli_launcher.py +151 -0
- titan_cli/ui/tui/screens/global_setup_wizard.py +363 -0
- titan_cli/ui/tui/screens/main_menu.py +162 -0
- titan_cli/ui/tui/screens/plugin_config_wizard.py +550 -0
- titan_cli/ui/tui/screens/plugin_management.py +377 -0
- titan_cli/ui/tui/screens/project_setup_wizard.py +686 -0
- titan_cli/ui/tui/screens/workflow_execution.py +592 -0
- titan_cli/ui/tui/screens/workflows.py +249 -0
- titan_cli/ui/tui/textual_components.py +537 -0
- titan_cli/ui/tui/textual_workflow_executor.py +405 -0
- titan_cli/ui/tui/theme.py +102 -0
- titan_cli/ui/tui/widgets/__init__.py +40 -0
- titan_cli/ui/tui/widgets/button.py +108 -0
- titan_cli/ui/tui/widgets/header.py +116 -0
- titan_cli/ui/tui/widgets/panel.py +81 -0
- titan_cli/ui/tui/widgets/status_bar.py +115 -0
- titan_cli/ui/tui/widgets/table.py +77 -0
- titan_cli/ui/tui/widgets/text.py +177 -0
- titan_cli/utils/__init__.py +0 -0
- titan_cli/utils/autoupdate.py +155 -0
- titan_cli-0.1.0.dist-info/METADATA +149 -0
- titan_cli-0.1.0.dist-info/RECORD +146 -0
- titan_cli-0.1.0.dist-info/WHEEL +4 -0
- titan_cli-0.1.0.dist-info/entry_points.txt +9 -0
- titan_cli-0.1.0.dist-info/licenses/LICENSE +201 -0
- titan_plugin_git/__init__.py +1 -0
- titan_plugin_git/clients/__init__.py +8 -0
- titan_plugin_git/clients/git_client.py +772 -0
- titan_plugin_git/exceptions.py +40 -0
- titan_plugin_git/messages.py +112 -0
- titan_plugin_git/models.py +39 -0
- titan_plugin_git/plugin.py +118 -0
- titan_plugin_git/steps/__init__.py +1 -0
- titan_plugin_git/steps/ai_commit_message_step.py +171 -0
- titan_plugin_git/steps/branch_steps.py +104 -0
- titan_plugin_git/steps/commit_step.py +80 -0
- titan_plugin_git/steps/push_step.py +63 -0
- titan_plugin_git/steps/status_step.py +59 -0
- titan_plugin_git/workflows/__previews__/__init__.py +1 -0
- titan_plugin_git/workflows/__previews__/commit_ai_preview.py +124 -0
- titan_plugin_git/workflows/commit-ai.yaml +28 -0
- titan_plugin_github/__init__.py +11 -0
- titan_plugin_github/agents/__init__.py +6 -0
- titan_plugin_github/agents/config_loader.py +130 -0
- titan_plugin_github/agents/issue_generator.py +353 -0
- titan_plugin_github/agents/pr_agent.py +528 -0
- titan_plugin_github/clients/__init__.py +8 -0
- titan_plugin_github/clients/github_client.py +1105 -0
- titan_plugin_github/config/__init__.py +0 -0
- titan_plugin_github/config/pr_agent.toml +85 -0
- titan_plugin_github/exceptions.py +28 -0
- titan_plugin_github/messages.py +88 -0
- titan_plugin_github/models.py +330 -0
- titan_plugin_github/plugin.py +131 -0
- titan_plugin_github/steps/__init__.py +12 -0
- titan_plugin_github/steps/ai_pr_step.py +172 -0
- titan_plugin_github/steps/create_pr_step.py +86 -0
- titan_plugin_github/steps/github_prompt_steps.py +171 -0
- titan_plugin_github/steps/issue_steps.py +143 -0
- titan_plugin_github/steps/preview_step.py +40 -0
- titan_plugin_github/utils.py +82 -0
- titan_plugin_github/workflows/__previews__/__init__.py +1 -0
- titan_plugin_github/workflows/__previews__/create_pr_ai_preview.py +140 -0
- titan_plugin_github/workflows/create-issue-ai.yaml +32 -0
- titan_plugin_github/workflows/create-pr-ai.yaml +49 -0
- titan_plugin_jira/__init__.py +8 -0
- titan_plugin_jira/agents/__init__.py +6 -0
- titan_plugin_jira/agents/config_loader.py +154 -0
- titan_plugin_jira/agents/jira_agent.py +553 -0
- titan_plugin_jira/agents/prompts.py +364 -0
- titan_plugin_jira/agents/response_parser.py +435 -0
- titan_plugin_jira/agents/token_tracker.py +223 -0
- titan_plugin_jira/agents/validators.py +246 -0
- titan_plugin_jira/clients/jira_client.py +745 -0
- titan_plugin_jira/config/jira_agent.toml +92 -0
- titan_plugin_jira/config/templates/issue_analysis.md.j2 +78 -0
- titan_plugin_jira/exceptions.py +37 -0
- titan_plugin_jira/formatters/__init__.py +6 -0
- titan_plugin_jira/formatters/markdown_formatter.py +245 -0
- titan_plugin_jira/messages.py +115 -0
- titan_plugin_jira/models.py +89 -0
- titan_plugin_jira/plugin.py +264 -0
- titan_plugin_jira/steps/ai_analyze_issue_step.py +105 -0
- titan_plugin_jira/steps/get_issue_step.py +82 -0
- titan_plugin_jira/steps/prompt_select_issue_step.py +80 -0
- titan_plugin_jira/steps/search_saved_query_step.py +238 -0
- titan_plugin_jira/utils/__init__.py +13 -0
- titan_plugin_jira/utils/issue_sorter.py +140 -0
- titan_plugin_jira/utils/saved_queries.py +150 -0
- titan_plugin_jira/workflows/analyze-jira-issues.yaml +34 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# plugins/titan-plugin-git/titan_plugin_git/steps/push_step.py
|
|
2
|
+
from titan_cli.engine import WorkflowContext, WorkflowResult, Success, Error
|
|
3
|
+
from titan_plugin_git.exceptions import GitCommandError
|
|
4
|
+
from titan_plugin_git.messages import msg
|
|
5
|
+
from titan_cli.ui.tui.widgets import Panel
|
|
6
|
+
|
|
7
|
+
def create_git_push_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
8
|
+
"""
|
|
9
|
+
Pushes changes to a remote repository.
|
|
10
|
+
|
|
11
|
+
Requires (from ctx.data):
|
|
12
|
+
remote (str, optional): The name of the remote to push to. Defaults to the client's default remote.
|
|
13
|
+
branch (str, optional): The name of the branch to push. Defaults to the current branch.
|
|
14
|
+
set_upstream (bool, optional): Whether to set the upstream tracking branch. Defaults to False.
|
|
15
|
+
|
|
16
|
+
Requires:
|
|
17
|
+
ctx.git: An initialized GitClient.
|
|
18
|
+
|
|
19
|
+
Outputs (saved to ctx.data):
|
|
20
|
+
pr_head_branch (str): The name of the branch that was pushed.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Success: If the push was successful.
|
|
24
|
+
Error: If the push operation fails.
|
|
25
|
+
"""
|
|
26
|
+
if not ctx.textual:
|
|
27
|
+
return Error("Textual UI context is not available for this step.")
|
|
28
|
+
|
|
29
|
+
if not ctx.git:
|
|
30
|
+
return Error(msg.Steps.Push.GIT_CLIENT_NOT_AVAILABLE)
|
|
31
|
+
|
|
32
|
+
# Get params from context
|
|
33
|
+
remote = ctx.get('remote')
|
|
34
|
+
branch = ctx.get('branch')
|
|
35
|
+
set_upstream = ctx.get('set_upstream', False)
|
|
36
|
+
|
|
37
|
+
# Use defaults from the GitClient if not provided in the context
|
|
38
|
+
remote_to_use = remote or ctx.git.default_remote
|
|
39
|
+
branch_to_use = branch or ctx.git.get_current_branch()
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
# The first push of a branch should set the upstream
|
|
43
|
+
if not ctx.git.branch_exists_on_remote(branch=branch_to_use, remote=remote_to_use):
|
|
44
|
+
set_upstream = True
|
|
45
|
+
|
|
46
|
+
ctx.git.push(remote=remote_to_use, branch=branch_to_use, set_upstream=set_upstream)
|
|
47
|
+
|
|
48
|
+
# Show success panel
|
|
49
|
+
ctx.textual.mount(
|
|
50
|
+
Panel(
|
|
51
|
+
text=f"Pushed to {remote_to_use}/{branch_to_use}",
|
|
52
|
+
panel_type="success"
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return Success(
|
|
57
|
+
message=msg.Git.PUSH_SUCCESS.format(remote=remote_to_use, branch=branch_to_use),
|
|
58
|
+
metadata={"pr_head_branch": branch_to_use}
|
|
59
|
+
)
|
|
60
|
+
except GitCommandError as e:
|
|
61
|
+
return Error(msg.Steps.Push.PUSH_FAILED.format(e=e))
|
|
62
|
+
except Exception as e:
|
|
63
|
+
return Error(msg.Git.UNEXPECTED_ERROR.format(e=e))
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# plugins/titan-plugin-git/titan_plugin_git/steps/status_step.py
|
|
2
|
+
from titan_cli.engine import (
|
|
3
|
+
WorkflowContext,
|
|
4
|
+
WorkflowResult,
|
|
5
|
+
Success,
|
|
6
|
+
Error
|
|
7
|
+
)
|
|
8
|
+
from titan_cli.messages import msg as global_msg
|
|
9
|
+
from ..messages import msg
|
|
10
|
+
from titan_cli.ui.tui.widgets import Panel
|
|
11
|
+
|
|
12
|
+
def get_git_status_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
13
|
+
"""
|
|
14
|
+
Retrieves the current git status and saves it to the context.
|
|
15
|
+
|
|
16
|
+
Requires:
|
|
17
|
+
ctx.git: An initialized GitClient.
|
|
18
|
+
|
|
19
|
+
Outputs (saved to ctx.data):
|
|
20
|
+
git_status (GitStatus): The full git status object, which includes the `is_clean` flag.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Success: If the status was retrieved successfully.
|
|
24
|
+
Error: If the GitClient is not available or the git command fails.
|
|
25
|
+
"""
|
|
26
|
+
if not ctx.git:
|
|
27
|
+
return Error(msg.Steps.Status.GIT_CLIENT_NOT_AVAILABLE)
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
status = ctx.git.get_status()
|
|
31
|
+
|
|
32
|
+
if not ctx.textual:
|
|
33
|
+
return Error("Textual UI context is not available for this step.")
|
|
34
|
+
|
|
35
|
+
# If there are uncommitted changes, show warning panel
|
|
36
|
+
if not status.is_clean:
|
|
37
|
+
ctx.textual.mount(
|
|
38
|
+
Panel(
|
|
39
|
+
text=global_msg.Workflow.UNCOMMITTED_CHANGES_WARNING,
|
|
40
|
+
panel_type="warning"
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
message = msg.Steps.Status.STATUS_RETRIEVED_WITH_UNCOMMITTED
|
|
44
|
+
else:
|
|
45
|
+
# Show success panel for clean working directory
|
|
46
|
+
ctx.textual.mount(
|
|
47
|
+
Panel(
|
|
48
|
+
text=msg.Steps.Status.WORKING_DIRECTORY_IS_CLEAN,
|
|
49
|
+
panel_type="success"
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
message = msg.Steps.Status.WORKING_DIRECTORY_IS_CLEAN
|
|
53
|
+
|
|
54
|
+
return Success(
|
|
55
|
+
message=message,
|
|
56
|
+
metadata={"git_status": status}
|
|
57
|
+
)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
return Error(msg.Steps.Status.FAILED_TO_GET_STATUS.format(e=e))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Workflow previews for git plugin."""
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Commit with AI Workflow Preview
|
|
4
|
+
|
|
5
|
+
Preview the commit-ai workflow by executing real steps with mocked data.
|
|
6
|
+
Run: titan preview workflow commit-ai
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from titan_cli.ui.components.typography import TextRenderer
|
|
10
|
+
from titan_cli.ui.components.spacer import SpacerRenderer
|
|
11
|
+
from titan_cli.engine.mock_context import (
|
|
12
|
+
MockGitClient,
|
|
13
|
+
MockAIClient,
|
|
14
|
+
MockSecretManager,
|
|
15
|
+
)
|
|
16
|
+
from titan_cli.engine import WorkflowContext
|
|
17
|
+
from titan_cli.engine.ui_container import UIComponents
|
|
18
|
+
from titan_cli.engine.views_container import UIViews
|
|
19
|
+
from titan_cli.engine.results import Success, Error, Skip
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def create_commit_ai_mock_context() -> WorkflowContext:
|
|
23
|
+
"""
|
|
24
|
+
Create mock context specifically for commit-ai workflow.
|
|
25
|
+
|
|
26
|
+
Customizes mock data to simulate the commit-ai workflow scenario:
|
|
27
|
+
- Git status with uncommitted changes
|
|
28
|
+
- AI generates commit message
|
|
29
|
+
- Commit is created and pushed
|
|
30
|
+
|
|
31
|
+
Pre-populates context data to ensure all steps execute successfully
|
|
32
|
+
and display their full UI in the preview.
|
|
33
|
+
"""
|
|
34
|
+
# Create UI components
|
|
35
|
+
ui = UIComponents.create()
|
|
36
|
+
views = UIViews.create(ui)
|
|
37
|
+
|
|
38
|
+
# Override prompts to auto-confirm (non-interactive preview)
|
|
39
|
+
views.prompts.ask_confirm = lambda question, default=True: True
|
|
40
|
+
|
|
41
|
+
# Create mock clients with workflow-specific data
|
|
42
|
+
git = MockGitClient()
|
|
43
|
+
git.current_branch = "feat/improve-ux"
|
|
44
|
+
git.main_branch = "master"
|
|
45
|
+
git.default_remote = "origin"
|
|
46
|
+
|
|
47
|
+
ai = MockAIClient()
|
|
48
|
+
|
|
49
|
+
secrets = MockSecretManager()
|
|
50
|
+
|
|
51
|
+
# Build context
|
|
52
|
+
ctx = WorkflowContext(
|
|
53
|
+
secrets=secrets,
|
|
54
|
+
ui=ui,
|
|
55
|
+
views=views
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Inject mocked clients
|
|
59
|
+
ctx.git = git
|
|
60
|
+
ctx.ai = ai
|
|
61
|
+
|
|
62
|
+
return ctx
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def preview_workflow():
|
|
66
|
+
"""
|
|
67
|
+
Preview the commit-ai workflow by executing real steps with mocked context.
|
|
68
|
+
|
|
69
|
+
This ensures the preview always matches the real workflow execution.
|
|
70
|
+
"""
|
|
71
|
+
text = TextRenderer()
|
|
72
|
+
spacer = SpacerRenderer()
|
|
73
|
+
|
|
74
|
+
# Header
|
|
75
|
+
text.title("Commit with AI - PREVIEW")
|
|
76
|
+
text.subtitle("(Executing real steps with mocked data)")
|
|
77
|
+
spacer.line()
|
|
78
|
+
|
|
79
|
+
# Create workflow-specific mock context
|
|
80
|
+
ctx = create_commit_ai_mock_context()
|
|
81
|
+
|
|
82
|
+
# Import steps
|
|
83
|
+
from titan_plugin_git.steps.status_step import get_git_status_step
|
|
84
|
+
from titan_plugin_git.steps.ai_commit_message_step import ai_generate_commit_message
|
|
85
|
+
from titan_plugin_git.steps.commit_step import create_git_commit_step
|
|
86
|
+
from titan_plugin_git.steps.push_step import create_git_push_step
|
|
87
|
+
|
|
88
|
+
# Execute steps in order (same as workflow YAML)
|
|
89
|
+
steps = [
|
|
90
|
+
("git_status", get_git_status_step),
|
|
91
|
+
("ai_commit_message", ai_generate_commit_message),
|
|
92
|
+
("create_commit", create_git_commit_step),
|
|
93
|
+
("push", create_git_push_step),
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
text.info("Executing workflow...")
|
|
97
|
+
spacer.small()
|
|
98
|
+
|
|
99
|
+
# Inject workflow metadata (like the real executor does)
|
|
100
|
+
ctx.workflow_name = "commit-ai"
|
|
101
|
+
ctx.total_steps = len(steps)
|
|
102
|
+
|
|
103
|
+
for i, (step_name, step_fn) in enumerate(steps, 1):
|
|
104
|
+
# Inject current step number (like the real executor does)
|
|
105
|
+
ctx.current_step = i
|
|
106
|
+
|
|
107
|
+
# Execute the step - it will handle all its own UI
|
|
108
|
+
result = step_fn(ctx)
|
|
109
|
+
|
|
110
|
+
# Merge metadata into context (like the real executor does)
|
|
111
|
+
if isinstance(result, (Success, Skip)) and result.metadata:
|
|
112
|
+
ctx.data.update(result.metadata)
|
|
113
|
+
|
|
114
|
+
# Only handle errors (steps handle their own success/skip UI)
|
|
115
|
+
if isinstance(result, Error):
|
|
116
|
+
text.error(f"Step '{step_name}' failed: {result.message}")
|
|
117
|
+
break
|
|
118
|
+
|
|
119
|
+
spacer.line()
|
|
120
|
+
text.info("(This was a preview - no actual git operations were performed)")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
if __name__ == "__main__":
|
|
124
|
+
preview_workflow()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: "Commit with AI"
|
|
2
|
+
description: "Create a commit with AI-generated message"
|
|
3
|
+
|
|
4
|
+
hooks:
|
|
5
|
+
- before_commit # Hook for linting/testing
|
|
6
|
+
|
|
7
|
+
steps:
|
|
8
|
+
- hook: before_commit # This is where injected steps will run
|
|
9
|
+
|
|
10
|
+
- id: git_status
|
|
11
|
+
name: "Check Git Status"
|
|
12
|
+
plugin: git
|
|
13
|
+
step: get_status
|
|
14
|
+
|
|
15
|
+
- id: ai_commit_message
|
|
16
|
+
name: "AI Commit Message"
|
|
17
|
+
plugin: git
|
|
18
|
+
step: ai_generate_commit_message
|
|
19
|
+
|
|
20
|
+
- id: create_commit
|
|
21
|
+
name: "Create Commit"
|
|
22
|
+
plugin: git
|
|
23
|
+
step: create_commit
|
|
24
|
+
|
|
25
|
+
- id: push
|
|
26
|
+
name: "Push changes to remote"
|
|
27
|
+
plugin: git
|
|
28
|
+
step: push
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# plugins/titan-plugin-github/titan_plugin_github/agents/config_loader.py
|
|
2
|
+
"""Configuration loader for PR Agent."""
|
|
3
|
+
|
|
4
|
+
import tomli
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional, Dict, Any
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
# Python 3.9+
|
|
11
|
+
from importlib.resources import files
|
|
12
|
+
except ImportError:
|
|
13
|
+
# Python 3.7-3.8 fallback
|
|
14
|
+
from importlib_resources import files
|
|
15
|
+
|
|
16
|
+
# Import default limits from plugin utils
|
|
17
|
+
from ..utils import (
|
|
18
|
+
DEFAULT_MAX_DIFF_SIZE,
|
|
19
|
+
DEFAULT_MAX_FILES_IN_DIFF,
|
|
20
|
+
DEFAULT_MAX_COMMITS_TO_ANALYZE
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PRAgentConfig(BaseModel):
|
|
25
|
+
"""PR Agent configuration loaded from TOML."""
|
|
26
|
+
|
|
27
|
+
name: str = Field(..., description="Agent name")
|
|
28
|
+
description: str = Field("", description="Agent description")
|
|
29
|
+
version: str = Field("1.0.0", description="Agent version")
|
|
30
|
+
|
|
31
|
+
# Prompts
|
|
32
|
+
pr_system_prompt: str = Field("", description="System prompt for PR generation")
|
|
33
|
+
commit_system_prompt: str = Field("", description="System prompt for commit messages")
|
|
34
|
+
architecture_system_prompt: str = Field("", description="System prompt for architecture review")
|
|
35
|
+
|
|
36
|
+
# Diff analysis limits
|
|
37
|
+
max_diff_size: int = Field(DEFAULT_MAX_DIFF_SIZE, ge=0, description="Maximum diff size to analyze")
|
|
38
|
+
max_files_in_diff: int = Field(DEFAULT_MAX_FILES_IN_DIFF, ge=1, description="Maximum files in diff")
|
|
39
|
+
max_commits_to_analyze: int = Field(DEFAULT_MAX_COMMITS_TO_ANALYZE, ge=1, description="Maximum commits to analyze")
|
|
40
|
+
|
|
41
|
+
# Features
|
|
42
|
+
enable_template_detection: bool = Field(True, description="Enable PR template detection")
|
|
43
|
+
enable_dynamic_sizing: bool = Field(True, description="Enable dynamic PR sizing")
|
|
44
|
+
enable_user_confirmation: bool = Field(True, description="Enable user confirmation")
|
|
45
|
+
enable_fallback_prompts: bool = Field(True, description="Enable fallback prompts")
|
|
46
|
+
enable_debug_output: bool = Field(False, description="Enable debug output")
|
|
47
|
+
|
|
48
|
+
# Raw config for custom access
|
|
49
|
+
raw: Dict[str, Any] = Field(default_factory=dict, description="Raw TOML data")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def load_agent_config(
|
|
53
|
+
agent_name: str = "pr_agent",
|
|
54
|
+
config_dir: Optional[Path] = None
|
|
55
|
+
) -> PRAgentConfig:
|
|
56
|
+
"""
|
|
57
|
+
Load agent configuration from TOML file.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
agent_name: Name of the agent (e.g., "pr_agent")
|
|
61
|
+
config_dir: Optional custom config directory
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
PRAgentConfig instance
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
FileNotFoundError: If config file doesn't exist
|
|
68
|
+
ValueError: If config is invalid
|
|
69
|
+
"""
|
|
70
|
+
# Determine config file path
|
|
71
|
+
if config_dir:
|
|
72
|
+
config_path = config_dir / f"{agent_name}.toml"
|
|
73
|
+
else:
|
|
74
|
+
# Use importlib.resources for robust path resolution
|
|
75
|
+
# Works with both development and installed (pip/pipx) environments
|
|
76
|
+
config_files = files("titan_plugin_github.config")
|
|
77
|
+
config_file = config_files.joinpath(f"{agent_name}.toml")
|
|
78
|
+
|
|
79
|
+
# Convert Traversable to Path
|
|
80
|
+
# In Python 3.9+, this handles both filesystem and zip-based resources
|
|
81
|
+
if hasattr(config_file, "__fspath__"):
|
|
82
|
+
config_path = Path(config_file.__fspath__())
|
|
83
|
+
else:
|
|
84
|
+
# Fallback for older Python or non-filesystem resources
|
|
85
|
+
config_path = Path(str(config_file))
|
|
86
|
+
|
|
87
|
+
if not config_path.exists():
|
|
88
|
+
raise FileNotFoundError(f"Agent config not found: {config_path}")
|
|
89
|
+
|
|
90
|
+
# Load TOML
|
|
91
|
+
try:
|
|
92
|
+
with open(config_path, "rb") as f:
|
|
93
|
+
data = tomli.load(f)
|
|
94
|
+
except tomli.TOMLDecodeError as e:
|
|
95
|
+
raise ValueError(f"Invalid TOML in {config_path}: {e}")
|
|
96
|
+
except Exception as e:
|
|
97
|
+
raise ValueError(f"Failed to read config {config_path}: {e}")
|
|
98
|
+
|
|
99
|
+
# Validate config structure
|
|
100
|
+
if "agent" not in data:
|
|
101
|
+
raise ValueError(f"Missing [agent] section in {config_path}")
|
|
102
|
+
|
|
103
|
+
# Extract sections
|
|
104
|
+
agent_meta = data.get("agent", {})
|
|
105
|
+
prompts = data.get("agent", {}).get("prompts", {})
|
|
106
|
+
limits = data.get("agent", {}).get("limits", {})
|
|
107
|
+
features = data.get("agent", {}).get("features", {})
|
|
108
|
+
|
|
109
|
+
# Build PRAgentConfig
|
|
110
|
+
return PRAgentConfig(
|
|
111
|
+
name=agent_meta.get("name", agent_name),
|
|
112
|
+
description=agent_meta.get("description", ""),
|
|
113
|
+
version=agent_meta.get("version", "1.0.0"),
|
|
114
|
+
# Prompts
|
|
115
|
+
pr_system_prompt=prompts.get("pr_description", {}).get("system", ""),
|
|
116
|
+
commit_system_prompt=prompts.get("commit_message", {}).get("system", ""),
|
|
117
|
+
architecture_system_prompt=prompts.get("architecture_review", {}).get("system", ""),
|
|
118
|
+
# Limits (use defaults from utils)
|
|
119
|
+
max_diff_size=limits.get("max_diff_size", DEFAULT_MAX_DIFF_SIZE),
|
|
120
|
+
max_files_in_diff=limits.get("max_files_in_diff", DEFAULT_MAX_FILES_IN_DIFF),
|
|
121
|
+
max_commits_to_analyze=limits.get("max_commits_to_analyze", DEFAULT_MAX_COMMITS_TO_ANALYZE),
|
|
122
|
+
# Features
|
|
123
|
+
enable_template_detection=features.get("enable_template_detection", True),
|
|
124
|
+
enable_dynamic_sizing=features.get("enable_dynamic_sizing", True),
|
|
125
|
+
enable_user_confirmation=features.get("enable_user_confirmation", True),
|
|
126
|
+
enable_fallback_prompts=features.get("enable_fallback_prompts", True),
|
|
127
|
+
enable_debug_output=features.get("enable_debug_output", False),
|
|
128
|
+
# Raw for custom access
|
|
129
|
+
raw=data
|
|
130
|
+
)
|