commitai 1.0.4__py3-none-any.whl → 2.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.
- commitai/__init__.py +1 -1
- commitai/agent.py +222 -0
- commitai/chains.py +135 -0
- commitai/cli.py +146 -105
- commitai/template.py +21 -0
- {commitai-1.0.4.dist-info → commitai-2.0.0.dist-info}/METADATA +35 -20
- commitai-2.0.0.dist-info/RECORD +11 -0
- {commitai-1.0.4.dist-info → commitai-2.0.0.dist-info}/WHEEL +1 -1
- commitai-1.0.4.dist-info/RECORD +0 -9
- {commitai-1.0.4.dist-info → commitai-2.0.0.dist-info}/entry_points.txt +0 -0
- {commitai-1.0.4.dist-info → commitai-2.0.0.dist-info}/licenses/LICENSE +0 -0
commitai/__init__.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# This __version__ string is read by hatchling during the build process
|
|
5
5
|
# Make sure to update it for new releases.
|
|
6
|
-
__version__ = "0.
|
|
6
|
+
__version__ = "2.0.0"
|
|
7
7
|
|
|
8
8
|
# The importlib.metadata approach is generally for reading the version
|
|
9
9
|
# of an *already installed* package at runtime. We don't need it here
|
commitai/agent.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
from typing import Any, Dict, Type
|
|
5
|
+
|
|
6
|
+
from langchain.agents import AgentExecutor, create_tool_calling_agent
|
|
7
|
+
from langchain_core.language_models import BaseChatModel
|
|
8
|
+
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
|
9
|
+
from langchain_core.runnables import Runnable
|
|
10
|
+
from langchain_core.tools import BaseTool
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
|
|
13
|
+
# --- TOOLS ---
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ShellInput(BaseModel):
|
|
17
|
+
command: str = Field(
|
|
18
|
+
description=(
|
|
19
|
+
"The git command to execute (e.g., 'git status', 'git log'). "
|
|
20
|
+
"Must start with 'git'."
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ReadOnlyShellTool(BaseTool):
|
|
26
|
+
name: str = "git_shell"
|
|
27
|
+
description: str = (
|
|
28
|
+
"Run read-only git commands to inspect the repository state. "
|
|
29
|
+
"Only 'git' commands are allowed. Write operations are blocked."
|
|
30
|
+
)
|
|
31
|
+
args_schema: Type[BaseModel] = ShellInput
|
|
32
|
+
|
|
33
|
+
def _run(self, command: str) -> str:
|
|
34
|
+
command = command.strip()
|
|
35
|
+
if not command.startswith("git"):
|
|
36
|
+
return "Error: Only 'git' commands are allowed."
|
|
37
|
+
|
|
38
|
+
# Simple blocklist for write operations
|
|
39
|
+
forbidden = [
|
|
40
|
+
"push",
|
|
41
|
+
"pull",
|
|
42
|
+
"commit",
|
|
43
|
+
"merge",
|
|
44
|
+
"rebase",
|
|
45
|
+
"cherry-pick",
|
|
46
|
+
"stash",
|
|
47
|
+
"clean",
|
|
48
|
+
"reset",
|
|
49
|
+
"checkout",
|
|
50
|
+
"switch",
|
|
51
|
+
"branch",
|
|
52
|
+
]
|
|
53
|
+
if any(w in command.split() for w in forbidden):
|
|
54
|
+
return f"Error: Command '{command}' contains forbidden write operations."
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
# shell=True is dangerous in general, but we heavily restricted input above
|
|
58
|
+
result = subprocess.run(
|
|
59
|
+
command, shell=True, capture_output=True, text=True, cwd=os.getcwd()
|
|
60
|
+
)
|
|
61
|
+
if result.returncode != 0:
|
|
62
|
+
return f"Error ({result.returncode}): {result.stderr}"
|
|
63
|
+
return result.stdout
|
|
64
|
+
except Exception as e:
|
|
65
|
+
return f"Execution Error: {str(e)}"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class FileSearchInput(BaseModel):
|
|
69
|
+
pattern: str = Field(
|
|
70
|
+
description="The glob pattern to search for files (e.g., 'src/**/*.py')."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class FileSearchTool(BaseTool):
|
|
75
|
+
name: str = "file_search"
|
|
76
|
+
description: str = (
|
|
77
|
+
"Search for file paths in the project using glob patterns. "
|
|
78
|
+
"Useful to find files to inspect."
|
|
79
|
+
)
|
|
80
|
+
args_schema: Type[BaseModel] = FileSearchInput
|
|
81
|
+
|
|
82
|
+
def _run(self, pattern: str) -> str:
|
|
83
|
+
try:
|
|
84
|
+
# Security: prevent breaking out of repo?
|
|
85
|
+
# For simplicity, just run glob.
|
|
86
|
+
if ".." in pattern:
|
|
87
|
+
return "Error: '..' not allowed in patterns."
|
|
88
|
+
|
|
89
|
+
files = glob.glob(pattern, recursive=True)
|
|
90
|
+
if not files:
|
|
91
|
+
return "No files found."
|
|
92
|
+
return "\n".join(files[:20]) # Limit output
|
|
93
|
+
except Exception as e:
|
|
94
|
+
return f"Error: {str(e)}"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class FileReadInput(BaseModel):
|
|
98
|
+
file_path: str = Field(description="The path of the file to read.")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class FileReadTool(BaseTool):
|
|
102
|
+
name: str = "file_read"
|
|
103
|
+
description: str = "Read the contents of a specific file."
|
|
104
|
+
args_schema: Type[BaseModel] = FileReadInput
|
|
105
|
+
|
|
106
|
+
def _run(self, file_path: str) -> str:
|
|
107
|
+
if ".." in file_path:
|
|
108
|
+
return "Error: Traversing up directories is not allowed."
|
|
109
|
+
if not os.path.exists(file_path):
|
|
110
|
+
return "Error: File does not exist."
|
|
111
|
+
try:
|
|
112
|
+
with open(file_path, "r") as f:
|
|
113
|
+
content = f.read()
|
|
114
|
+
return content[:2000] # Truncate large files
|
|
115
|
+
except Exception as e:
|
|
116
|
+
return f"Error reading file: {str(e)}"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# --- MIDDLEWARE (Simulated for Agent) ---
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class SummarizationMiddleware:
|
|
123
|
+
"""Uses LLM to summarize diff before agent sees it."""
|
|
124
|
+
|
|
125
|
+
def __init__(self, llm: BaseChatModel):
|
|
126
|
+
self.llm = llm
|
|
127
|
+
|
|
128
|
+
def process(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
|
|
129
|
+
diff = inputs.get("diff", "")
|
|
130
|
+
if not diff:
|
|
131
|
+
return inputs
|
|
132
|
+
|
|
133
|
+
# Simple summarization chain (inline invocation)
|
|
134
|
+
# Truncate for summary
|
|
135
|
+
msg = f"Summarize these changes in 2 sentences:\n\n{diff[:5000]}"
|
|
136
|
+
resp = self.llm.invoke(msg)
|
|
137
|
+
inputs["summary"] = resp.content
|
|
138
|
+
return inputs
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class TodoMiddleware:
|
|
142
|
+
"""Scans diff for TODOs and adds to inputs."""
|
|
143
|
+
|
|
144
|
+
def process(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
|
|
145
|
+
diff = inputs.get("diff", "")
|
|
146
|
+
todos = []
|
|
147
|
+
for line in diff.splitlines():
|
|
148
|
+
if line.startswith("+") and any(
|
|
149
|
+
x in line.lower() for x in ["todo", "fixme"]
|
|
150
|
+
):
|
|
151
|
+
todos.append(line[1:].strip())
|
|
152
|
+
inputs["todos"] = todos
|
|
153
|
+
inputs["todo_str"] = "\n".join(f"- {t}" for t in todos) if todos else "None"
|
|
154
|
+
return inputs
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# --- AGENT ---
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def create_commit_agent(llm: BaseChatModel) -> Runnable:
|
|
161
|
+
# 1. Init Tools
|
|
162
|
+
tools = [ReadOnlyShellTool(), FileSearchTool(), FileReadTool()]
|
|
163
|
+
|
|
164
|
+
# 2. Middlewares
|
|
165
|
+
summ_mw = SummarizationMiddleware(llm)
|
|
166
|
+
todo_mw = TodoMiddleware()
|
|
167
|
+
|
|
168
|
+
# 3. Prompt
|
|
169
|
+
system_prompt = """You are an expert software engineer acting as a Commit Assistant.
|
|
170
|
+
Your goal is to generate a conventional commit message.
|
|
171
|
+
|
|
172
|
+
Context:
|
|
173
|
+
- User Explanation: {explanation}
|
|
174
|
+
- Detected TODOs: {todo_str}
|
|
175
|
+
- Auto-Summary: {summary}
|
|
176
|
+
- Staged Diff: {diff}
|
|
177
|
+
|
|
178
|
+
You have access to tools to explore the codebase if the diff + explanation is ambiguous.
|
|
179
|
+
- Use `git_shell` to check status or logs.
|
|
180
|
+
- Use `file_search` and `file_read` to understand context of modified files.
|
|
181
|
+
|
|
182
|
+
Protocol:
|
|
183
|
+
1. Analyze the input.
|
|
184
|
+
2. If detecting POTENTIAL SENSITIVE DATA (API keys, secrets) in the diff, you MUST stop
|
|
185
|
+
and ask the user (simulated by returning a warning message).
|
|
186
|
+
3. If clarification is needed, explore files.
|
|
187
|
+
4. Final Answer MUST be ONLY the commit message.
|
|
188
|
+
"""
|
|
189
|
+
prompt = ChatPromptTemplate.from_messages(
|
|
190
|
+
[
|
|
191
|
+
("system", system_prompt),
|
|
192
|
+
MessagesPlaceholder("chat_history", optional=True),
|
|
193
|
+
("human", "Generate the commit message."),
|
|
194
|
+
MessagesPlaceholder("agent_scratchpad"),
|
|
195
|
+
]
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# 4. Construct Agent
|
|
199
|
+
agent = create_tool_calling_agent(llm, tools, prompt)
|
|
200
|
+
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=False)
|
|
201
|
+
|
|
202
|
+
# 5. Pipeline with Middleware
|
|
203
|
+
def run_pipeline(inputs: Dict[str, Any]) -> str:
|
|
204
|
+
# Run Middleware
|
|
205
|
+
state = inputs.copy()
|
|
206
|
+
state = todo_mw.process(state)
|
|
207
|
+
state = summ_mw.process(state)
|
|
208
|
+
|
|
209
|
+
# Inject formatted fields if missing
|
|
210
|
+
state.setdefault("explanation", "None")
|
|
211
|
+
state.setdefault("summary", "None")
|
|
212
|
+
state.setdefault("todo_str", "None")
|
|
213
|
+
state.setdefault("chat_history", [])
|
|
214
|
+
|
|
215
|
+
# Run Agent
|
|
216
|
+
result = agent_executor.invoke(state)
|
|
217
|
+
return str(result["output"])
|
|
218
|
+
|
|
219
|
+
# Wrap in RunnableLambda to expose 'invoke'
|
|
220
|
+
from langchain_core.runnables import RunnableLambda
|
|
221
|
+
|
|
222
|
+
return RunnableLambda(run_pipeline)
|
commitai/chains.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional, TypedDict
|
|
2
|
+
|
|
3
|
+
from langchain_core.language_models import BaseChatModel
|
|
4
|
+
from langchain_core.output_parsers import StrOutputParser
|
|
5
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
6
|
+
from langchain_core.runnables import Runnable, RunnableLambda, RunnablePassthrough
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CommitState(TypedDict):
|
|
10
|
+
diff: str
|
|
11
|
+
explanation: Optional[str]
|
|
12
|
+
summary: Optional[str]
|
|
13
|
+
todos: Optional[List[str]]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SummarizationMiddleware:
|
|
17
|
+
"""Middleware to summarize the diff before generating the commit message."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, llm: BaseChatModel):
|
|
20
|
+
self.llm = llm
|
|
21
|
+
self.prompt = ChatPromptTemplate.from_template(
|
|
22
|
+
"Summarize the following code changes concisely in 1-2 sentences:\n\n{diff}"
|
|
23
|
+
)
|
|
24
|
+
self.chain = self.prompt | self.llm | StrOutputParser()
|
|
25
|
+
|
|
26
|
+
def __call__(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
|
|
27
|
+
"""Run the summarizer and add 'summary' to the state."""
|
|
28
|
+
diff = inputs.get("diff", "")
|
|
29
|
+
if not diff:
|
|
30
|
+
return {**inputs, "summary": ""}
|
|
31
|
+
|
|
32
|
+
# We invoke the chain synchronously here
|
|
33
|
+
summary = self.chain.invoke({"diff": diff})
|
|
34
|
+
return {**inputs, "summary": summary}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TodoMiddleware:
|
|
38
|
+
"""Middleware to scan the diff for TODO/FIXME/HACK comments."""
|
|
39
|
+
|
|
40
|
+
def __call__(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
|
|
41
|
+
diff = inputs.get("diff", "")
|
|
42
|
+
todos = []
|
|
43
|
+
for line in diff.splitlines():
|
|
44
|
+
if line.startswith("+"):
|
|
45
|
+
lower_line = line.lower()
|
|
46
|
+
if (
|
|
47
|
+
"todo" in lower_line
|
|
48
|
+
or "fixme" in lower_line
|
|
49
|
+
or "hack" in lower_line
|
|
50
|
+
):
|
|
51
|
+
# Strip the + and whitespace
|
|
52
|
+
clean_line = line[1:].strip()
|
|
53
|
+
todos.append(clean_line)
|
|
54
|
+
|
|
55
|
+
return {**inputs, "todos": todos}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def create_commit_chain(llm: BaseChatModel) -> Runnable:
|
|
59
|
+
"""Creates the full commit generation pipeline with middleware."""
|
|
60
|
+
|
|
61
|
+
# 1. Initialize Middlewares
|
|
62
|
+
summarizer = SummarizationMiddleware(llm)
|
|
63
|
+
todo_scanner = TodoMiddleware()
|
|
64
|
+
|
|
65
|
+
# 2. Define the Prompt
|
|
66
|
+
# We include placeholders for summary and todos if they exist
|
|
67
|
+
system_template = (
|
|
68
|
+
"You are an expert software engineer and git commit message generator.\n"
|
|
69
|
+
"Your task is to generate a clean, concise commit message following the "
|
|
70
|
+
"Conventional Commits specification.\n\n"
|
|
71
|
+
"Values from middleware:\n"
|
|
72
|
+
"{summary_section}\n"
|
|
73
|
+
"{todo_section}\n\n"
|
|
74
|
+
"Input context:\n"
|
|
75
|
+
"{explanation_section}\n\n"
|
|
76
|
+
"Existing Code Changes (Diff):\n"
|
|
77
|
+
"{diff}\n\n"
|
|
78
|
+
"Instructions:\n"
|
|
79
|
+
"1. Use the format: <type>(<scope>): <subject>\n"
|
|
80
|
+
"2. Keep the subject line under 50 characters if possible.\n"
|
|
81
|
+
"3. If there are multiple changes, provide a bulleted body.\n"
|
|
82
|
+
"4. If TODOs were detected, mention them in the footer or body as "
|
|
83
|
+
"appropriate.\n"
|
|
84
|
+
"5. If an explanation is provided, prioritize it.\n"
|
|
85
|
+
)
|
|
86
|
+
prompt = ChatPromptTemplate.from_template(system_template)
|
|
87
|
+
|
|
88
|
+
# 3. Helper to format the prompt inputs from state
|
|
89
|
+
def format_inputs(state: CommitState) -> Dict[str, Any]:
|
|
90
|
+
summary = state.get("summary")
|
|
91
|
+
todos = state.get("todos")
|
|
92
|
+
explanation = state.get("explanation")
|
|
93
|
+
|
|
94
|
+
summary_section = f"Summary of changes:\n{summary}\n" if summary else ""
|
|
95
|
+
|
|
96
|
+
todo_section = ""
|
|
97
|
+
if todos:
|
|
98
|
+
todo_section = (
|
|
99
|
+
"Detected TODOs in this diff:\n"
|
|
100
|
+
+ "\n".join(f"- {t}" for t in todos)
|
|
101
|
+
+ "\n"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
explanation_section = ""
|
|
105
|
+
if explanation:
|
|
106
|
+
explanation_section = f"User Explanation:\n{explanation}\n"
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
"diff": state["diff"],
|
|
110
|
+
"summary_section": summary_section,
|
|
111
|
+
"todo_section": todo_section,
|
|
112
|
+
"explanation_section": explanation_section,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# 4. Construct the Pipeline
|
|
116
|
+
# Parallel step to run middlewares
|
|
117
|
+
# (conceptually, though here we chain them or use RunnablePassthrough)
|
|
118
|
+
# Since middlewares modify state, we can chain them:
|
|
119
|
+
|
|
120
|
+
middleware_chain: Runnable = (
|
|
121
|
+
RunnablePassthrough()
|
|
122
|
+
| RunnableLambda(todo_scanner)
|
|
123
|
+
| RunnableLambda(summarizer)
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Final generation chain
|
|
127
|
+
generation_chain = (
|
|
128
|
+
middleware_chain
|
|
129
|
+
| RunnableLambda(format_inputs)
|
|
130
|
+
| prompt
|
|
131
|
+
| llm
|
|
132
|
+
| StrOutputParser()
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
return generation_chain
|
commitai/cli.py
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
4
|
import os
|
|
5
|
+
import sys
|
|
5
6
|
from typing import Optional, Tuple
|
|
6
7
|
|
|
7
8
|
import click
|
|
8
|
-
from langchain_anthropic import ChatAnthropic
|
|
9
9
|
from langchain_core.language_models.chat_models import BaseChatModel
|
|
10
|
-
from langchain_openai import ChatOpenAI
|
|
11
10
|
|
|
12
11
|
# Keep SecretStr import in case it's needed elsewhere or for future refinement
|
|
13
12
|
|
|
@@ -17,6 +16,7 @@ try:
|
|
|
17
16
|
except ImportError:
|
|
18
17
|
ChatGoogleGenerativeAI = None # type: ignore
|
|
19
18
|
|
|
19
|
+
from commitai.agent import create_commit_agent
|
|
20
20
|
from commitai.git import (
|
|
21
21
|
create_commit,
|
|
22
22
|
get_commit_template,
|
|
@@ -27,14 +27,8 @@ from commitai.git import (
|
|
|
27
27
|
save_commit_template,
|
|
28
28
|
stage_all_changes,
|
|
29
29
|
)
|
|
30
|
-
from commitai.template import (
|
|
31
|
-
adding_template,
|
|
32
|
-
build_user_message,
|
|
33
|
-
default_system_message,
|
|
34
|
-
)
|
|
35
30
|
|
|
36
31
|
|
|
37
|
-
# Helper function to get API key with priority
|
|
38
32
|
def _get_google_api_key() -> Optional[str]:
|
|
39
33
|
"""Gets the Google API key from environment variables in priority order."""
|
|
40
34
|
return (
|
|
@@ -44,100 +38,55 @@ def _get_google_api_key() -> Optional[str]:
|
|
|
44
38
|
)
|
|
45
39
|
|
|
46
40
|
|
|
47
|
-
# Helper function to initialize the LLM
|
|
48
41
|
def _initialize_llm(model: str) -> BaseChatModel:
|
|
49
42
|
"""Initializes and returns the LangChain chat model based on the model name."""
|
|
50
43
|
google_api_key_str = _get_google_api_key()
|
|
51
44
|
|
|
52
45
|
try:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
# Pass raw string and ignore Mypy SecretStr complaint
|
|
68
|
-
# Also ignore missing timeout argument if it's optional
|
|
69
|
-
return ChatAnthropic(model_name=model, api_key=api_key, temperature=0.7)
|
|
70
|
-
elif model.startswith("gemini-"):
|
|
71
|
-
if ChatGoogleGenerativeAI is None:
|
|
72
|
-
raise click.ClickException(
|
|
73
|
-
"Error: 'langchain-google-genai' is not installed. "
|
|
74
|
-
"Run 'pip install commitai[test]' or "
|
|
75
|
-
"'pip install langchain-google-genai'"
|
|
76
|
-
)
|
|
77
|
-
if not google_api_key_str:
|
|
78
|
-
raise click.ClickException(
|
|
79
|
-
"Error: Google API Key not found. Set GOOGLE_API_KEY, "
|
|
80
|
-
"GEMINI_API_KEY, or GOOGLE_GENERATIVE_AI_API_KEY."
|
|
81
|
-
)
|
|
82
|
-
# Pass raw string and ignore Mypy SecretStr complaint
|
|
83
|
-
# Also ignore missing optional arguments
|
|
84
|
-
return ChatGoogleGenerativeAI(
|
|
85
|
-
model=model,
|
|
86
|
-
google_api_key=google_api_key_str,
|
|
87
|
-
temperature=0.7,
|
|
88
|
-
convert_system_message_to_human=True,
|
|
46
|
+
# Enforce Gemini-Only Policy
|
|
47
|
+
# Enforce Strict Gemini-3 Policy
|
|
48
|
+
allowed_models = ["gemini-3-flash-preview", "gemini-3-pro-preview"]
|
|
49
|
+
if model not in allowed_models:
|
|
50
|
+
raise click.ClickException(
|
|
51
|
+
f"🚫 Unsupported model: {model}. "
|
|
52
|
+
f"Only Google Gemini 3 models are allowed: {', '.join(allowed_models)}"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if ChatGoogleGenerativeAI is None:
|
|
56
|
+
raise click.ClickException(
|
|
57
|
+
"Error: 'langchain-google-genai' is not installed. "
|
|
58
|
+
"Run 'pip install commitai[test]' or "
|
|
59
|
+
"'pip install langchain-google-genai'"
|
|
89
60
|
)
|
|
90
|
-
|
|
91
|
-
raise click.ClickException(
|
|
61
|
+
if not google_api_key_str:
|
|
62
|
+
raise click.ClickException(
|
|
63
|
+
"Error: Google API Key not found. Set GOOGLE_API_KEY, "
|
|
64
|
+
"GEMINI_API_KEY, or GOOGLE_GENERATIVE_AI_API_KEY."
|
|
65
|
+
)
|
|
66
|
+
return ChatGoogleGenerativeAI(
|
|
67
|
+
model=model,
|
|
68
|
+
google_api_key=google_api_key_str,
|
|
69
|
+
)
|
|
70
|
+
|
|
92
71
|
except Exception as e:
|
|
93
72
|
raise click.ClickException(f"Error initializing AI model: {e}") from e
|
|
94
73
|
|
|
95
74
|
|
|
96
|
-
# Helper function to prepare context (diff, repo, branch)
|
|
97
75
|
def _prepare_context() -> str:
|
|
98
|
-
"""
|
|
99
|
-
Gets the repository context (name, branch, diff).
|
|
100
|
-
|
|
101
|
-
Returns:
|
|
102
|
-
str: The formatted diff string.
|
|
103
|
-
Raises:
|
|
104
|
-
click.ClickException: If no staged changes are found.
|
|
105
|
-
"""
|
|
106
76
|
diff = get_staged_changes_diff()
|
|
107
77
|
if not diff:
|
|
108
78
|
raise click.ClickException("⚠️ Warning: No staged changes found. Exiting.")
|
|
109
79
|
|
|
110
80
|
repo_name = get_repository_name()
|
|
111
81
|
branch_name = get_current_branch_name()
|
|
82
|
+
# Return just the diff for the chain, or context?
|
|
83
|
+
# The chain prompt expects 'diff'.
|
|
84
|
+
# Current helper was returning "Repo/Branch\n\nDiff".
|
|
85
|
+
# Let's keep it to maximize context for the chain.
|
|
112
86
|
return f"{repo_name}/{branch_name}\n\n{diff}"
|
|
113
87
|
|
|
114
88
|
|
|
115
|
-
# Helper function to build the final prompt
|
|
116
|
-
def _build_prompt(
|
|
117
|
-
explanation: str, formatted_diff: str, template: Optional[str]
|
|
118
|
-
) -> str:
|
|
119
|
-
"""Builds the complete prompt for the AI model."""
|
|
120
|
-
system_message = default_system_message
|
|
121
|
-
if template:
|
|
122
|
-
system_message += adding_template
|
|
123
|
-
system_message += template
|
|
124
|
-
|
|
125
|
-
if explanation:
|
|
126
|
-
diff_message = build_user_message(explanation, formatted_diff)
|
|
127
|
-
else:
|
|
128
|
-
diff_message = formatted_diff
|
|
129
|
-
|
|
130
|
-
return f"{system_message}\n\n{diff_message}"
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
# Helper function to handle commit message editing and creation
|
|
134
89
|
def _handle_commit(commit_message: str, commit_flag: bool) -> None:
|
|
135
|
-
"""
|
|
136
|
-
Writes message, optionally opens editor, and creates the commit.
|
|
137
|
-
|
|
138
|
-
Raises:
|
|
139
|
-
click.ClickException: On file I/O errors or if the commit is aborted.
|
|
140
|
-
"""
|
|
141
90
|
repo_path = get_repository_name()
|
|
142
91
|
git_dir = os.path.join(repo_path, ".git")
|
|
143
92
|
try:
|
|
@@ -153,19 +102,33 @@ def _handle_commit(commit_message: str, commit_flag: bool) -> None:
|
|
|
153
102
|
except IOError as e:
|
|
154
103
|
raise click.ClickException(f"Error writing commit message file: {e}") from e
|
|
155
104
|
|
|
105
|
+
final_commit_message = commit_message
|
|
156
106
|
final_commit_message = commit_message
|
|
157
107
|
if not commit_flag:
|
|
108
|
+
click.secho(
|
|
109
|
+
f"\n📝 Generated Commit Message:\n{'-'*40}\n{commit_message}\n{'-'*40}\n",
|
|
110
|
+
fg="green",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Interactive loop for Enter-Enter flow
|
|
158
114
|
try:
|
|
159
|
-
|
|
160
|
-
with
|
|
161
|
-
final_commit_message
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
115
|
+
# Default to Yes (Enter)
|
|
116
|
+
if click.confirm("🚀 Commit with this message?", default=True):
|
|
117
|
+
pass # final_commit_message is already set
|
|
118
|
+
else:
|
|
119
|
+
if click.confirm("✏️ Edit message?", default=True):
|
|
120
|
+
try:
|
|
121
|
+
click.edit(filename=commit_msg_path)
|
|
122
|
+
with open(commit_msg_path, "r") as f:
|
|
123
|
+
final_commit_message = f.read().strip()
|
|
124
|
+
except click.UsageError as e:
|
|
125
|
+
click.secho(f"Could not open editor: {e}", fg="yellow")
|
|
126
|
+
else:
|
|
127
|
+
raise click.ClickException("Aborted by user.")
|
|
128
|
+
except click.Abort:
|
|
129
|
+
raise click.ClickException("Aborted by user.") from None
|
|
130
|
+
except Exception as e:
|
|
131
|
+
raise click.ClickException(f"Error handling user input: {e}") from e
|
|
169
132
|
|
|
170
133
|
if not final_commit_message:
|
|
171
134
|
raise click.ClickException("Aborting commit due to empty commit message.")
|
|
@@ -180,7 +143,6 @@ def _handle_commit(commit_message: str, commit_flag: bool) -> None:
|
|
|
180
143
|
|
|
181
144
|
@click.group(context_settings={"help_option_names": ["-h", "--help"]})
|
|
182
145
|
def cli() -> None:
|
|
183
|
-
"""CommitAi CLI group."""
|
|
184
146
|
pass
|
|
185
147
|
|
|
186
148
|
|
|
@@ -192,6 +154,11 @@ def cli() -> None:
|
|
|
192
154
|
is_flag=True,
|
|
193
155
|
help="Commit the changes with the generated message",
|
|
194
156
|
)
|
|
157
|
+
@click.option(
|
|
158
|
+
"--review/--no-review",
|
|
159
|
+
default=True,
|
|
160
|
+
help="AI review the diff before generating the commit message (default: enabled)",
|
|
161
|
+
)
|
|
195
162
|
@click.option(
|
|
196
163
|
"--template",
|
|
197
164
|
"-t",
|
|
@@ -209,24 +176,42 @@ def cli() -> None:
|
|
|
209
176
|
@click.option(
|
|
210
177
|
"--model",
|
|
211
178
|
"-m",
|
|
212
|
-
default="gemini-
|
|
179
|
+
default="gemini-3-flash-preview",
|
|
213
180
|
help=(
|
|
214
|
-
"Set the engine model (
|
|
215
|
-
"
|
|
216
|
-
"(
|
|
217
|
-
"GOOGLE_API_KEY
|
|
181
|
+
"Set the engine model (default: gemini-3-flash-preview). "
|
|
182
|
+
"Only Google Gemini 3 models are supported "
|
|
183
|
+
"('gemini-3-flash-preview', 'gemini-3-pro-preview'). "
|
|
184
|
+
"Ensure GOOGLE_API_KEY is set."
|
|
218
185
|
),
|
|
219
186
|
)
|
|
187
|
+
@click.option(
|
|
188
|
+
"--deep",
|
|
189
|
+
"-d",
|
|
190
|
+
is_flag=True,
|
|
191
|
+
help="Use the deeper reasoning model (gemini-3-pro-preview).",
|
|
192
|
+
)
|
|
220
193
|
def generate_message(
|
|
221
194
|
description: Tuple[str, ...],
|
|
222
195
|
commit: bool,
|
|
196
|
+
review: bool,
|
|
223
197
|
template: Optional[str],
|
|
224
198
|
add: bool,
|
|
225
199
|
model: str,
|
|
200
|
+
deep: bool,
|
|
226
201
|
) -> None:
|
|
227
|
-
"""Generates a commit message based on staged changes and description."""
|
|
228
202
|
explanation = " ".join(description)
|
|
229
203
|
|
|
204
|
+
# Handle Model Selection Logic
|
|
205
|
+
# 1. Default is gemini-3-flash-preview
|
|
206
|
+
# 2. If --deep is passed, upgrade to gemini-3-pro-preview
|
|
207
|
+
# (unless -m is explicitly distinct)
|
|
208
|
+
if deep:
|
|
209
|
+
# Upgrade to Pro model if deep flag is set
|
|
210
|
+
# We override the model unless the user explicitly chose a different one
|
|
211
|
+
# (For simplicity here, we assume --deep implies pro)
|
|
212
|
+
if model == "gemini-3-flash-preview":
|
|
213
|
+
model = "gemini-3-pro-preview"
|
|
214
|
+
|
|
230
215
|
llm = _initialize_llm(model)
|
|
231
216
|
|
|
232
217
|
if add:
|
|
@@ -242,26 +227,63 @@ def generate_message(
|
|
|
242
227
|
|
|
243
228
|
formatted_diff = _prepare_context()
|
|
244
229
|
|
|
230
|
+
# Initialize Agent Pipeline
|
|
231
|
+
agent_pipeline = create_commit_agent(llm)
|
|
232
|
+
|
|
233
|
+
# Optional pre-generation review
|
|
234
|
+
if review:
|
|
235
|
+
click.secho(
|
|
236
|
+
"\n\n🔎 Reviewing the staged changes before "
|
|
237
|
+
"generating a commit message...\n",
|
|
238
|
+
fg="blue",
|
|
239
|
+
bold=True,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# Only prompt for confirmation when running in an interactive TTY
|
|
243
|
+
try:
|
|
244
|
+
is_interactive = sys.stdin.isatty()
|
|
245
|
+
except Exception:
|
|
246
|
+
is_interactive = False
|
|
247
|
+
if is_interactive:
|
|
248
|
+
if not click.confirm(
|
|
249
|
+
"Proceed with generating the commit message?", default=True
|
|
250
|
+
):
|
|
251
|
+
raise click.ClickException("Aborted by user after review.")
|
|
252
|
+
|
|
245
253
|
if template:
|
|
246
254
|
click.secho(
|
|
247
255
|
"⚠️ Warning: The --template/-t option is deprecated. Use environment "
|
|
248
256
|
"variable TEMPLATE_COMMIT or `commitai-create-template` command.",
|
|
249
257
|
fg="yellow",
|
|
250
258
|
)
|
|
251
|
-
final_template = template or get_commit_template()
|
|
252
259
|
|
|
253
|
-
|
|
260
|
+
# Check for template from env or file if not provided via CLI
|
|
261
|
+
# (though CLI overrides or is deprecated)
|
|
262
|
+
# The agent/chain prompt Logic usually handles 'template' variable
|
|
263
|
+
# if passed in input.
|
|
264
|
+
# We need to fetch the template content if it exists to pass to agent.
|
|
265
|
+
|
|
266
|
+
final_template_content = template
|
|
267
|
+
if not final_template_content:
|
|
268
|
+
# Check env var or local file
|
|
269
|
+
final_template_content = os.getenv("TEMPLATE_COMMIT") or get_commit_template()
|
|
254
270
|
|
|
255
271
|
click.clear()
|
|
256
272
|
click.secho(
|
|
257
|
-
"\n\n🧠 Analyzing
|
|
273
|
+
"\n\n🧠 internal-monologue: Analyzing changes, "
|
|
274
|
+
"checking for sensitive data, and summarizing...\n\n",
|
|
258
275
|
fg="blue",
|
|
259
276
|
bold=True,
|
|
260
277
|
)
|
|
261
278
|
try:
|
|
262
279
|
assert llm is not None
|
|
263
|
-
|
|
264
|
-
|
|
280
|
+
# Invoke the Agent Pipeline
|
|
281
|
+
inputs = {"diff": formatted_diff, "explanation": explanation}
|
|
282
|
+
if final_template_content:
|
|
283
|
+
inputs["template"] = final_template_content
|
|
284
|
+
|
|
285
|
+
commit_message = agent_pipeline.invoke(inputs)
|
|
286
|
+
|
|
265
287
|
if not isinstance(commit_message, str):
|
|
266
288
|
commit_message = str(commit_message)
|
|
267
289
|
|
|
@@ -303,23 +325,42 @@ def create_template_command(template_content: Tuple[str, ...]) -> None:
|
|
|
303
325
|
is_flag=True,
|
|
304
326
|
help="Commit the changes with the generated message",
|
|
305
327
|
)
|
|
328
|
+
@click.option(
|
|
329
|
+
"--review/--no-review",
|
|
330
|
+
default=True,
|
|
331
|
+
help="AI review the diff before generating the commit message (default: enabled)",
|
|
332
|
+
)
|
|
306
333
|
@click.option(
|
|
307
334
|
"--model",
|
|
308
335
|
"-m",
|
|
309
|
-
default="gemini-
|
|
336
|
+
default="gemini-3-flash-preview",
|
|
310
337
|
help="Set the engine model to be used.",
|
|
311
338
|
)
|
|
339
|
+
@click.option(
|
|
340
|
+
"--deep",
|
|
341
|
+
"-d",
|
|
342
|
+
is_flag=True,
|
|
343
|
+
help="Use the deeper reasoning model (gemini-3-pro-preview).",
|
|
344
|
+
)
|
|
312
345
|
@click.pass_context
|
|
313
346
|
def commitai_alias(
|
|
314
347
|
ctx: click.Context,
|
|
315
348
|
description: Tuple[str, ...],
|
|
316
349
|
add: bool,
|
|
317
350
|
commit: bool,
|
|
351
|
+
review: bool,
|
|
318
352
|
model: str,
|
|
353
|
+
deep: bool,
|
|
319
354
|
) -> None:
|
|
320
355
|
"""Alias for the 'generate' command."""
|
|
321
356
|
ctx.forward(
|
|
322
|
-
generate_message,
|
|
357
|
+
generate_message,
|
|
358
|
+
description=description,
|
|
359
|
+
add=add,
|
|
360
|
+
commit=commit,
|
|
361
|
+
review=review,
|
|
362
|
+
model=model,
|
|
363
|
+
deep=deep,
|
|
323
364
|
)
|
|
324
365
|
|
|
325
366
|
|
commitai/template.py
CHANGED
|
@@ -23,3 +23,24 @@ adding_template = " The message should follow this template: "
|
|
|
23
23
|
|
|
24
24
|
def build_user_message(explanation, diff):
|
|
25
25
|
return f"Here is a high-level explanation of the commit: {explanation}\n\n{diff}"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def build_review_prompt(explanation: str, formatted_diff: str) -> str:
|
|
29
|
+
"""Builds a review prompt asking the AI to review the diff before message generation.
|
|
30
|
+
|
|
31
|
+
The review should highlight:
|
|
32
|
+
- correctness concerns, risky changes, missing tests or docs
|
|
33
|
+
- obvious refactors or style violations
|
|
34
|
+
- a short bullet summary of the changes
|
|
35
|
+
Keep it concise.
|
|
36
|
+
"""
|
|
37
|
+
review_system = (
|
|
38
|
+
"You are a senior code reviewer. You will receive a repository path/branch and a git diff. "
|
|
39
|
+
"Provide a brief review focusing on potential issues, risks, and improvement suggestions. "
|
|
40
|
+
"Then provide a very short summary of changes. Keep output as plain text, no markdown code fences."
|
|
41
|
+
)
|
|
42
|
+
if explanation:
|
|
43
|
+
intro = f"High-level explanation: {explanation}\n\n{formatted_diff}"
|
|
44
|
+
else:
|
|
45
|
+
intro = formatted_diff
|
|
46
|
+
return f"{review_system}\n\n{intro}"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: commitai
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Commitai helps you generate git commit messages using AI
|
|
5
5
|
Project-URL: Bug Tracker, https://github.com/lguibr/commitai/issues
|
|
6
6
|
Project-URL: Documentation, https://github.com/lguibr/commitai/blob/main/README.md
|
|
@@ -34,23 +34,20 @@ Classifier: Intended Audience :: Developers
|
|
|
34
34
|
Classifier: License :: OSI Approved :: MIT License
|
|
35
35
|
Classifier: Operating System :: OS Independent
|
|
36
36
|
Classifier: Programming Language :: Python :: 3
|
|
37
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
38
37
|
Classifier: Programming Language :: Python :: 3.10
|
|
39
38
|
Classifier: Programming Language :: Python :: 3.11
|
|
40
39
|
Classifier: Programming Language :: Python :: 3.12
|
|
41
40
|
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
42
41
|
Classifier: Topic :: Utilities
|
|
43
|
-
Requires-Python: >=3.
|
|
42
|
+
Requires-Python: >=3.10
|
|
44
43
|
Requires-Dist: click<9.0,>=8.0
|
|
45
|
-
Requires-Dist: langchain-
|
|
46
|
-
Requires-Dist: langchain-
|
|
47
|
-
Requires-Dist: langchain-
|
|
48
|
-
Requires-Dist: langchain
|
|
49
|
-
Requires-Dist: langchain-openai<0.3.0,>=0.1.0
|
|
50
|
-
Requires-Dist: langchain<0.3.0,>=0.1.0
|
|
44
|
+
Requires-Dist: langchain-community<=0.3.23,>=0.0.20
|
|
45
|
+
Requires-Dist: langchain-core<=0.3.58,>=0.1.0
|
|
46
|
+
Requires-Dist: langchain-google-genai~=2.1.4
|
|
47
|
+
Requires-Dist: langchain<=0.3.25,>=0.1.0
|
|
51
48
|
Requires-Dist: pydantic<3.0,>=2.0
|
|
52
49
|
Provides-Extra: test
|
|
53
|
-
Requires-Dist: langchain-google-genai~=
|
|
50
|
+
Requires-Dist: langchain-google-genai~=2.1.4; extra == 'test'
|
|
54
51
|
Requires-Dist: mypy>=1.9.0; extra == 'test'
|
|
55
52
|
Requires-Dist: pytest-cov>=3.0; extra == 'test'
|
|
56
53
|
Requires-Dist: pytest>=7.0; extra == 'test'
|
|
@@ -67,9 +64,13 @@ Description-Content-Type: text/markdown
|
|
|
67
64
|
[](https://github.com/lguibr/CommitAi/blob/main/LICENSE)
|
|
68
65
|
[](https://github.com/astral-sh/ruff)
|
|
69
66
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
|
|
68
|
+
<img
|
|
69
|
+
src="https://raw.githubusercontent.com/lguibr/commitai/main/bitmap.png"
|
|
70
|
+
alt="screenshot"
|
|
71
|
+
width="400"
|
|
72
|
+
/>
|
|
73
|
+
|
|
73
74
|
|
|
74
75
|
**Tired of writing Git commit messages? Let AI do the heavy lifting!**
|
|
75
76
|
|
|
@@ -97,12 +98,12 @@ Simply stage your files and run `commitai`. It analyzes the diff, optionally tak
|
|
|
97
98
|
|
|
98
99
|
## Features
|
|
99
100
|
|
|
100
|
-
* 🧠 **Intelligent Commit Generation**: Analyzes staged code differences (`git diff --staged`) using state-of-the-art AI models (GPT, Claude
|
|
101
|
+
* 🧠 **Intelligent Commit Generation**: Analyzes staged code differences (`git diff --staged`) using state-of-the-art AI models (Gemini, GPT, Claude) to create meaningful commit messages.
|
|
101
102
|
* 📄 **Conventional Commits**: Automatically formats messages according to the Conventional Commits specification (e.g., `feat(auth): add JWT authentication`). This improves readability and enables automated changelog generation.
|
|
102
103
|
* 📝 **Optional Explanations**: Provide a high-level description of your changes as input to guide the AI, or let it infer the context solely from the code diff.
|
|
103
104
|
* ✅ **Pre-commit Hook Integration**: Automatically runs your existing native Git pre-commit hook (`.git/hooks/pre-commit`) before generating the message, ensuring code quality and style checks pass.
|
|
104
105
|
* 🔧 **Customizable Prompts via Templates**: Add custom instructions or context to the AI prompt using global environment variables or repository-specific template files.
|
|
105
|
-
* 🤖 **Multiple AI Provider Support**: Choose your preferred AI model from OpenAI, Anthropic, or
|
|
106
|
+
* 🤖 **Multiple AI Provider Support**: Choose your preferred AI model from OpenAI, Anthropic, Google or local AI models with Ollama.
|
|
106
107
|
* ⚙️ **Flexible Workflow**:
|
|
107
108
|
* Stages all changes automatically (`-a` flag).
|
|
108
109
|
* Reviews message in your default Git editor (default behavior).
|
|
@@ -152,7 +153,15 @@ CommitAi requires API keys for the AI provider you intend to use. Set these as e
|
|
|
152
153
|
export GOOGLE_API_KEY="your_google_api_key_here"
|
|
153
154
|
```
|
|
154
155
|
|
|
155
|
-
You only need to set the key for the provider corresponding to the model you select (or the default, Gemini).
|
|
156
|
+
You only need to set the key for the provider corresponding to the model you select (or the default, Gemini 3 Flash with Google).
|
|
157
|
+
|
|
158
|
+
### Ollama
|
|
159
|
+
|
|
160
|
+
CommitAi can also work with Ollama models:
|
|
161
|
+
```bash
|
|
162
|
+
export OLLAMA_HOST="your_ollama_base_url"
|
|
163
|
+
```
|
|
164
|
+
|
|
156
165
|
|
|
157
166
|
### Commit Templates (Optional)
|
|
158
167
|
|
|
@@ -211,14 +220,20 @@ The `commitai` command (which is an alias for `commitai generate`) accepts the f
|
|
|
211
220
|
* Example: `commitai -c "Fix typo in documentation"` (for minor changes)
|
|
212
221
|
* Can be combined with `-a`: `commitai -a -c "Quick fix and commit all"`
|
|
213
222
|
|
|
223
|
+
* `--review` / `--no-review`:
|
|
224
|
+
* Toggle a preliminary AI review of the staged diff before generating the commit message. Default is `--review` (enabled).
|
|
225
|
+
* When enabled, CommitAi prints a brief review and asks if you want to proceed.
|
|
226
|
+
* Example: `commitai --no-review` to skip the review step.
|
|
227
|
+
|
|
214
228
|
* `-m <model_name>`, `--model <model_name>`:
|
|
215
229
|
* Specifies which AI model to use.
|
|
216
|
-
*
|
|
230
|
+
* Specifies which AI model to use.
|
|
231
|
+
* Defaults to `gemini-3-flash-preview`.
|
|
217
232
|
* Ensure the corresponding API key environment variable is set.
|
|
218
233
|
* Examples:
|
|
234
|
+
* `commitai -m gemini-3-pro-preview "Use Google's Gemini 3 Pro"`
|
|
219
235
|
* `commitai -m gpt-4 "Use OpenAI's GPT-4"`
|
|
220
|
-
* `commitai -m claude-3-opus
|
|
221
|
-
* `commitai -m gemini-2.5-flash-preview-04-17 "Use Google's Gemini 1.5 Flash"`
|
|
236
|
+
* `commitai -m claude-3-opus "Use Anthropic's Claude 3 Opus"`
|
|
222
237
|
|
|
223
238
|
### Creating Repository Templates
|
|
224
239
|
|
|
@@ -290,7 +305,7 @@ Contributions are highly welcome! Please follow these steps:
|
|
|
290
305
|
9. Run checks locally before committing:
|
|
291
306
|
* Format code: `ruff format .`
|
|
292
307
|
* Lint code: `ruff check .`
|
|
293
|
-
* Run type checks: `mypy commitai
|
|
308
|
+
* Run type checks: `mypy commitai tests`
|
|
294
309
|
* Run tests: `pytest`
|
|
295
310
|
10. Commit your changes (you can use `commitai`!).
|
|
296
311
|
11. Push your branch to your fork: `git push origin my-feature-branch`
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
commitai/__init__.py,sha256=__b9FU1YSgHa5pyZG0hsgo2bQrNVAFPUMH2o-pBKzzE,388
|
|
2
|
+
commitai/agent.py,sha256=fHh8d_ND9s0gqPaWqbHwmW9Kkt9dS5M6sRnGmhHb87Y,7037
|
|
3
|
+
commitai/chains.py,sha256=i5tQL9Qg-kP4yVCiQavhOLS9ljh72bxYALQ27Hoqivc,4612
|
|
4
|
+
commitai/cli.py,sha256=9-_y6STLCz-E7kHH6bX7CaV3aIxt8cdp-oa3IqbKDHY,11638
|
|
5
|
+
commitai/git.py,sha256=XWAloZWQuLrFHUyfh3SkOgLsL4kfKRtgj3fzuJjcL2A,1649
|
|
6
|
+
commitai/template.py,sha256=PAS3BUjj6fcdsvSheopU_0xGCLPMj-vvFeZVCNRf0VM,2274
|
|
7
|
+
commitai-2.0.0.dist-info/METADATA,sha256=Vjrry5eOeDtht4XyJuloAzQJXOBJf6WEVnMEx0R9BbE,13868
|
|
8
|
+
commitai-2.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
9
|
+
commitai-2.0.0.dist-info/entry_points.txt,sha256=qzWJQdPoR38mjQgRPRCB3tA7Kojtj3WrozlFWR4KhLY,128
|
|
10
|
+
commitai-2.0.0.dist-info/licenses/LICENSE,sha256=wVkmSz0UMpGw0xYxk4AmkPLd_tVFcuszTdNIoq02tJA,1087
|
|
11
|
+
commitai-2.0.0.dist-info/RECORD,,
|
commitai-1.0.4.dist-info/RECORD
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
commitai/__init__.py,sha256=Z5hB8NLKlfi7oPDRcDSQmtPSo_arLjoa6jwjFt3Pb9o,388
|
|
2
|
-
commitai/cli.py,sha256=-uM5xYOvQ8NUDQM4fJ1Ktk3st9d5QxtT55rbTFP7e_o,10620
|
|
3
|
-
commitai/git.py,sha256=XWAloZWQuLrFHUyfh3SkOgLsL4kfKRtgj3fzuJjcL2A,1649
|
|
4
|
-
commitai/template.py,sha256=q4AO64hKhJP2y9DCc5-ePFoRZfOQBkbkB6vt8CnoMh8,1379
|
|
5
|
-
commitai-1.0.4.dist-info/METADATA,sha256=iOijXC_ZayBVdArer0z-htp9mso3iqhBHwpRQyXSB0U,13487
|
|
6
|
-
commitai-1.0.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
-
commitai-1.0.4.dist-info/entry_points.txt,sha256=qzWJQdPoR38mjQgRPRCB3tA7Kojtj3WrozlFWR4KhLY,128
|
|
8
|
-
commitai-1.0.4.dist-info/licenses/LICENSE,sha256=wVkmSz0UMpGw0xYxk4AmkPLd_tVFcuszTdNIoq02tJA,1087
|
|
9
|
-
commitai-1.0.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|