mlx-code 0.0.13__tar.gz → 0.0.14__tar.gz
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.
- {mlx_code-0.0.13 → mlx_code-0.0.14}/PKG-INFO +1 -1
- {mlx_code-0.0.13 → mlx_code-0.0.14}/mlx_code/gits.py +50 -1
- {mlx_code-0.0.13 → mlx_code-0.0.14}/mlx_code/repl.py +38 -4
- {mlx_code-0.0.13 → mlx_code-0.0.14}/mlx_code.egg-info/PKG-INFO +1 -1
- {mlx_code-0.0.13 → mlx_code-0.0.14}/setup.py +1 -1
- {mlx_code-0.0.13 → mlx_code-0.0.14}/LICENSE +0 -0
- {mlx_code-0.0.13 → mlx_code-0.0.14}/README.md +0 -0
- {mlx_code-0.0.13 → mlx_code-0.0.14}/mlx_code/__init__.py +0 -0
- {mlx_code-0.0.13 → mlx_code-0.0.14}/mlx_code/apis.py +0 -0
- {mlx_code-0.0.13 → mlx_code-0.0.14}/mlx_code/lsp_tool.py +0 -0
- {mlx_code-0.0.13 → mlx_code-0.0.14}/mlx_code/main.py +0 -0
- {mlx_code-0.0.13 → mlx_code-0.0.14}/mlx_code/mcb.py +0 -0
- {mlx_code-0.0.13 → mlx_code-0.0.14}/mlx_code/mcb_tool.py +0 -0
- {mlx_code-0.0.13 → mlx_code-0.0.14}/mlx_code/stream_log.py +0 -0
- {mlx_code-0.0.13 → mlx_code-0.0.14}/mlx_code/tools.py +0 -0
- {mlx_code-0.0.13 → mlx_code-0.0.14}/mlx_code/util.py +0 -0
- {mlx_code-0.0.13 → mlx_code-0.0.14}/mlx_code/view_git.py +0 -0
- {mlx_code-0.0.13 → mlx_code-0.0.14}/mlx_code/view_log.py +0 -0
- {mlx_code-0.0.13 → mlx_code-0.0.14}/mlx_code.egg-info/SOURCES.txt +0 -0
- {mlx_code-0.0.13 → mlx_code-0.0.14}/mlx_code.egg-info/dependency_links.txt +0 -0
- {mlx_code-0.0.13 → mlx_code-0.0.14}/mlx_code.egg-info/entry_points.txt +0 -0
- {mlx_code-0.0.13 → mlx_code-0.0.14}/mlx_code.egg-info/requires.txt +0 -0
- {mlx_code-0.0.13 → mlx_code-0.0.14}/mlx_code.egg-info/top_level.txt +0 -0
- {mlx_code-0.0.13 → mlx_code-0.0.14}/setup.cfg +0 -0
- {mlx_code-0.0.13 → mlx_code-0.0.14}/tests/__init__.py +0 -0
- {mlx_code-0.0.13 → mlx_code-0.0.14}/tests/test.py +0 -0
|
@@ -177,4 +177,53 @@ def current_point(worktree_dir: str) -> LedgerPoint:
|
|
|
177
177
|
worktree_dir = os.path.abspath(worktree_dir)
|
|
178
178
|
branch = _git(worktree_dir, 'symbolic-ref', '--short', 'HEAD', check=False) or 'DETACHED'
|
|
179
179
|
sha = _git(worktree_dir, 'rev-parse', 'HEAD', check=False)
|
|
180
|
-
return LedgerPoint(branch=branch, commit=sha, worktree=worktree_dir)
|
|
180
|
+
return LedgerPoint(branch=branch, commit=sha, worktree=worktree_dir)
|
|
181
|
+
|
|
182
|
+
def get_commit_history_with_stats(worktree: str, limit: int=50) -> list[dict]:
|
|
183
|
+
try:
|
|
184
|
+
cmd = ['git', 'log', '--name-status', '--format=COMMIT:%H%nSHORT:%h%nREFS:%d%nSUBJECT:%s%nBODY_START%n%b%nBODY_END', f'-n{limit}']
|
|
185
|
+
result = subprocess.run(cmd, cwd=worktree, capture_output=True, text=True, check=True)
|
|
186
|
+
output = result.stdout.strip()
|
|
187
|
+
commits = []
|
|
188
|
+
current_commit = None
|
|
189
|
+
in_body = False
|
|
190
|
+
body_lines = []
|
|
191
|
+
for line in output.split('\n'):
|
|
192
|
+
if line.startswith('COMMIT:'):
|
|
193
|
+
if current_commit:
|
|
194
|
+
body_text = '\n'.join(body_lines)
|
|
195
|
+
current_commit['user_turns'] = body_text.count('"role": "user"')
|
|
196
|
+
commits.append(current_commit)
|
|
197
|
+
current_commit = {'sha': line[7:], 'short_sha': '', 'refs': '', 'subject': '', 'files': []}
|
|
198
|
+
in_body = False
|
|
199
|
+
body_lines = []
|
|
200
|
+
elif line.startswith('SHORT:'):
|
|
201
|
+
if current_commit:
|
|
202
|
+
current_commit['short_sha'] = line[6:]
|
|
203
|
+
elif line.startswith('REFS:'):
|
|
204
|
+
if current_commit:
|
|
205
|
+
current_commit['refs'] = line[5:].strip()
|
|
206
|
+
elif line.startswith('SUBJECT:'):
|
|
207
|
+
if current_commit:
|
|
208
|
+
current_commit['subject'] = line[8:]
|
|
209
|
+
elif line == 'BODY_START':
|
|
210
|
+
in_body = True
|
|
211
|
+
elif line == 'BODY_END':
|
|
212
|
+
in_body = False
|
|
213
|
+
elif current_commit:
|
|
214
|
+
if in_body:
|
|
215
|
+
body_lines.append(line)
|
|
216
|
+
elif line.strip():
|
|
217
|
+
parts = line.split('\t', 1)
|
|
218
|
+
if len(parts) == 2:
|
|
219
|
+
current_commit['files'].append(f'{parts[0]} {parts[1]}')
|
|
220
|
+
else:
|
|
221
|
+
current_commit['files'].append(line)
|
|
222
|
+
if current_commit:
|
|
223
|
+
body_text = '\n'.join(body_lines)
|
|
224
|
+
current_commit['user_turns'] = body_text.count('"role": "user"')
|
|
225
|
+
commits.append(current_commit)
|
|
226
|
+
return commits
|
|
227
|
+
except Exception as e:
|
|
228
|
+
logger.warning(f'get_commit_history_with_stats failed: {e}')
|
|
229
|
+
return []
|
|
@@ -12,7 +12,7 @@ import time
|
|
|
12
12
|
import logging
|
|
13
13
|
import urllib.parse
|
|
14
14
|
from typing import Any, Callable, Literal
|
|
15
|
-
from .gits import create_worktree, commit_worktree, resume_worktree, cleanup_worktree, git_new_branch, git_switch_branch, GitError
|
|
15
|
+
from .gits import create_worktree, commit_worktree, resume_worktree, cleanup_worktree, git_new_branch, git_switch_branch, GitError, get_commit_history_with_stats
|
|
16
16
|
from .tools import Tool, validate_tool_call, DEFAULT_TOOLS
|
|
17
17
|
from .apis import resolve_api
|
|
18
18
|
logger = logging.getLogger(__name__)
|
|
@@ -561,14 +561,48 @@ class ReplApp(App[None]):
|
|
|
561
561
|
tab.show_command(text, 'No prompts yet.')
|
|
562
562
|
else:
|
|
563
563
|
lines = []
|
|
564
|
+
gwt = tab.agent.ctx.get('gwt')
|
|
565
|
+
commit_stats = []
|
|
566
|
+
if gwt and getattr(gwt, 'worktree', None):
|
|
567
|
+
try:
|
|
568
|
+
commit_stats = get_commit_history_with_stats(gwt.worktree, limit=len(user_msgs) + 5)
|
|
569
|
+
except Exception as e:
|
|
570
|
+
logger.warning(f'Failed to get commit stats: {e}')
|
|
571
|
+
turn_commits = {}
|
|
572
|
+
for c in commit_stats:
|
|
573
|
+
turn = c.get('user_turns', 0)
|
|
574
|
+
if turn > 0:
|
|
575
|
+
if turn not in turn_commits:
|
|
576
|
+
turn_commits[turn] = []
|
|
577
|
+
turn_commits[turn].append(c)
|
|
564
578
|
for i, m in enumerate(user_msgs, 1):
|
|
565
579
|
content = m.get('content', '')
|
|
566
580
|
if isinstance(content, list):
|
|
567
|
-
content = ''.join((b.get('text', '') for b in content if isinstance(b, dict) and b.get('type') == 'text'))
|
|
568
|
-
content = re.sub('\\s+', '
|
|
581
|
+
content = ' '.join((b.get('text', '') for b in content if isinstance(b, dict) and b.get('type') == 'text'))
|
|
582
|
+
content = re.sub('\\s+', ' ', content).strip()
|
|
569
583
|
if len(content) > 100:
|
|
570
584
|
content = content[:100] + '…'
|
|
571
|
-
|
|
585
|
+
line = f'{i}. {content}'
|
|
586
|
+
if i in turn_commits:
|
|
587
|
+
for c in turn_commits[i]:
|
|
588
|
+
ref_str = c.get('refs', '').strip(' ()')
|
|
589
|
+
if ref_str.startswith('HEAD -> '):
|
|
590
|
+
ref_str = ref_str[8:]
|
|
591
|
+
ref_str = ref_str.replace('HEAD, ', '').strip()
|
|
592
|
+
hash_str = f'[{c['short_sha']}]'
|
|
593
|
+
if ref_str:
|
|
594
|
+
hash_str += f' on {ref_str}'
|
|
595
|
+
else:
|
|
596
|
+
hash_str += ' (detached)'
|
|
597
|
+
line += f'\n ↪ Commit {hash_str}'
|
|
598
|
+
seen = set()
|
|
599
|
+
unique_files = [f for f in c['files'] if not (f in seen or seen.add(f))]
|
|
600
|
+
if unique_files:
|
|
601
|
+
file_lines = '\n '.join(unique_files[:8])
|
|
602
|
+
if len(unique_files) > 8:
|
|
603
|
+
file_lines += f'\n ... and {len(unique_files) - 8} more'
|
|
604
|
+
line += f'\n {file_lines}'
|
|
605
|
+
lines.append(line)
|
|
572
606
|
tab.show_command(text, '\n'.join(lines))
|
|
573
607
|
elif cmd == '/errors':
|
|
574
608
|
if not tab.errors:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|