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.
Files changed (146) hide show
  1. titan_cli/__init__.py +3 -0
  2. titan_cli/__main__.py +4 -0
  3. titan_cli/ai/__init__.py +0 -0
  4. titan_cli/ai/agents/__init__.py +15 -0
  5. titan_cli/ai/agents/base.py +152 -0
  6. titan_cli/ai/client.py +170 -0
  7. titan_cli/ai/constants.py +56 -0
  8. titan_cli/ai/exceptions.py +48 -0
  9. titan_cli/ai/models.py +34 -0
  10. titan_cli/ai/oauth_helper.py +120 -0
  11. titan_cli/ai/providers/__init__.py +9 -0
  12. titan_cli/ai/providers/anthropic.py +117 -0
  13. titan_cli/ai/providers/base.py +75 -0
  14. titan_cli/ai/providers/gemini.py +278 -0
  15. titan_cli/cli.py +59 -0
  16. titan_cli/clients/__init__.py +1 -0
  17. titan_cli/clients/gcloud_client.py +52 -0
  18. titan_cli/core/__init__.py +3 -0
  19. titan_cli/core/config.py +274 -0
  20. titan_cli/core/discovery.py +51 -0
  21. titan_cli/core/errors.py +81 -0
  22. titan_cli/core/models.py +52 -0
  23. titan_cli/core/plugins/available.py +36 -0
  24. titan_cli/core/plugins/models.py +67 -0
  25. titan_cli/core/plugins/plugin_base.py +108 -0
  26. titan_cli/core/plugins/plugin_registry.py +163 -0
  27. titan_cli/core/secrets.py +141 -0
  28. titan_cli/core/workflows/__init__.py +22 -0
  29. titan_cli/core/workflows/models.py +88 -0
  30. titan_cli/core/workflows/project_step_source.py +86 -0
  31. titan_cli/core/workflows/workflow_exceptions.py +17 -0
  32. titan_cli/core/workflows/workflow_filter_service.py +137 -0
  33. titan_cli/core/workflows/workflow_registry.py +419 -0
  34. titan_cli/core/workflows/workflow_sources.py +307 -0
  35. titan_cli/engine/__init__.py +39 -0
  36. titan_cli/engine/builder.py +159 -0
  37. titan_cli/engine/context.py +82 -0
  38. titan_cli/engine/mock_context.py +176 -0
  39. titan_cli/engine/results.py +91 -0
  40. titan_cli/engine/steps/ai_assistant_step.py +185 -0
  41. titan_cli/engine/steps/command_step.py +93 -0
  42. titan_cli/engine/utils/__init__.py +3 -0
  43. titan_cli/engine/utils/venv.py +31 -0
  44. titan_cli/engine/workflow_executor.py +187 -0
  45. titan_cli/external_cli/__init__.py +0 -0
  46. titan_cli/external_cli/configs.py +17 -0
  47. titan_cli/external_cli/launcher.py +65 -0
  48. titan_cli/messages.py +121 -0
  49. titan_cli/ui/tui/__init__.py +205 -0
  50. titan_cli/ui/tui/__previews__/statusbar_preview.py +88 -0
  51. titan_cli/ui/tui/app.py +113 -0
  52. titan_cli/ui/tui/icons.py +70 -0
  53. titan_cli/ui/tui/screens/__init__.py +24 -0
  54. titan_cli/ui/tui/screens/ai_config.py +498 -0
  55. titan_cli/ui/tui/screens/ai_config_wizard.py +882 -0
  56. titan_cli/ui/tui/screens/base.py +110 -0
  57. titan_cli/ui/tui/screens/cli_launcher.py +151 -0
  58. titan_cli/ui/tui/screens/global_setup_wizard.py +363 -0
  59. titan_cli/ui/tui/screens/main_menu.py +162 -0
  60. titan_cli/ui/tui/screens/plugin_config_wizard.py +550 -0
  61. titan_cli/ui/tui/screens/plugin_management.py +377 -0
  62. titan_cli/ui/tui/screens/project_setup_wizard.py +686 -0
  63. titan_cli/ui/tui/screens/workflow_execution.py +592 -0
  64. titan_cli/ui/tui/screens/workflows.py +249 -0
  65. titan_cli/ui/tui/textual_components.py +537 -0
  66. titan_cli/ui/tui/textual_workflow_executor.py +405 -0
  67. titan_cli/ui/tui/theme.py +102 -0
  68. titan_cli/ui/tui/widgets/__init__.py +40 -0
  69. titan_cli/ui/tui/widgets/button.py +108 -0
  70. titan_cli/ui/tui/widgets/header.py +116 -0
  71. titan_cli/ui/tui/widgets/panel.py +81 -0
  72. titan_cli/ui/tui/widgets/status_bar.py +115 -0
  73. titan_cli/ui/tui/widgets/table.py +77 -0
  74. titan_cli/ui/tui/widgets/text.py +177 -0
  75. titan_cli/utils/__init__.py +0 -0
  76. titan_cli/utils/autoupdate.py +155 -0
  77. titan_cli-0.1.0.dist-info/METADATA +149 -0
  78. titan_cli-0.1.0.dist-info/RECORD +146 -0
  79. titan_cli-0.1.0.dist-info/WHEEL +4 -0
  80. titan_cli-0.1.0.dist-info/entry_points.txt +9 -0
  81. titan_cli-0.1.0.dist-info/licenses/LICENSE +201 -0
  82. titan_plugin_git/__init__.py +1 -0
  83. titan_plugin_git/clients/__init__.py +8 -0
  84. titan_plugin_git/clients/git_client.py +772 -0
  85. titan_plugin_git/exceptions.py +40 -0
  86. titan_plugin_git/messages.py +112 -0
  87. titan_plugin_git/models.py +39 -0
  88. titan_plugin_git/plugin.py +118 -0
  89. titan_plugin_git/steps/__init__.py +1 -0
  90. titan_plugin_git/steps/ai_commit_message_step.py +171 -0
  91. titan_plugin_git/steps/branch_steps.py +104 -0
  92. titan_plugin_git/steps/commit_step.py +80 -0
  93. titan_plugin_git/steps/push_step.py +63 -0
  94. titan_plugin_git/steps/status_step.py +59 -0
  95. titan_plugin_git/workflows/__previews__/__init__.py +1 -0
  96. titan_plugin_git/workflows/__previews__/commit_ai_preview.py +124 -0
  97. titan_plugin_git/workflows/commit-ai.yaml +28 -0
  98. titan_plugin_github/__init__.py +11 -0
  99. titan_plugin_github/agents/__init__.py +6 -0
  100. titan_plugin_github/agents/config_loader.py +130 -0
  101. titan_plugin_github/agents/issue_generator.py +353 -0
  102. titan_plugin_github/agents/pr_agent.py +528 -0
  103. titan_plugin_github/clients/__init__.py +8 -0
  104. titan_plugin_github/clients/github_client.py +1105 -0
  105. titan_plugin_github/config/__init__.py +0 -0
  106. titan_plugin_github/config/pr_agent.toml +85 -0
  107. titan_plugin_github/exceptions.py +28 -0
  108. titan_plugin_github/messages.py +88 -0
  109. titan_plugin_github/models.py +330 -0
  110. titan_plugin_github/plugin.py +131 -0
  111. titan_plugin_github/steps/__init__.py +12 -0
  112. titan_plugin_github/steps/ai_pr_step.py +172 -0
  113. titan_plugin_github/steps/create_pr_step.py +86 -0
  114. titan_plugin_github/steps/github_prompt_steps.py +171 -0
  115. titan_plugin_github/steps/issue_steps.py +143 -0
  116. titan_plugin_github/steps/preview_step.py +40 -0
  117. titan_plugin_github/utils.py +82 -0
  118. titan_plugin_github/workflows/__previews__/__init__.py +1 -0
  119. titan_plugin_github/workflows/__previews__/create_pr_ai_preview.py +140 -0
  120. titan_plugin_github/workflows/create-issue-ai.yaml +32 -0
  121. titan_plugin_github/workflows/create-pr-ai.yaml +49 -0
  122. titan_plugin_jira/__init__.py +8 -0
  123. titan_plugin_jira/agents/__init__.py +6 -0
  124. titan_plugin_jira/agents/config_loader.py +154 -0
  125. titan_plugin_jira/agents/jira_agent.py +553 -0
  126. titan_plugin_jira/agents/prompts.py +364 -0
  127. titan_plugin_jira/agents/response_parser.py +435 -0
  128. titan_plugin_jira/agents/token_tracker.py +223 -0
  129. titan_plugin_jira/agents/validators.py +246 -0
  130. titan_plugin_jira/clients/jira_client.py +745 -0
  131. titan_plugin_jira/config/jira_agent.toml +92 -0
  132. titan_plugin_jira/config/templates/issue_analysis.md.j2 +78 -0
  133. titan_plugin_jira/exceptions.py +37 -0
  134. titan_plugin_jira/formatters/__init__.py +6 -0
  135. titan_plugin_jira/formatters/markdown_formatter.py +245 -0
  136. titan_plugin_jira/messages.py +115 -0
  137. titan_plugin_jira/models.py +89 -0
  138. titan_plugin_jira/plugin.py +264 -0
  139. titan_plugin_jira/steps/ai_analyze_issue_step.py +105 -0
  140. titan_plugin_jira/steps/get_issue_step.py +82 -0
  141. titan_plugin_jira/steps/prompt_select_issue_step.py +80 -0
  142. titan_plugin_jira/steps/search_saved_query_step.py +238 -0
  143. titan_plugin_jira/utils/__init__.py +13 -0
  144. titan_plugin_jira/utils/issue_sorter.py +140 -0
  145. titan_plugin_jira/utils/saved_queries.py +150 -0
  146. 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,11 @@
1
+ # plugins/titan-plugin-github/titan_plugin_github/__init__.py
2
+ """
3
+ Titan GitHub Plugin
4
+
5
+ Provides GitHub integration for pull requests, issues, and more.
6
+ """
7
+
8
+ from .plugin import GitHubPlugin
9
+
10
+ __version__ = "0.1.0"
11
+ __all__ = ["GitHubPlugin"]
@@ -0,0 +1,6 @@
1
+ # plugins/titan-plugin-github/titan_plugin_github/agents/__init__.py
2
+ """AI agents for GitHub plugin."""
3
+
4
+ from .pr_agent import PRAgent, PRAnalysis
5
+
6
+ __all__ = ["PRAgent", "PRAnalysis"]
@@ -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
+ )