ai-cr 2.0.0.dev2__py3-none-any.whl → 2.0.1__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.
gito/pipeline.py CHANGED
@@ -1,70 +1,82 @@
1
- import logging
2
- from enum import StrEnum
3
- from dataclasses import dataclass, field
4
-
5
- from gito.utils import is_running_in_github_action
6
- from microcore import ui
7
- from microcore.utils import resolve_callable
8
-
9
-
10
- class PipelineEnv(StrEnum):
11
- LOCAL = "local"
12
- GH_ACTION = "gh-action"
13
-
14
- @staticmethod
15
- def all():
16
- return [PipelineEnv.LOCAL, PipelineEnv.GH_ACTION]
17
-
18
- @staticmethod
19
- def current():
20
- return (
21
- PipelineEnv.GH_ACTION
22
- if is_running_in_github_action()
23
- else PipelineEnv.LOCAL
24
- )
25
-
26
-
27
- @dataclass
28
- class PipelineStep:
29
- call: str
30
- envs: list[PipelineEnv] = field(default_factory=PipelineEnv.all)
31
-
32
- def get_callable(self):
33
- """
34
- Resolve the callable from the string representation.
35
- """
36
- return resolve_callable(self.call)
37
-
38
- def run(self, *args, **kwargs):
39
- return self.get_callable()(*args, **kwargs)
40
-
41
-
42
- @dataclass
43
- class Pipeline:
44
- ctx: dict = field(default_factory=dict)
45
- steps: dict[str, PipelineStep] = field(default_factory=dict)
46
-
47
- def run(self, *args, **kwargs):
48
- cur_env = PipelineEnv.current()
49
- logging.info("Running pipeline... [env: %s]", ui.yellow(cur_env))
50
- self.ctx["pipeline_out"] = self.ctx.get("pipeline_out", {})
51
- for step_name, step in self.steps.items():
52
- if cur_env in step.envs:
53
- logging.info(f"Running pipeline step: {step_name}")
54
- try:
55
- step_output = step.run(*args, **kwargs, **self.ctx)
56
- if isinstance(step_output, dict):
57
- self.ctx["pipeline_out"].update(step_output)
58
- self.ctx["pipeline_out"][step_name] = step_output
59
- if not step_output:
60
- logging.warning(
61
- f'Pipeline step "{step_name}" returned {repr(step_output)}.'
62
- )
63
- except Exception as e:
64
- logging.error(f'Error in pipeline step "{step_name}": {e}')
65
- else:
66
- logging.info(
67
- f"Skipping pipeline step: {step_name}"
68
- f" [env: {ui.yellow(cur_env)} not in {step.envs}]"
69
- )
70
- return self.ctx["pipeline_out"]
1
+ import logging
2
+ from enum import StrEnum
3
+ from dataclasses import dataclass, field
4
+
5
+ from gito.utils import is_running_in_github_action
6
+ from microcore import ui
7
+ from microcore.utils import resolve_callable
8
+
9
+
10
+ class PipelineEnv(StrEnum):
11
+ LOCAL = "local"
12
+ GH_ACTION = "gh-action"
13
+
14
+ @staticmethod
15
+ def all():
16
+ return [PipelineEnv.LOCAL, PipelineEnv.GH_ACTION]
17
+
18
+ @staticmethod
19
+ def current():
20
+ return (
21
+ PipelineEnv.GH_ACTION
22
+ if is_running_in_github_action()
23
+ else PipelineEnv.LOCAL
24
+ )
25
+
26
+
27
+ @dataclass
28
+ class PipelineStep:
29
+ call: str
30
+ envs: list[PipelineEnv] = field(default_factory=PipelineEnv.all)
31
+ enabled: bool = field(default=True)
32
+
33
+ def get_callable(self):
34
+ """
35
+ Resolve the callable from the string representation.
36
+ """
37
+ return resolve_callable(self.call)
38
+
39
+ def run(self, *args, **kwargs):
40
+ return self.get_callable()(*args, **kwargs)
41
+
42
+
43
+ @dataclass
44
+ class Pipeline:
45
+ ctx: dict = field(default_factory=dict)
46
+ steps: dict[str, PipelineStep] = field(default_factory=dict)
47
+ verbose: bool = False
48
+
49
+ @property
50
+ def enabled_steps(self):
51
+ return {
52
+ k: v for k, v in self.steps.items() if v.enabled
53
+ }
54
+
55
+ def run(self, *args, **kwargs):
56
+ cur_env = PipelineEnv.current()
57
+ logging.info("Running pipeline... [env: %s]", ui.yellow(cur_env))
58
+ self.ctx["pipeline_out"] = self.ctx.get("pipeline_out", {})
59
+ for step_name, step in self.enabled_steps.items():
60
+ if cur_env in step.envs:
61
+ logging.info(f"Running pipeline step: {step_name}")
62
+ try:
63
+ step_output = step.run(*args, **kwargs, **self.ctx)
64
+ if isinstance(step_output, dict):
65
+ self.ctx["pipeline_out"].update(step_output)
66
+ self.ctx["pipeline_out"][step_name] = step_output
67
+ if self.verbose and step_output:
68
+ logging.info(
69
+ f"Pipeline step {step_name} output: {repr(step_output)}"
70
+ )
71
+ if not step_output:
72
+ logging.warning(
73
+ f'Pipeline step "{step_name}" returned {repr(step_output)}.'
74
+ )
75
+ except Exception as e:
76
+ logging.error(f'Error in pipeline step "{step_name}": {e}')
77
+ else:
78
+ logging.info(
79
+ f"Skipping pipeline step: {step_name}"
80
+ f" [env: {ui.yellow(cur_env)} not in {step.envs}]"
81
+ )
82
+ return self.ctx["pipeline_out"]
@@ -1,83 +1,57 @@
1
- import logging
2
- import os
3
-
4
- import git
5
- from jira import JIRA
6
-
7
- from gito.issue_trackers import extract_issue_key, IssueTrackerIssue
8
- from gito.utils import is_running_in_github_action
9
-
10
-
11
- def fetch_issue(issue_key, jira_url, username, api_token) -> IssueTrackerIssue | None:
12
- try:
13
- jira = JIRA(jira_url, basic_auth=(username, api_token))
14
- issue = jira.issue(issue_key)
15
- return IssueTrackerIssue(
16
- title=issue.fields.summary,
17
- description=issue.fields.description or "",
18
- url=f"{jira_url.rstrip('/')}/browse/{issue_key}"
19
- )
20
- except Exception as e:
21
- logging.error(f"Failed to fetch Jira issue {issue_key}: {e}")
22
- return None
23
-
24
-
25
- def get_branch(repo: git.Repo):
26
- if is_running_in_github_action():
27
- branch_name = os.getenv('GITHUB_HEAD_REF')
28
- if branch_name:
29
- return branch_name
30
-
31
- github_ref = os.getenv('GITHUB_REF', '')
32
- if github_ref.startswith('refs/heads/'):
33
- return github_ref.replace('refs/heads/', '')
34
- try:
35
- branch_name = repo.active_branch.name
36
- return branch_name
37
- except Exception as e: # @todo: specify more precise exception
38
- logging.error("Could not determine the active branch name: %s", e)
39
- return None
40
-
41
-
42
- def fetch_associated_issue(
43
- repo: git.Repo,
44
- jira_url=None,
45
- jira_username=None,
46
- jira_api_token=None,
47
- **kwargs
48
- ):
49
- """
50
- Pipeline step to fetch a Jira issue based on the current branch name.
51
- """
52
- branch_name = get_branch(repo)
53
- if not branch_name:
54
- logging.error("No active branch found in the repository, cannot determine Jira issue key.")
55
- return None
56
-
57
- if not (issue_key := extract_issue_key(branch_name)):
58
- logging.error(f"No Jira issue key found in branch name: {branch_name}")
59
- return None
60
-
61
- jira_url = jira_url or os.getenv("JIRA_URL")
62
- jira_username = (
63
- jira_username
64
- or os.getenv("JIRA_USERNAME")
65
- or os.getenv("JIRA_USER")
66
- or os.getenv("JIRA_EMAIL")
67
- )
68
- jira_token = (
69
- jira_api_token
70
- or os.getenv("JIRA_API_TOKEN")
71
- or os.getenv("JIRA_API_KEY")
72
- or os.getenv("JIRA_TOKEN")
73
- )
74
- try:
75
- assert jira_url, "JIRA_URL is not set"
76
- assert jira_username, "JIRA_USERNAME is not set"
77
- assert jira_token, "JIRA_API_TOKEN is not set"
78
- except AssertionError as e:
79
- logging.error(f"Jira configuration error: {e}")
80
- return None
81
- return dict(
82
- associated_issue=fetch_issue(issue_key, jira_url, jira_username, jira_token)
83
- )
1
+ import logging
2
+ import os
3
+
4
+ import git
5
+ from jira import JIRA
6
+
7
+ from gito.issue_trackers import IssueTrackerIssue, resolve_issue_key
8
+
9
+
10
+ def fetch_issue(issue_key, jira_url, username, api_token) -> IssueTrackerIssue | None:
11
+ try:
12
+ jira = JIRA(jira_url, basic_auth=(username, api_token))
13
+ issue = jira.issue(issue_key)
14
+ return IssueTrackerIssue(
15
+ title=issue.fields.summary,
16
+ description=issue.fields.description or "",
17
+ url=f"{jira_url.rstrip('/')}/browse/{issue_key}"
18
+ )
19
+ except Exception as e:
20
+ logging.error(f"Failed to fetch Jira issue {issue_key}: {e}")
21
+ return None
22
+
23
+
24
+ def fetch_associated_issue(
25
+ repo: git.Repo,
26
+ jira_url=None,
27
+ jira_username=None,
28
+ jira_api_token=None,
29
+ **kwargs
30
+ ):
31
+ """
32
+ Pipeline step to fetch a Jira issue based on the current branch name.
33
+ """
34
+ jira_url = jira_url or os.getenv("JIRA_URL")
35
+ jira_username = (
36
+ jira_username
37
+ or os.getenv("JIRA_USERNAME")
38
+ or os.getenv("JIRA_USER")
39
+ or os.getenv("JIRA_EMAIL")
40
+ )
41
+ jira_token = (
42
+ jira_api_token
43
+ or os.getenv("JIRA_API_TOKEN")
44
+ or os.getenv("JIRA_API_KEY")
45
+ or os.getenv("JIRA_TOKEN")
46
+ )
47
+ try:
48
+ assert jira_url, "JIRA_URL is not set"
49
+ assert jira_username, "JIRA_USERNAME is not set"
50
+ assert jira_token, "JIRA_API_TOKEN is not set"
51
+ except AssertionError as e:
52
+ logging.error(f"Jira configuration error: {e}")
53
+ return None
54
+ issue_key = resolve_issue_key(repo)
55
+ return dict(
56
+ associated_issue=fetch_issue(issue_key, jira_url, jira_username, jira_token)
57
+ ) if issue_key else None
@@ -0,0 +1,84 @@
1
+ import logging
2
+ import os
3
+ import requests
4
+
5
+ import git
6
+
7
+ from gito.issue_trackers import IssueTrackerIssue, resolve_issue_key
8
+
9
+
10
+ def fetch_issue(issue_key, api_key) -> IssueTrackerIssue | None:
11
+ """
12
+ Fetch a Linear issue using GraphQL API.
13
+ """
14
+ try:
15
+ url = "https://api.linear.app/graphql"
16
+ headers = {
17
+ "Authorization": f"{api_key}",
18
+ "Content-Type": "application/json"
19
+ }
20
+
21
+ query = """
22
+ query Issues($teamKey: String!, $issueNumber: Float) {
23
+ issues(filter: {team: {key: {eq: $teamKey}}, number: {eq: $issueNumber}}) {
24
+ nodes {
25
+ id
26
+ identifier
27
+ title
28
+ description
29
+ url
30
+ }
31
+ }
32
+ }
33
+ """
34
+ team_key, issue_number = issue_key.split("-")
35
+ response = requests.post(
36
+ url,
37
+ json={
38
+ "query": query,
39
+ "variables": {'teamKey': team_key, 'issueNumber': int(issue_number)}
40
+ },
41
+ headers=headers
42
+ )
43
+ response.raise_for_status()
44
+ data = response.json()
45
+
46
+ if "errors" in data:
47
+ logging.error(f"Linear API error: {data['errors']}")
48
+ return None
49
+
50
+ nodes = data.get("data", {}).get("issues", {}).get("nodes", [])
51
+ if not nodes:
52
+ logging.error(f"Linear issue {issue_key} not found")
53
+ return None
54
+
55
+ issue = nodes[0]
56
+ return IssueTrackerIssue(
57
+ title=issue["title"],
58
+ description=issue.get("description") or "",
59
+ url=issue["url"]
60
+ )
61
+
62
+ except requests.HTTPError as e:
63
+ logging.error(f"Failed to fetch Linear issue {issue_key}: {e}")
64
+ logging.error(f"Response body: {response.text}")
65
+ return None
66
+
67
+
68
+ def fetch_associated_issue(
69
+ repo: git.Repo,
70
+ api_key=None,
71
+ **kwargs
72
+ ):
73
+ """
74
+ Pipeline step to fetch a Linear issue based on the current branch name.
75
+ """
76
+ api_key = api_key or os.getenv("LINEAR_API_KEY")
77
+ if not api_key:
78
+ logging.error("LINEAR_API_KEY environment variable is not set")
79
+ return
80
+
81
+ issue_key = resolve_issue_key(repo)
82
+ return dict(
83
+ associated_issue=fetch_issue(issue_key, api_key)
84
+ ) if issue_key else None
gito/project_config.py CHANGED
@@ -1,71 +1,73 @@
1
- import logging
2
- import tomllib
3
- from dataclasses import dataclass, field
4
- from pathlib import Path
5
-
6
- import microcore as mc
7
- from gito.utils import detect_github_env
8
- from microcore import ui
9
- from git import Repo
10
-
11
- from .constants import PROJECT_CONFIG_BUNDLED_DEFAULTS_FILE, PROJECT_CONFIG_FILE_PATH
12
- from .pipeline import PipelineStep
13
-
14
-
15
- @dataclass
16
- class ProjectConfig:
17
- prompt: str = ""
18
- summary_prompt: str = ""
19
- report_template_md: str = ""
20
- """Markdown report template"""
21
- report_template_cli: str = ""
22
- """Report template for CLI output"""
23
- post_process: str = ""
24
- retries: int = 3
25
- """LLM retries for one request"""
26
- max_code_tokens: int = 32000
27
- prompt_vars: dict = field(default_factory=dict)
28
- mention_triggers: list[str] = field(default_factory=list)
29
- """
30
- Defines the keyword or mention tag that triggers bot actions
31
- when referenced in code review comments.
32
- """
33
- pipeline_steps: dict[str, dict | PipelineStep] = field(default_factory=dict)
34
-
35
- def __post_init__(self):
36
- self.pipeline_steps = {
37
- k: PipelineStep(**v) if isinstance(v, dict) else v
38
- for k, v in self.pipeline_steps.items()
39
- }
40
-
41
- @staticmethod
42
- def _read_bundled_defaults() -> dict:
43
- with open(PROJECT_CONFIG_BUNDLED_DEFAULTS_FILE, "rb") as f:
44
- config = tomllib.load(f)
45
- return config
46
-
47
- @staticmethod
48
- def load_for_repo(repo: Repo):
49
- return ProjectConfig.load(Path(repo.working_tree_dir) / PROJECT_CONFIG_FILE_PATH)
50
-
51
- @staticmethod
52
- def load(config_path: str | Path | None = None) -> "ProjectConfig":
53
- config = ProjectConfig._read_bundled_defaults()
54
- github_env = detect_github_env()
55
- config["prompt_vars"] |= github_env | dict(github_env=github_env)
56
-
57
- config_path = Path(config_path or PROJECT_CONFIG_FILE_PATH)
58
- if config_path.exists():
59
- logging.info(
60
- f"Loading project-specific configuration from {mc.utils.file_link(config_path)}...")
61
- default_prompt_vars = config["prompt_vars"]
62
- with open(config_path, "rb") as f:
63
- config.update(tomllib.load(f))
64
- # overriding prompt_vars config section will not empty default values
65
- config["prompt_vars"] = default_prompt_vars | config["prompt_vars"]
66
- else:
67
- logging.info(
68
- f"No project config found at {ui.blue(config_path)}, using defaults"
69
- )
70
-
71
- return ProjectConfig(**config)
1
+ import logging
2
+ import tomllib
3
+ from dataclasses import dataclass, field
4
+ from pathlib import Path
5
+
6
+ import microcore as mc
7
+ from gito.utils import detect_github_env
8
+ from microcore import ui
9
+ from git import Repo
10
+
11
+ from .constants import PROJECT_CONFIG_BUNDLED_DEFAULTS_FILE, PROJECT_CONFIG_FILE_PATH
12
+ from .pipeline import PipelineStep
13
+
14
+
15
+ @dataclass
16
+ class ProjectConfig:
17
+ prompt: str = ""
18
+ summary_prompt: str = ""
19
+ answer_prompt: str = ""
20
+ report_template_md: str = ""
21
+ """Markdown report template"""
22
+ report_template_cli: str = ""
23
+ """Report template for CLI output"""
24
+ post_process: str = ""
25
+ retries: int = 3
26
+ """LLM retries for one request"""
27
+ max_code_tokens: int = 32000
28
+ prompt_vars: dict = field(default_factory=dict)
29
+ mention_triggers: list[str] = field(default_factory=list)
30
+ answer_github_comments: bool = field(default=True)
31
+ """
32
+ Defines the keyword or mention tag that triggers bot actions
33
+ when referenced in code review comments.
34
+ """
35
+ pipeline_steps: dict[str, dict | PipelineStep] = field(default_factory=dict)
36
+
37
+ def __post_init__(self):
38
+ self.pipeline_steps = {
39
+ k: PipelineStep(**v) if isinstance(v, dict) else v
40
+ for k, v in self.pipeline_steps.items()
41
+ }
42
+
43
+ @staticmethod
44
+ def _read_bundled_defaults() -> dict:
45
+ with open(PROJECT_CONFIG_BUNDLED_DEFAULTS_FILE, "rb") as f:
46
+ config = tomllib.load(f)
47
+ return config
48
+
49
+ @staticmethod
50
+ def load_for_repo(repo: Repo):
51
+ return ProjectConfig.load(Path(repo.working_tree_dir) / PROJECT_CONFIG_FILE_PATH)
52
+
53
+ @staticmethod
54
+ def load(config_path: str | Path | None = None) -> "ProjectConfig":
55
+ config = ProjectConfig._read_bundled_defaults()
56
+ github_env = detect_github_env()
57
+ config["prompt_vars"] |= github_env | dict(github_env=github_env)
58
+
59
+ config_path = Path(config_path or PROJECT_CONFIG_FILE_PATH)
60
+ if config_path.exists():
61
+ logging.info(
62
+ f"Loading project-specific configuration from {mc.utils.file_link(config_path)}...")
63
+ default_prompt_vars = config["prompt_vars"]
64
+ with open(config_path, "rb") as f:
65
+ config.update(tomllib.load(f))
66
+ # overriding prompt_vars config section will not empty default values
67
+ config["prompt_vars"] = default_prompt_vars | config["prompt_vars"]
68
+ else:
69
+ logging.info(
70
+ f"No project config found at {ui.blue(config_path)}, using defaults"
71
+ )
72
+
73
+ return ProjectConfig(**config)