imp-git 0.0.26__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.
imp/commands/fix.py ADDED
@@ -0,0 +1,78 @@
1
+ import json
2
+ import shutil
3
+ import subprocess
4
+
5
+ import typer
6
+
7
+ from imp import ai, console, git, prompts, validate
8
+
9
+
10
+ def fix (
11
+ issue: int = typer.Argument (..., help="GitHub issue number"),
12
+ yes: bool = typer.Option (False, "--yes", "-y", help="Accept suggested branch name"),
13
+ whisper: str = typer.Option ("", "--whisper", "-w", help="Hint to guide the AI"),
14
+ ):
15
+ """Create a branch from a GitHub issue.
16
+
17
+ Fetches the issue title and body from GitHub using the gh CLI, then
18
+ uses AI to generate a branch name. After creation, displays the issue
19
+ context so you can start working immediately.
20
+ """
21
+
22
+ git.require ()
23
+
24
+ if not shutil.which ("gh"):
25
+ console.err ("GitHub CLI (gh) not installed")
26
+ console.hint ("https://cli.github.com")
27
+ raise typer.Exit (1)
28
+
29
+ console.header (f"Fix Issue #{issue}")
30
+
31
+ try:
32
+ result = console.spin (
33
+ "Fetching issue...",
34
+ subprocess.run,
35
+ [ "gh", "issue", "view", str (issue), "--json", "title,body,labels" ],
36
+ capture_output=True,
37
+ text=True,
38
+ check=True,
39
+ )
40
+ data = json.loads (result.stdout)
41
+ except (subprocess.CalledProcessError, json.JSONDecodeError, OSError) as e:
42
+ console.err (f"Could not fetch issue #{issue}: {e}")
43
+ raise typer.Exit (1) from None
44
+
45
+ title = data.get ("title", "")
46
+ body = (data.get ("body", "") or "") [:500]
47
+
48
+ console.label ("Issue")
49
+ console.item (f"#{issue}: {title}")
50
+ console.out.print ()
51
+
52
+ name = ai.fast (prompts.fix (title, body, whisper))
53
+ name = ai.oneline (name)
54
+
55
+ if not validate.branch (name):
56
+ console.err (f"Invalid branch name: {name}")
57
+ raise typer.Exit (1)
58
+
59
+ console.label ("Branch")
60
+ console.item (name)
61
+ console.out.print ()
62
+
63
+ if yes or console.confirm ("Create branch?"):
64
+ git.checkout (name, create=True)
65
+ console.success (f"Switched to {name}")
66
+ console.out.print ()
67
+ console.label ("Context")
68
+ console.divider ()
69
+ console.out.print (title)
70
+ console.out.print ()
71
+ body_preview = "\n".join (body.splitlines () [:10])
72
+ if body_preview:
73
+ console.out.print (body_preview)
74
+ console.divider ()
75
+ console.hint ("make changes, then imp commit")
76
+ else:
77
+ console.muted ("Cancelled")
78
+ raise typer.Exit (0)
imp/commands/help.py ADDED
@@ -0,0 +1,103 @@
1
+ from imp import console
2
+
3
+
4
+ def help ():
5
+ """Show workflow guide and common commands.
6
+
7
+ Prints a quick-reference of all imp commands organized by workflow
8
+ phase (starting, working, syncing, shipping) with common flow
9
+ examples for solo, feature-branch, and hotfix patterns.
10
+ """
11
+
12
+ console.header ("imp workflow")
13
+
14
+ console.out.print ("imp wraps git with AI. You commit locally as you work,")
15
+ console.out.print ("then squash everything into a clean release when ready.")
16
+ console.out.print ()
17
+
18
+ console.divider ()
19
+ console.out.print ()
20
+
21
+ console.out.print ("[bold]Starting a feature[/bold]")
22
+ console.out.print (' imp branch "add auth" [muted]# create branch from description[/muted]')
23
+ console.out.print (" imp branch [muted]# switch between branches[/muted]")
24
+ console.out.print (" imp fix 42 [muted]# or from a GitHub issue[/muted]")
25
+ console.out.print ()
26
+
27
+ console.out.print ("[bold]While working[/bold]")
28
+ console.out.print (" imp review [muted]# AI code review[/muted]")
29
+ console.out.print (" imp commit -a [muted]# stage all + AI commit message[/muted]")
30
+ console.out.print (" imp split [muted]# group changes into logical commits[/muted]")
31
+ console.out.print (" imp amend [muted]# fix last commit[/muted]")
32
+ console.out.print (" imp undo [N] [muted]# undo last N commits[/muted]")
33
+ console.out.print (" imp revert <hash> [muted]# safely undo a pushed commit[/muted]")
34
+ console.out.print ()
35
+
36
+ console.out.print ("[bold]Staying in sync[/bold]")
37
+ console.out.print (" imp sync [muted]# pull, rebase, push[/muted]")
38
+ console.out.print (" imp resolve [muted]# AI merge conflict resolution[/muted]")
39
+ console.out.print (" imp status [muted]# repo overview[/muted]")
40
+ console.out.print (" imp log [muted]# pretty commit graph[/muted]")
41
+ console.out.print ()
42
+
43
+ console.out.print ("[bold]Shipping[/bold]")
44
+ console.out.print (" imp pr [muted]# create pull request[/muted]")
45
+ console.out.print (" imp done [muted]# clean up after PR merge[/muted]")
46
+ console.out.print (" imp clean [muted]# delete merged branches[/muted]")
47
+ console.out.print (" imp release [muted]# squash + changelog + tag + push[/muted]")
48
+ console.out.print (" imp ship [level] [muted]# commit + release, no prompts[/muted]")
49
+ console.out.print ()
50
+
51
+ console.out.print ("[bold]Setup[/bold]")
52
+ console.out.print (" imp config [muted]# configure AI provider and models[/muted]")
53
+ console.out.print (" imp doctor [muted]# verify tools and connection[/muted]")
54
+ console.out.print ()
55
+
56
+ console.divider ()
57
+ console.out.print ()
58
+
59
+ console.out.print ("[bold]Commit format[/bold] [muted](Conventional Commits)[/muted]")
60
+ console.out.print ()
61
+ console.out.print (" [muted]type: message[/muted]")
62
+ console.out.print (" [muted]type(scope): message[/muted]")
63
+ console.out.print (" [muted]type!: message breaking change[/muted]")
64
+ console.out.print ()
65
+ console.out.print (" feat [muted]new feature[/muted] build [muted]build system, deps[/muted]")
66
+ console.out.print (" fix [muted]bug fix[/muted] chore [muted]maintenance, config[/muted]")
67
+ console.out.print (" refactor [muted]restructure code[/muted] docs [muted]documentation[/muted]")
68
+ console.out.print (" test [muted]add/update tests[/muted] style [muted]formatting, whitespace[/muted]")
69
+ console.out.print (" perf [muted]performance[/muted] ci [muted]CI/CD pipelines[/muted]")
70
+ console.out.print ()
71
+ console.out.print (" [muted]Tickets go after the colon:[/muted] fix: IMP-123 resolve timeout")
72
+ console.out.print (" [muted]Scopes are optional:[/muted] refactor(auth): simplify flow")
73
+ console.out.print ()
74
+ console.out.print (" [muted]All lowercase after colon (except ticket IDs)[/muted]")
75
+ console.out.print (" [muted]Imperative mood (add, not added). Max 72 chars, no period.[/muted]")
76
+ console.out.print ()
77
+
78
+ console.divider ()
79
+ console.out.print ()
80
+
81
+ console.out.print ("[bold]AI whisper[/bold]")
82
+ console.out.print ()
83
+ console.out.print (' [muted]Any AI command accepts[/muted] --whisper / -w [muted]to hint the AI:[/muted]')
84
+ console.out.print ()
85
+ console.out.print (' imp commit -a -w "use IMP-99999 as ticket"')
86
+ console.out.print (' imp branch "auth flow" -w "use feat/ prefix"')
87
+ console.out.print (' imp review -w "focus on error handling"')
88
+ console.out.print ()
89
+
90
+ console.divider ()
91
+ console.out.print ()
92
+
93
+ console.out.print ("[bold]Common flows[/bold]")
94
+ console.out.print ()
95
+ console.muted ("Solo (trunk-based):")
96
+ console.out.print (" imp commit -a → imp commit -a → imp release")
97
+ console.out.print ()
98
+ console.muted ("Feature branch:")
99
+ console.out.print (" imp branch → imp commit -a → imp pr → imp done")
100
+ console.out.print ()
101
+ console.muted ("Hotfix:")
102
+ console.out.print (" imp fix 42 → imp commit -a → imp pr → imp done")
103
+ console.out.print ()
imp/commands/log.py ADDED
@@ -0,0 +1,25 @@
1
+ import typer
2
+
3
+ from imp import console, git
4
+
5
+
6
+ def log (
7
+ count: int = typer.Option (20, "-n", help="Number of commits"),
8
+ ref: str | None = typer.Argument (None, help="Branch or commit ref"),
9
+ ):
10
+ """Show pretty commit graph.
11
+
12
+ Displays a decorated commit graph with branch topology. Defaults to
13
+ the last 20 commits; use -n to adjust. Optionally pass a branch or
14
+ ref to view its history instead of the current branch.
15
+ """
16
+
17
+ git.require ()
18
+
19
+ console.header ("Log")
20
+
21
+ output = git.log_graph (count, ref or "")
22
+ if output:
23
+ print (output)
24
+ else:
25
+ console.muted ("No commits")
imp/commands/pr.py ADDED
@@ -0,0 +1,125 @@
1
+ import shutil
2
+ import subprocess
3
+
4
+ import typer
5
+
6
+ from imp import ai, console, git, prompts
7
+
8
+
9
+ def _parse_response (content: str) -> tuple [str, str]:
10
+ title = ""
11
+ for line in content.splitlines ():
12
+ if line.startswith ("TITLE:"):
13
+ title = line [6:].strip ()
14
+ break
15
+
16
+ description = ""
17
+ parts = content.split ("DESCRIPTION:", 1)
18
+ if len (parts) > 1:
19
+ description = parts [1].strip ()
20
+
21
+ return title, description
22
+
23
+
24
+ def pr (
25
+ yes: bool = typer.Option (False, "--yes", "-y", help="Accept AI description without review"),
26
+ whisper: str = typer.Option ("", "--whisper", "-w", help="Hint to guide the AI"),
27
+ ):
28
+ """Create a GitHub pull request with AI-generated description.
29
+
30
+ Diffs the current branch against the base branch, then uses AI to
31
+ generate a PR title and description. Pushes to origin if needed and
32
+ creates the PR via the gh CLI. Requires gh to be installed.
33
+ """
34
+
35
+ git.require ()
36
+
37
+ if not shutil.which ("gh"):
38
+ console.err ("GitHub CLI (gh) not installed")
39
+ console.hint ("https://cli.github.com")
40
+ raise typer.Exit (1)
41
+
42
+ console.header ("Pull Request")
43
+
44
+ b = git.branch ()
45
+ base = git.base_branch ()
46
+
47
+ if b == base:
48
+ console.err (f"Cannot create PR from {base}")
49
+ console.hint ("imp branch <description>")
50
+ raise typer.Exit (1)
51
+
52
+ log = git.log_oneline (rev_range=f"{base}..{b}")
53
+
54
+ if not log:
55
+ console.err (f"No commits on {b}")
56
+ raise typer.Exit (1)
57
+
58
+ console.label ("Branch")
59
+ console.item (f"{b} → {base}")
60
+ console.out.print ()
61
+
62
+ console.items ("Commits", log)
63
+
64
+ d = git.diff_range (f"{base}..{b}", max_lines=ai.MAX_DIFF_LINES)
65
+
66
+ pr_content = console.spin (
67
+ "Thinking...",
68
+ ai.smart,
69
+ prompts.pr (b, log, d, whisper),
70
+ False,
71
+ )
72
+
73
+ title, description = _parse_response (pr_content)
74
+
75
+ if not title:
76
+ console.warn ("Could not parse title, using branch name")
77
+ title = b
78
+
79
+ console.label ("Title")
80
+ console.item (title)
81
+ console.out.print ()
82
+
83
+ console.label ("Description")
84
+ console.divider ()
85
+ console.md (description)
86
+ console.divider ()
87
+ console.out.print ()
88
+
89
+ if not yes:
90
+ choice = console.choose ("Create PR?", [ "Yes", "Edit", "No" ])
91
+
92
+ if choice == "Edit":
93
+ edited = console.edit (f"{title}\n\n{description}")
94
+ lines = edited.splitlines ()
95
+ title = lines [0] if lines else title
96
+ description = "\n".join (lines [2:]) if len (lines) > 2 else description
97
+ elif choice == "No":
98
+ console.muted ("Cancelled")
99
+ raise typer.Exit (0)
100
+
101
+ if not git.has_upstream ():
102
+ console.spin ("Pushing to origin...", git.push, False, True, b)
103
+
104
+ try:
105
+ result = subprocess.run (
106
+ [
107
+ "gh", "pr", "create",
108
+ "--title", title,
109
+ "--body", description,
110
+ "--base", base,
111
+ ],
112
+ capture_output=True,
113
+ text=True,
114
+ check=True,
115
+ )
116
+ pr_url = result.stdout.strip ()
117
+ except subprocess.CalledProcessError:
118
+ console.err ("Failed to create PR")
119
+ raise typer.Exit (1) from None
120
+
121
+ console.out.print ()
122
+ console.success ("Created PR")
123
+ console.item (pr_url)
124
+
125
+ console.hint ("gh pr view --web to open in browser")
imp/commands/push.py ADDED
@@ -0,0 +1,42 @@
1
+ import subprocess
2
+
3
+ import typer
4
+
5
+ from imp import console, git
6
+
7
+
8
+ def push ():
9
+ """Push commits to origin.
10
+
11
+ Pushes the current branch. Sets upstream tracking on first push.
12
+ Does not create tags, changelogs, or releases.
13
+ """
14
+
15
+ git.require ()
16
+ git.require_clean ()
17
+
18
+ console.header ("Push")
19
+
20
+ b = git.branch ()
21
+
22
+ if not git.remote_exists ():
23
+ console.err ("No remote configured")
24
+ console.hint ("git remote add origin <url>")
25
+ raise typer.Exit (1)
26
+
27
+ ahead = 0
28
+ if git.has_upstream ():
29
+ git.fetch ()
30
+ ahead = git.count_ahead ()
31
+
32
+ if ahead == 0:
33
+ console.success ("Nothing to push")
34
+ raise typer.Exit (0)
35
+
36
+ console.item (f"{ahead} commits on {b}")
37
+ git.push ()
38
+ else:
39
+ console.item (f"Setting upstream for {b}")
40
+ git.push (set_upstream=True, target=b)
41
+
42
+ console.success ("Pushed to origin")
@@ -0,0 +1,273 @@
1
+ import shutil
2
+ import subprocess
3
+ from datetime import date
4
+ from pathlib import Path
5
+
6
+ import typer
7
+
8
+ from imp import console, git, version
9
+
10
+
11
+ def _rollback_release (
12
+ ver: str,
13
+ original_head: str,
14
+ changelog_path: Path,
15
+ original_changelog: str,
16
+ committed: bool,
17
+ ):
18
+ console.warn ("Rolling back...")
19
+ git.tag_delete (f"v{ver}")
20
+ if committed:
21
+ git.reset (original_head, hard=True)
22
+ if original_changelog:
23
+ changelog_path.write_text (original_changelog)
24
+ elif changelog_path.is_file ():
25
+ changelog_path.unlink ()
26
+ console.err ("Release failed")
27
+
28
+
29
+ def _write_changelog (path: Path, new_entry: str):
30
+ if path.is_file ():
31
+ content = path.read_text ()
32
+
33
+ lines = content.splitlines (keepends=True)
34
+ insert_at = None
35
+ for i, line in enumerate (lines):
36
+ if line.lstrip ().startswith ("## "):
37
+ insert_at = i
38
+ break
39
+
40
+ if insert_at is not None:
41
+ before = "".join (lines [:insert_at])
42
+ after = "".join (lines [insert_at:])
43
+ content = before + new_entry + "\n\n" + after
44
+ else:
45
+ content = content + "\n" + new_entry + "\n"
46
+
47
+ path.write_text (content)
48
+ else:
49
+ path.write_text (
50
+ f"# Changelog\n\n"
51
+ f"All notable changes to this project will be documented in this file.\n\n"
52
+ f"{new_entry}\n"
53
+ )
54
+
55
+
56
+ def _squash_commits (tag: str, summary: str, changelog_path: str, count: int) -> bool:
57
+ can_squash = False
58
+ if tag:
59
+ if git.has_upstream ():
60
+ unpushed = git.log_oneline (rev_range="@{u}..HEAD")
61
+ if unpushed:
62
+ can_squash = True
63
+ else:
64
+ can_squash = True
65
+
66
+ if can_squash:
67
+ git.reset (tag, soft=True)
68
+ git.stage (all=True)
69
+ git.commit (summary)
70
+ console.success (f"Squashed {count} commits")
71
+ else:
72
+ git.add ([ str (changelog_path) ])
73
+ git.commit (summary)
74
+ if tag:
75
+ console.muted ("Commits already pushed, skipped squash")
76
+
77
+ return can_squash
78
+
79
+
80
+ def _push_release (ver: str, entry: str, can_squash: bool):
81
+ if git.has_upstream ():
82
+ if can_squash:
83
+ git.push (force_lease=True)
84
+ else:
85
+ git.push ()
86
+ else:
87
+ b = git.branch ()
88
+ git.push (set_upstream=True, target=b)
89
+
90
+ git.push (ref=f"v{ver}")
91
+ console.success ("Pushed to origin")
92
+
93
+ if shutil.which ("gh"):
94
+ try:
95
+ subprocess.run (
96
+ [
97
+ "gh", "release", "create",
98
+ f"v{ver}",
99
+ "--title", f"v{ver}",
100
+ "--notes", entry,
101
+ ],
102
+ capture_output=True,
103
+ text=True,
104
+ check=True,
105
+ )
106
+ console.success ("Created GitHub release")
107
+ except subprocess.CalledProcessError:
108
+ console.muted ("GitHub release skipped (gh auth or repo issue)")
109
+
110
+
111
+ def release ():
112
+ """Squash, changelog, tag, and push a release.
113
+
114
+ Collects commits since the last tag, lets you pick a semver bump,
115
+ generates a changelog entry, squashes unpushed commits into one, tags
116
+ the release, and optionally pushes with a GitHub release. Rolls back
117
+ automatically if anything fails.
118
+ """
119
+
120
+ git.require ()
121
+
122
+ base = git.base_branch ()
123
+ current = git.branch ()
124
+ if current != base:
125
+ console.warn (f"Releasing from {current}, not {base}")
126
+ if not console.confirm ("Continue?"):
127
+ console.muted ("Cancelled")
128
+ raise typer.Exit (0)
129
+
130
+ git.require_clean ("imp commit first")
131
+
132
+ tag = git.last_tag ()
133
+
134
+ log = ""
135
+ if tag:
136
+ log = git.log_oneline (rev_range=f"{tag}..HEAD")
137
+
138
+ if not log:
139
+ log = git.log_oneline (count=20)
140
+ tag = ""
141
+
142
+ if not log:
143
+ console.muted ("No commits to release")
144
+ raise typer.Exit (0)
145
+
146
+ count = len (log.splitlines ())
147
+
148
+ console.header ("Release")
149
+
150
+ console.items (f"Commits since {tag or 'beginning'}", log)
151
+
152
+ highest = git.highest_tag ()
153
+ current = highest.lstrip ("v") if highest else "0.0.0"
154
+ if not current:
155
+ current = "0.0.0"
156
+
157
+ patch_ver = version.bump (current, "patch")
158
+ minor_ver = version.bump (current, "minor")
159
+ major_ver = version.bump (current, "major")
160
+
161
+ console.muted (f"Current: {current}")
162
+ console.out.print ()
163
+
164
+ choice = console.choose (
165
+ "Version bump",
166
+ [
167
+ f"patch {patch_ver}",
168
+ f"minor {minor_ver}",
169
+ f"major {major_ver}",
170
+ "custom",
171
+ "quit",
172
+ ],
173
+ )
174
+
175
+ if choice.startswith ("patch"):
176
+ new_version = patch_ver
177
+ elif choice.startswith ("minor"):
178
+ new_version = minor_ver
179
+ elif choice.startswith ("major"):
180
+ new_version = major_ver
181
+ elif choice == "custom":
182
+ new_version = console.prompt ("Version:", patch_ver)
183
+ else:
184
+ console.muted ("Cancelled")
185
+ raise typer.Exit (0)
186
+
187
+ if git.tag_exists (f"v{new_version}"):
188
+ console.err (f"Tag v{new_version} already exists")
189
+ console.hint (f"pick a different version, or: git tag -d v{new_version}")
190
+ raise typer.Exit (1)
191
+
192
+ if tag:
193
+ subjects = git.log_subjects (rev_range=f"{tag}..HEAD")
194
+ else:
195
+ subjects = git.log_subjects (count=count)
196
+
197
+ entry = version.changelog_from_commits (subjects)
198
+
199
+ console.label (f"v{new_version}")
200
+ console.divider ()
201
+ console.md (entry)
202
+ console.divider ()
203
+ console.out.print ()
204
+
205
+ choice = console.choose (
206
+ f"Release v{new_version}?",
207
+ [ "Yes", "Edit", "No" ],
208
+ )
209
+
210
+ if choice == "Edit":
211
+ entry = console.edit (entry)
212
+ console.out.print ()
213
+ console.label (f"v{new_version} (edited)")
214
+ console.divider ()
215
+ console.md (entry)
216
+ console.divider ()
217
+ console.out.print ()
218
+ if not console.confirm (f"Release v{new_version}?"):
219
+ console.muted ("Cancelled")
220
+ raise typer.Exit (0)
221
+ elif choice == "No":
222
+ console.muted ("Cancelled")
223
+ raise typer.Exit (0)
224
+
225
+ will_push = console.confirm ("Push after release?")
226
+
227
+ if will_push and not git.remote_exists ():
228
+ console.err ("No remote configured")
229
+ console.hint ("git remote add origin <url>")
230
+ raise typer.Exit (1)
231
+
232
+ summary = f"chore: release v{new_version}"
233
+ today = date.today ().isoformat ()
234
+ new_entry = f"## [{new_version}] - {today}\n\n{entry}"
235
+
236
+ root = git.repo_root ()
237
+ changelog_path = Path (root) / "CHANGELOG.md"
238
+
239
+ original_head = git.rev_parse ("HEAD")
240
+ original_changelog = ""
241
+ if changelog_path.is_file ():
242
+ original_changelog = changelog_path.read_text ()
243
+
244
+ committed = False
245
+
246
+ try:
247
+ _write_changelog (changelog_path, new_entry)
248
+ console.success ("Updated CHANGELOG.md")
249
+
250
+ can_squash = _squash_commits (tag, summary, changelog_path, count)
251
+ committed = True
252
+
253
+ git.tag (f"v{new_version}")
254
+ console.success (f"Tagged v{new_version}")
255
+
256
+ except (subprocess.CalledProcessError, OSError) as e:
257
+ msg = getattr (e, "stderr", "") or str (e)
258
+ console.err (f"Release failed: {msg.strip ()}")
259
+ _rollback_release (
260
+ new_version, original_head, changelog_path,
261
+ original_changelog, committed,
262
+ )
263
+ raise typer.Exit (1) from None
264
+
265
+ if will_push:
266
+ try:
267
+ _push_release (new_version, entry, can_squash)
268
+ except (subprocess.CalledProcessError, OSError) as e:
269
+ msg = getattr (e, "stderr", "") or str (e)
270
+ console.err (f"Push failed: {msg.strip ()}")
271
+ raise typer.Exit (1) from None
272
+
273
+ console.hint ("make changes, then imp commit")