ai-cr 2.0.2__py3-none-any.whl → 3.0.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.
gito/gh_api.py CHANGED
@@ -1,6 +1,12 @@
1
+ import os
1
2
  import logging
2
3
 
3
4
  import requests
5
+ from fastcore.basics import AttrDict # objects returned by ghapi
6
+
7
+
8
+ def resolve_gh_token(token_or_none: str | None = None) -> str | None:
9
+ return token_or_none or os.getenv("GITHUB_TOKEN", None) or os.getenv("GH_TOKEN", None)
4
10
 
5
11
 
6
12
  def post_gh_comment(
@@ -33,3 +39,47 @@ def post_gh_comment(
33
39
  else:
34
40
  logging.error(f"Failed to post comment: {resp.status_code} {resp.reason}\n{resp.text}")
35
41
  return False
42
+
43
+
44
+ def hide_gh_comment(
45
+ comment: dict | str,
46
+ token: str = None,
47
+ reason: str = "OUTDATED"
48
+ ) -> bool:
49
+ """
50
+ Hide a GitHub comment using GraphQL API with specified reason.
51
+ Args:
52
+ comment (dict | str):
53
+ The comment to hide,
54
+ either as a object returned from ghapi or a string node ID.
55
+ note: comment.id is not the same as node_id.
56
+ token (str): GitHub personal access token with permissions to minimize comments.
57
+ reason (str): The reason for hiding the comment, e.g., "OUTDATED".
58
+ """
59
+ comment_node_id = comment.node_id if isinstance(comment, AttrDict) else comment
60
+ token = resolve_gh_token(token)
61
+ mutation = """
62
+ mutation($commentId: ID!, $reason: ReportedContentClassifiers!) {
63
+ minimizeComment(input: {subjectId: $commentId, classifier: $reason}) {
64
+ minimizedComment { isMinimized }
65
+ }
66
+ }"""
67
+
68
+ response = requests.post(
69
+ "https://api.github.com/graphql",
70
+ headers={"Authorization": f"Bearer {token}"},
71
+ json={
72
+ "query": mutation,
73
+ "variables": {"commentId": comment_node_id, "reason": reason}
74
+ }
75
+ )
76
+ success = (
77
+ response.status_code == 200
78
+ and response.json().get("data", {}).get("minimizeComment") is not None
79
+ )
80
+ if not success:
81
+ logging.error(
82
+ f"Failed to hide comment {comment_node_id}: "
83
+ f"{response.status_code} {response.reason}\n{response.text}"
84
+ )
85
+ return success
gito/issue_trackers.py CHANGED
@@ -15,9 +15,10 @@ class IssueTrackerIssue:
15
15
 
16
16
 
17
17
  def extract_issue_key(branch_name: str, min_len=2, max_len=10) -> str | None:
18
- pattern = fr"\b[A-Z][A-Z0-9]{{{min_len - 1},{max_len - 1}}}-\d+\b"
18
+ boundary = r'\b|_|-|/|\\'
19
+ pattern = fr"(?:{boundary})([A-Z][A-Z0-9]{{{min_len - 1},{max_len - 1}}}-\d+)(?:{boundary})"
19
20
  match = re.search(pattern, branch_name)
20
- return match.group(0) if match else None
21
+ return match.group(1) if match else None
21
22
 
22
23
 
23
24
  def get_branch(repo: git.Repo):
gito/pipeline.py CHANGED
@@ -2,10 +2,12 @@ import logging
2
2
  from enum import StrEnum
3
3
  from dataclasses import dataclass, field
4
4
 
5
- from gito.utils import is_running_in_github_action
6
5
  from microcore import ui
7
6
  from microcore.utils import resolve_callable
8
7
 
8
+ from .context import Context
9
+ from .utils import is_running_in_github_action
10
+
9
11
 
10
12
  class PipelineEnv(StrEnum):
11
13
  LOCAL = "local"
@@ -42,7 +44,7 @@ class PipelineStep:
42
44
 
43
45
  @dataclass
44
46
  class Pipeline:
45
- ctx: dict = field(default_factory=dict)
47
+ ctx: Context = field()
46
48
  steps: dict[str, PipelineStep] = field(default_factory=dict)
47
49
  verbose: bool = False
48
50
 
@@ -55,15 +57,14 @@ class Pipeline:
55
57
  def run(self, *args, **kwargs):
56
58
  cur_env = PipelineEnv.current()
57
59
  logging.info("Running pipeline... [env: %s]", ui.yellow(cur_env))
58
- self.ctx["pipeline_out"] = self.ctx.get("pipeline_out", {})
59
60
  for step_name, step in self.enabled_steps.items():
60
61
  if cur_env in step.envs:
61
62
  logging.info(f"Running pipeline step: {step_name}")
62
63
  try:
63
- step_output = step.run(*args, **kwargs, **self.ctx)
64
+ step_output = step.run(*args, **kwargs, **vars(self.ctx))
64
65
  if isinstance(step_output, dict):
65
- self.ctx["pipeline_out"].update(step_output)
66
- self.ctx["pipeline_out"][step_name] = step_output
66
+ self.ctx.pipeline_out.update(step_output)
67
+ self.ctx.pipeline_out[step_name] = step_output
67
68
  if self.verbose and step_output:
68
69
  logging.info(
69
70
  f"Pipeline step {step_name} output: {repr(step_output)}"
@@ -79,4 +80,4 @@ class Pipeline:
79
80
  f"Skipping pipeline step: {step_name}"
80
81
  f" [env: {ui.yellow(cur_env)} not in {step.envs}]"
81
82
  )
82
- return self.ctx["pipeline_out"]
83
+ return self.ctx.pipeline_out
@@ -2,7 +2,7 @@ import logging
2
2
  import os
3
3
 
4
4
  import git
5
- from jira import JIRA
5
+ from jira import JIRA, JIRAError
6
6
 
7
7
  from gito.issue_trackers import IssueTrackerIssue, resolve_issue_key
8
8
 
@@ -16,6 +16,11 @@ def fetch_issue(issue_key, jira_url, username, api_token) -> IssueTrackerIssue |
16
16
  description=issue.fields.description or "",
17
17
  url=f"{jira_url.rstrip('/')}/browse/{issue_key}"
18
18
  )
19
+ except JIRAError as e:
20
+ logging.error(
21
+ f"Failed to fetch Jira issue {issue_key}: code {e.status_code} :: {e.text}"
22
+ )
23
+ return None
19
24
  except Exception as e:
20
25
  logging.error(f"Failed to fetch Jira issue {issue_key}: {e}")
21
26
  return None
gito/project_config.py CHANGED
@@ -33,6 +33,11 @@ class ProjectConfig:
33
33
  when referenced in code review comments.
34
34
  """
35
35
  pipeline_steps: dict[str, dict | PipelineStep] = field(default_factory=dict)
36
+ collapse_previous_code_review_comments: bool = field(default=True)
37
+ """
38
+ If True, previously added code review comments in the pull request
39
+ will be collapsed automatically when a new comment is added.
40
+ """
36
41
 
37
42
  def __post_init__(self):
38
43
  self.pipeline_steps = {
@@ -61,10 +66,16 @@ class ProjectConfig:
61
66
  logging.info(
62
67
  f"Loading project-specific configuration from {mc.utils.file_link(config_path)}...")
63
68
  default_prompt_vars = config["prompt_vars"]
69
+ default_pipeline_steps = config["pipeline_steps"]
64
70
  with open(config_path, "rb") as f:
65
71
  config.update(tomllib.load(f))
66
72
  # overriding prompt_vars config section will not empty default values
67
73
  config["prompt_vars"] = default_prompt_vars | config["prompt_vars"]
74
+ # merge individual pipeline steps
75
+ for k, v in config["pipeline_steps"].items():
76
+ config["pipeline_steps"][k] = default_pipeline_steps.get(k, {}) | v
77
+ # merge pipeline steps dict
78
+ config["pipeline_steps"] = default_pipeline_steps | config["pipeline_steps"]
68
79
  else:
69
80
  logging.info(
70
81
  f"No project config found at {ui.blue(config_path)}, using defaults"
gito/report_struct.py CHANGED
@@ -10,9 +10,9 @@ from colorama import Fore, Style, Back
10
10
  from microcore.utils import file_link
11
11
  import textwrap
12
12
 
13
- from .constants import JSON_REPORT_FILE_NAME, HTML_TEXT_ICON
13
+ from .constants import JSON_REPORT_FILE_NAME, HTML_TEXT_ICON, HTML_CR_COMMENT_MARKER
14
14
  from .project_config import ProjectConfig
15
- from .utils import syntax_hint, block_wrap_lr, max_line_len
15
+ from .utils import syntax_hint, block_wrap_lr, max_line_len, remove_html_comments
16
16
 
17
17
 
18
18
  @dataclass
@@ -125,6 +125,8 @@ class Report:
125
125
  block_wrap_lr=block_wrap_lr,
126
126
  max_line_len=max_line_len,
127
127
  HTML_TEXT_ICON=HTML_TEXT_ICON,
128
+ HTML_CR_COMMENT_MARKER=HTML_CR_COMMENT_MARKER,
129
+ remove_html_comments=remove_html_comments,
128
130
  **config.prompt_vars
129
131
  )
130
132
 
@@ -0,0 +1,10 @@
1
+
2
+ LLM_API_TYPE: {{ api_type }}
3
+ LLM_API_KEY: {{ "${{ secrets." + secret_name + " }}" }}
4
+ MODEL: {{ model }}
5
+ {% raw -%}
6
+ JIRA_TOKEN: ${{ secrets.JIRA_TOKEN }}
7
+ JIRA_URL: ${{ secrets.JIRA_URL }}
8
+ JIRA_USER: ${{ secrets.JIRA_USER }}
9
+ LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
10
+ {%- endraw %}
@@ -0,0 +1,23 @@
1
+
2
+
3
+ - name: Set up Python
4
+ uses: actions/setup-python@v5
5
+ with: { python-version: "3.13" }
6
+
7
+ - name: Fetch Latest Gito Version
8
+ id: gito-version
9
+ run: pip index versions gito.bot 2>/dev/null | head -1 | sed -n 's/.* (\([^)]*\)).*/version=\1/p' >> $GITHUB_OUTPUT
10
+
11
+ {% raw -%}
12
+ - uses: actions/cache@v4
13
+ id: cache
14
+ with:
15
+ path: |
16
+ ${{ env.pythonLocation }}/lib/python3.13/site-packages
17
+ ${{ env.pythonLocation }}/bin
18
+ key: gito_v${{ steps.gito-version.outputs.version }}
19
+ {%- endraw %}
20
+
21
+ - name: Install Gito
22
+ if: steps.cache.outputs.cache-hit != 'true'
23
+ run: pip install gito.bot~={{ major }}.{{ minor }}
@@ -0,0 +1,33 @@
1
+ name: "Gito: AI Code Reviewer"
2
+ on:
3
+ pull_request:
4
+ types: [opened, synchronize, reopened]
5
+ workflow_dispatch:
6
+ inputs:
7
+ pr_number:
8
+ description: "Pull Request number"
9
+ required: true
10
+ jobs:
11
+ review:
12
+ runs-on: ubuntu-latest
13
+ permissions: { contents: read, pull-requests: write } # 'write' for leaving the summary comment
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ with: { fetch-depth: 0 }
17
+
18
+ {%- include("github_workflows/components/installs.j2") %}
19
+
20
+ - name: Run AI code review
21
+ env:
22
+ {%- include("github_workflows/components/env-vars.j2") %}
23
+ PR_NUMBER_FROM_WORKFLOW_DISPATCH: {% raw %}${{ github.event.inputs.pr_number }}{% endraw %}
24
+ run: |{% raw %}
25
+ gito --verbose review ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref && format(' --against="origin/{0}"', github.event.pull_request.base.ref) || '' }}
26
+ gito github-comment --token ${{ secrets.GITHUB_TOKEN }}{% endraw %}
27
+
28
+ - uses: actions/upload-artifact@v4
29
+ with:
30
+ name: gito-code-review-results
31
+ path: |
32
+ code-review-report.md
33
+ code-review-report.json
@@ -0,0 +1,70 @@
1
+ name: "Gito: React to GitHub comment"
2
+
3
+ on:
4
+ issue_comment:
5
+ types: [created]
6
+
7
+ permissions:
8
+ contents: write # to make PR
9
+ issues: write
10
+ pull-requests: write
11
+ # read: to download the code review artifact
12
+ # write: to trigger other actions
13
+ actions: write
14
+
15
+ jobs:
16
+ process-comment:
17
+ if: |
18
+ github.event.issue.pull_request &&
19
+ (
20
+ github.event.comment.author_association == 'OWNER' ||
21
+ github.event.comment.author_association == 'MEMBER' ||
22
+ github.event.comment.author_association == 'COLLABORATOR'
23
+ ) &&
24
+ (
25
+ startsWith(github.event.comment.body, '/') ||
26
+ startsWith(github.event.comment.body, 'gito') ||
27
+ startsWith(github.event.comment.body, 'ai') ||
28
+ startsWith(github.event.comment.body, 'bot') ||
29
+ contains(github.event.comment.body, '@gito') ||
30
+ contains(github.event.comment.body, '@ai') ||
31
+ contains(github.event.comment.body, '@bot')
32
+ )
33
+ runs-on: ubuntu-latest
34
+
35
+ steps:
36
+ - name: Get PR details
37
+ id: pr
38
+ uses: actions/github-script@v7
39
+ with:
40
+ script: |
41
+ const pr = await github.rest.pulls.get({
42
+ owner: context.repo.owner,
43
+ repo: context.repo.repo,
44
+ pull_number: context.issue.number
45
+ });
46
+ return {
47
+ head_ref: pr.data.head.ref,
48
+ head_sha: pr.data.head.sha,
49
+ base_ref: pr.data.base.ref
50
+ };
51
+
52
+ - name: Checkout repository
53
+ uses: actions/checkout@v4
54
+ with:
55
+ {% raw -%}
56
+ repository: ${{ github.repository }}
57
+ token: ${{ secrets.GITHUB_TOKEN }}
58
+ ref: ${{ fromJson(steps.pr.outputs.result).head_ref }}
59
+ fetch-depth: 0
60
+ {%- endraw %}
61
+
62
+ {%- include("github_workflows/components/installs.j2") %}
63
+
64
+ - name: Run Gito react
65
+ env:
66
+ # LLM config is needed only if answer_github_comments = true in .gito/config.toml
67
+ # Otherwise, use LLM_API_TYPE: none
68
+ {%- include("github_workflows/components/env-vars.j2") %}
69
+ run: |
70
+ {% raw %}gito react-to-comment ${{ github.event.comment.id }} --token ${{ secrets.GITHUB_TOKEN }}{%- endraw %}
@@ -0,0 +1,24 @@
1
+ {{ self_id }}
2
+ ----TASK----
3
+ Write release notes for public documentation.
4
+ Summarize the following changes, focusing on what is new, improved, or fixed for the end user.
5
+ Do not include internal or technical details.
6
+ Structure release notes using clear sections: Added, Changed, Fixed.
7
+ Avoid internal technical jargon or developer-specific details.
8
+
9
+
10
+ ----RELATED CODEBASE CHANGES----
11
+ {% for part in diff %}{{ part }}\n{% endfor %}
12
+
13
+ ----FULL FILE CONTENT AFTER APPLYING CHANGES----
14
+ {% for file, file_lines in all_file_lines.items() %}
15
+ --FILE: {{ file }}--
16
+ {{ file_lines }}
17
+ {% endfor %}
18
+
19
+ {%- if pipeline_out.associated_issue and pipeline_out.associated_issue.title %}
20
+ ----ASSOCIATED ISSUE----
21
+ # {{ pipeline_out.associated_issue.title }}
22
+ {{ pipeline_out.associated_issue.description }}
23
+ URL: {{ pipeline_out.associated_issue.url }}
24
+ {%- endif -%}{{ '\n' }}
gito/utils.py CHANGED
@@ -2,11 +2,12 @@ import re
2
2
  import sys
3
3
  import os
4
4
  from pathlib import Path
5
+ import importlib.metadata
6
+ from typing import Optional
5
7
 
6
8
  import typer
7
9
  import git
8
10
  from git import Repo
9
- from microcore import ui
10
11
 
11
12
 
12
13
  _EXT_TO_HINT: dict[str, str] = {
@@ -222,5 +223,20 @@ def detect_github_env() -> dict:
222
223
  return d
223
224
 
224
225
 
225
- def stream_to_cli(text):
226
- print(ui.blue(text), end='')
226
+ def make_streaming_function(handler: Optional[callable] = None) -> callable:
227
+ def stream(text):
228
+ if handler:
229
+ text = handler(text)
230
+ print(text, end='', flush=True)
231
+ return stream
232
+
233
+
234
+ def version() -> str:
235
+ return importlib.metadata.version("gito.bot")
236
+
237
+
238
+ def remove_html_comments(text):
239
+ """
240
+ Removes all HTML comments (<!-- ... -->) from the input text.
241
+ """
242
+ return re.sub(r'<!--.*?-->\s*', '', text, flags=re.DOTALL)
@@ -1,26 +0,0 @@
1
- gito/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- gito/__main__.py,sha256=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
3
- gito/bootstrap.py,sha256=ceeoSCD8RX4xKxWri_n0xx00aTFS6xPUHhAs3rKVP5o,2337
4
- gito/cli.py,sha256=qFY1ObA0-Ep19bNSEcwjn6KIu2OOSWDj6QGe_o7Ujoc,6555
5
- gito/commands/__init__.py,sha256=B2uUQsLMEsHfNT1N3lWYm38WSuQIHFmjiGs2tdBuDBA,55
6
- gito/commands/fix.py,sha256=OS829xVrLMLQqlol9AoxJMZ9qaAWRCSbUkVNszK96ws,5305
7
- gito/commands/gh_post_review_comment.py,sha256=oZh3kSRBx3cSMozI2llDEOVZ2WeT_5msXMLLCiLina4,1984
8
- gito/commands/gh_react_to_comment.py,sha256=4uUb39FnM45DO-wESQq6pUlO5AFvh0FSluCCTxkl8_g,6442
9
- gito/commands/repl.py,sha256=wDHWl2VyRK32hLZHImrhqpQNkWV3u3358fwDiA6D3iA,521
10
- gito/config.toml,sha256=iK0CeP3mynTnWIEsCi_jA9Gtb_UdAqwLC5nWHSTc-zc,17367
11
- gito/constants.py,sha256=9G4sZbqLBg13FYieJqkq_hzek4-smmL4KKYF64tovQQ,698
12
- gito/core.py,sha256=tp-eB_FIsZ2wffTiMrRLc_9MaFaGIky_ExWHWTf1DTk,9341
13
- gito/gh_api.py,sha256=syhM8sbs_lLNG53bfRHQpusO818nwXCl4jDxVY__IK0,1204
14
- gito/issue_trackers.py,sha256=nfib6zhvmL_zjRDCdZ0d6rMT4ZFJ3PxO5UORaJzb6gk,1495
15
- gito/pipeline.py,sha256=Nq5VUVrXVDXVVYX6nVSMX1CyhoWsNtSeTOQyNgQAsdE,2672
16
- gito/pipeline_steps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- gito/pipeline_steps/jira.py,sha256=cgRSHoU6j0lcK6hQfhg950PEmIu3uWLGMKZbW3-H3mg,1683
18
- gito/pipeline_steps/linear.py,sha256=6UDc8nGKGpwHruPq8VItE2QBWshWxaTapoMhu_qjN_g,2445
19
- gito/project_config.py,sha256=XdpXsRDFWBNNpktbZDp9p-nbijyKjG_oD6UWzGZSLjM,2580
20
- gito/report_struct.py,sha256=tLhdmCPse3Jbo56y752vuGlndWY2f2g_mNFQ-BmJZGw,4160
21
- gito/utils.py,sha256=HpterYtjceVhklJe0w1n7PIQJk8MJFUYi4FiG-CzMqg,6418
22
- ai_cr-2.0.2.dist-info/LICENSE,sha256=VbdF_GbbDK24JvdTfnsxa2M6jmhsxmRSFeHCx-lICGE,1075
23
- ai_cr-2.0.2.dist-info/METADATA,sha256=K8YJPk8Mv3GwYKbGe3gpE4qJgoWfv2wQBemoXOe-awc,7989
24
- ai_cr-2.0.2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
25
- ai_cr-2.0.2.dist-info/entry_points.txt,sha256=Ua1DxkhJJ8TZuLgnH-IlWCkrre_0S0dq_GtYRaYupWk,38
26
- ai_cr-2.0.2.dist-info/RECORD,,
File without changes
File without changes