jj-stack 0.1.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.
- jj_stack/__init__.py +11 -0
- jj_stack/__main__.py +8 -0
- jj_stack/bootstrap.py +213 -0
- jj_stack/cli.py +1297 -0
- jj_stack/cli_help.py +680 -0
- jj_stack/commands/__init__.py +1 -0
- jj_stack/commands/_cleanup_actions.py +389 -0
- jj_stack/commands/_json_status.py +84 -0
- jj_stack/commands/checkout.py +668 -0
- jj_stack/commands/cleanup/command.py +702 -0
- jj_stack/commands/cleanup/shared.py +62 -0
- jj_stack/commands/cleanup/stale.py +79 -0
- jj_stack/commands/doctor.py +358 -0
- jj_stack/commands/in_use.py +31 -0
- jj_stack/commands/list_.py +667 -0
- jj_stack/commands/merge/__init__.py +1 -0
- jj_stack/commands/merge/command.py +385 -0
- jj_stack/commands/merge/github_stack.py +333 -0
- jj_stack/commands/merge/models.py +97 -0
- jj_stack/commands/merge/plan.py +109 -0
- jj_stack/commands/merge/preconditions.py +162 -0
- jj_stack/commands/merge/render.py +52 -0
- jj_stack/commands/relink.py +210 -0
- jj_stack/commands/submit/__init__.py +1 -0
- jj_stack/commands/submit/auto_close.py +108 -0
- jj_stack/commands/submit/changes.py +90 -0
- jj_stack/commands/submit/command.py +780 -0
- jj_stack/commands/submit/descriptions.py +570 -0
- jj_stack/commands/submit/github_stack.py +139 -0
- jj_stack/commands/submit/inputs.py +168 -0
- jj_stack/commands/submit/models.py +198 -0
- jj_stack/commands/submit/overview_comments.py +178 -0
- jj_stack/commands/submit/prs.py +461 -0
- jj_stack/commands/submit/render.py +129 -0
- jj_stack/commands/sync.py +510 -0
- jj_stack/commands/sync_apply.py +474 -0
- jj_stack/commands/unstack.py +321 -0
- jj_stack/commands/view.py +1151 -0
- jj_stack/completion.py +484 -0
- jj_stack/concurrency.py +93 -0
- jj_stack/config.py +170 -0
- jj_stack/console.py +708 -0
- jj_stack/errors.py +152 -0
- jj_stack/formatting.py +95 -0
- jj_stack/github/__init__.py +1 -0
- jj_stack/github/auth.py +34 -0
- jj_stack/github/client.py +1098 -0
- jj_stack/github/error_messages.py +89 -0
- jj_stack/github/overview_comments.py +35 -0
- jj_stack/github/pr_refs.py +70 -0
- jj_stack/github/resolution.py +208 -0
- jj_stack/github/stack_availability.py +30 -0
- jj_stack/identifiers.py +7 -0
- jj_stack/jj/__init__.py +1 -0
- jj_stack/jj/cli_args.py +23 -0
- jj_stack/jj/client.py +1459 -0
- jj_stack/jj/colors.py +199 -0
- jj_stack/models/__init__.py +1 -0
- jj_stack/models/git.py +15 -0
- jj_stack/models/github.py +243 -0
- jj_stack/models/stack.py +70 -0
- jj_stack/models/tracking.py +115 -0
- jj_stack/pr_branch_namespace.py +78 -0
- jj_stack/stack/__init__.py +1 -0
- jj_stack/stack/change_status.py +216 -0
- jj_stack/stack/convergence.py +501 -0
- jj_stack/stack/convergence_models.py +69 -0
- jj_stack/stack/convergence_observation.py +162 -0
- jj_stack/stack/github_stack_safety.py +132 -0
- jj_stack/stack/global_convergence.py +270 -0
- jj_stack/stack/path.py +235 -0
- jj_stack/stack/pr_branches.py +126 -0
- jj_stack/stack/pr_facts.py +165 -0
- jj_stack/stack/repo.py +95 -0
- jj_stack/stack/selected.py +358 -0
- jj_stack/stack/selection.py +124 -0
- jj_stack/stack/status.py +711 -0
- jj_stack/stack/trunk_evidence.py +174 -0
- jj_stack/state/__init__.py +1 -0
- jj_stack/state/operation_lock.py +220 -0
- jj_stack/state/store.py +260 -0
- jj_stack/ui.py +230 -0
- jj_stack-0.1.0.dist-info/METADATA +170 -0
- jj_stack-0.1.0.dist-info/RECORD +88 -0
- jj_stack-0.1.0.dist-info/WHEEL +4 -0
- jj_stack-0.1.0.dist-info/entry_points.txt +2 -0
- jj_stack-0.1.0.dist-info/licenses/LICENSE +201 -0
- jj_stack-0.1.0.dist-info/licenses/NOTICE +2 -0
jj_stack/ui.py
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""Semantic message fragments shared across layers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable, Iterable
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from string.templatelib import Interpolation, Template, convert
|
|
8
|
+
from typing import Literal
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True, slots=True)
|
|
12
|
+
class SemanticText:
|
|
13
|
+
"""A short semantic text fragment that should preserve its semantic labels."""
|
|
14
|
+
|
|
15
|
+
text: str
|
|
16
|
+
labels: tuple[str, ...]
|
|
17
|
+
|
|
18
|
+
def __str__(self) -> str:
|
|
19
|
+
return self.text
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
StatusValue = Literal["ok", "warn", "fail", "fixed", "skip"]
|
|
23
|
+
type Message = str | Template | SemanticText | tuple[Message, ...]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True, slots=True)
|
|
27
|
+
class StatusBadge:
|
|
28
|
+
"""A semantic status indicator rendered by the console layer."""
|
|
29
|
+
|
|
30
|
+
value: StatusValue
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True, slots=True)
|
|
34
|
+
class PrefixedLine:
|
|
35
|
+
"""A hanging-indent line with semantic prefix and body content."""
|
|
36
|
+
|
|
37
|
+
prefix: Message
|
|
38
|
+
body: Message | StatusBadge
|
|
39
|
+
message_labels: tuple[str, ...] | None = None
|
|
40
|
+
prefix_labels: tuple[str, ...] | None = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
type TableCell = Message | StatusBadge | PrefixedLine
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True, slots=True)
|
|
47
|
+
class TableColumn:
|
|
48
|
+
"""A presentation-layer table column description."""
|
|
49
|
+
|
|
50
|
+
header: str
|
|
51
|
+
no_wrap: bool = False
|
|
52
|
+
width: int | None = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass(frozen=True, slots=True)
|
|
56
|
+
class DataTable:
|
|
57
|
+
"""A lightweight table model rendered by the console layer."""
|
|
58
|
+
|
|
59
|
+
columns: tuple[TableColumn, ...]
|
|
60
|
+
rows: tuple[tuple[TableCell, ...], ...]
|
|
61
|
+
box: str = "simple"
|
|
62
|
+
expand: bool = False
|
|
63
|
+
header_style: str | None = "bold"
|
|
64
|
+
pad_edge: bool = False
|
|
65
|
+
padding: int | tuple[int, int] | tuple[int, int, int, int] = (0, 0)
|
|
66
|
+
show_edge: bool = False
|
|
67
|
+
show_header: bool = True
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
type Renderable = TableCell | DataTable
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def semantic_text(text: str, *labels: str) -> SemanticText:
|
|
74
|
+
"""Wrap text with semantic labels for later rendering."""
|
|
75
|
+
|
|
76
|
+
return SemanticText(text=text, labels=labels)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def bookmark(name: str) -> SemanticText:
|
|
80
|
+
"""Wrap bookmark-like names, including Git remotes, for semantic rendering."""
|
|
81
|
+
|
|
82
|
+
label = "remote_bookmarks" if "@" in name else "local_bookmarks"
|
|
83
|
+
return semantic_text(name, label)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def change_id(name: str) -> SemanticText:
|
|
87
|
+
"""Wrap a change ID for semantic rendering, shortening it for display."""
|
|
88
|
+
|
|
89
|
+
return semantic_text(name[:8], "change_id")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def commit_id(name: str) -> SemanticText:
|
|
93
|
+
"""Wrap a commit ID for semantic rendering, shortening it for display."""
|
|
94
|
+
|
|
95
|
+
return semantic_text(name[:8], "commit_id")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def revset(text: str) -> SemanticText:
|
|
99
|
+
"""Wrap jj revset syntax for semantic rendering."""
|
|
100
|
+
|
|
101
|
+
return semantic_text(text, "revset")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def metavar(text: str) -> SemanticText:
|
|
105
|
+
"""Wrap a command-line metavariable."""
|
|
106
|
+
|
|
107
|
+
return semantic_text(text, "metavar")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def code(text: str) -> SemanticText:
|
|
111
|
+
"""Wrap a code-like token for semantic rendering."""
|
|
112
|
+
|
|
113
|
+
return semantic_text(text, "code")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def cmd(text: str) -> SemanticText:
|
|
117
|
+
"""Wrap a command-line snippet for semantic rendering.
|
|
118
|
+
|
|
119
|
+
This can be used for commands, options, or arguments.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
return semantic_text(text, "command", "hint")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def option(text: str) -> SemanticText:
|
|
126
|
+
"""Wrap one command-line option."""
|
|
127
|
+
|
|
128
|
+
return semantic_text(text, "option", "command", "hint")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def status(value: StatusValue) -> StatusBadge:
|
|
132
|
+
"""Wrap a status indicator for semantic rendering."""
|
|
133
|
+
|
|
134
|
+
return StatusBadge(value=value)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def join[T](
|
|
138
|
+
render_item: Callable[[T], Message],
|
|
139
|
+
items: Iterable[T],
|
|
140
|
+
) -> tuple[Message, ...]:
|
|
141
|
+
"""Render and comma-join items."""
|
|
142
|
+
|
|
143
|
+
parts: list[Message] = []
|
|
144
|
+
for index, item in enumerate(items):
|
|
145
|
+
if index:
|
|
146
|
+
parts.append(", ")
|
|
147
|
+
parts.append(render_item(item))
|
|
148
|
+
return tuple(parts)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def prefixed_line(
|
|
152
|
+
prefix: Message,
|
|
153
|
+
body: Message | StatusBadge,
|
|
154
|
+
*,
|
|
155
|
+
message_labels: tuple[str, ...] | None = None,
|
|
156
|
+
prefix_labels: tuple[str, ...] | None = None,
|
|
157
|
+
) -> PrefixedLine:
|
|
158
|
+
"""Build a hanging-indent line without choosing a concrete renderer."""
|
|
159
|
+
|
|
160
|
+
return PrefixedLine(
|
|
161
|
+
prefix=prefix,
|
|
162
|
+
body=body,
|
|
163
|
+
message_labels=message_labels,
|
|
164
|
+
prefix_labels=prefix_labels,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def plain_text(content: Message) -> str:
|
|
169
|
+
"""Render semantic template content into plain text."""
|
|
170
|
+
|
|
171
|
+
parts: list[str] = []
|
|
172
|
+
_append_plain_text(parts, content)
|
|
173
|
+
return "".join(parts)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _append_plain_text(parts: list[str], content: Message) -> None:
|
|
177
|
+
if isinstance(content, tuple):
|
|
178
|
+
for item in content:
|
|
179
|
+
_append_plain_text(parts, item)
|
|
180
|
+
return
|
|
181
|
+
if isinstance(content, Template):
|
|
182
|
+
for part in content:
|
|
183
|
+
if isinstance(part, str):
|
|
184
|
+
parts.append(part)
|
|
185
|
+
else:
|
|
186
|
+
_append_plain_text(parts, resolve_interpolation(part))
|
|
187
|
+
return
|
|
188
|
+
if isinstance(content, SemanticText):
|
|
189
|
+
parts.append(content.text)
|
|
190
|
+
return
|
|
191
|
+
parts.append(str(content))
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def resolve_interpolation(interpolation: Interpolation) -> Message:
|
|
195
|
+
value = interpolation.value
|
|
196
|
+
if isinstance(value, SemanticText):
|
|
197
|
+
if interpolation.conversion is not None:
|
|
198
|
+
converted = convert(value.text, interpolation.conversion)
|
|
199
|
+
return (
|
|
200
|
+
format(converted, interpolation.format_spec)
|
|
201
|
+
if interpolation.format_spec
|
|
202
|
+
else converted
|
|
203
|
+
)
|
|
204
|
+
if interpolation.format_spec:
|
|
205
|
+
return SemanticText(
|
|
206
|
+
text=format(value.text, interpolation.format_spec),
|
|
207
|
+
labels=value.labels,
|
|
208
|
+
)
|
|
209
|
+
return value
|
|
210
|
+
if isinstance(value, Template):
|
|
211
|
+
if interpolation.conversion is not None or interpolation.format_spec:
|
|
212
|
+
plain = plain_text(value)
|
|
213
|
+
converted = convert(plain, interpolation.conversion)
|
|
214
|
+
if interpolation.format_spec:
|
|
215
|
+
return format(converted, interpolation.format_spec)
|
|
216
|
+
return converted
|
|
217
|
+
return value
|
|
218
|
+
if isinstance(value, tuple):
|
|
219
|
+
if interpolation.conversion is not None or interpolation.format_spec:
|
|
220
|
+
plain = plain_text(value)
|
|
221
|
+
converted = convert(plain, interpolation.conversion)
|
|
222
|
+
if interpolation.format_spec:
|
|
223
|
+
return format(converted, interpolation.format_spec)
|
|
224
|
+
return converted
|
|
225
|
+
return value
|
|
226
|
+
|
|
227
|
+
converted = convert(value, interpolation.conversion)
|
|
228
|
+
if interpolation.format_spec:
|
|
229
|
+
return format(converted, interpolation.format_spec)
|
|
230
|
+
return str(converted)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: jj-stack
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Stacked GitHub pull requests for Jujutsu
|
|
5
|
+
Project-URL: Homepage, https://www.serpentine.com/software/jj-stack/
|
|
6
|
+
Project-URL: Documentation, https://www.serpentine.com/software/jj-stack/
|
|
7
|
+
Project-URL: Repository, https://github.com/bos/jj-stack
|
|
8
|
+
Project-URL: Issues, https://github.com/bos/jj-stack/issues
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
License-File: NOTICE
|
|
12
|
+
Keywords: github,jj,jujutsu,pull-requests,stacked-pull-requests
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Software Development :: Version Control
|
|
20
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
21
|
+
Requires-Python: >=3.14
|
|
22
|
+
Requires-Dist: httpxyz<0.32,>=0.31.0
|
|
23
|
+
Requires-Dist: pydantic<3,>=2.13.3
|
|
24
|
+
Requires-Dist: rich<16,>=15.0.0
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# jj-stack: manage stacked GitHub PRs with jj
|
|
28
|
+
|
|
29
|
+
`jj-stack` turns a linear series of local `jj` changes into a stack of GitHub pull requests.
|
|
30
|
+
Rewrite, split, squash, or reorder the changes with `jj`, then let `jj-stack` update the
|
|
31
|
+
matching PRs.
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
### Requirements
|
|
36
|
+
|
|
37
|
+
- Python 3.14 or newer
|
|
38
|
+
- `uv`
|
|
39
|
+
- `jj` 0.44.0 or newer
|
|
40
|
+
- GitHub authentication
|
|
41
|
+
|
|
42
|
+
### Install
|
|
43
|
+
|
|
44
|
+
Install `jj-stack` from PyPI:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
uv tool install jj-stack
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
To upgrade, rerun that command with `--force`. If the command is not on your shell `PATH`, run
|
|
51
|
+
`uv tool update-shell`.
|
|
52
|
+
|
|
53
|
+
For tab completion, add the output of `jj-stack completion` to your shell startup file:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
eval "$(jj-stack completion zsh)"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`bash` and `fish` work the same way.
|
|
60
|
+
|
|
61
|
+
To invoke it as `jj stack` and complete that alias too, see
|
|
62
|
+
[Configuration](https://www.serpentine.com/software/jj-stack/reference/configuration/).
|
|
63
|
+
|
|
64
|
+
### Submit your first stack
|
|
65
|
+
|
|
66
|
+
Start with a linear series of local `jj` changes on top of `trunk()`. In a new repo, check
|
|
67
|
+
the setup and apply the safe local fixes:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
jj-stack doctor --fix
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Inspect the stack that ends at your working copy:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
jj-stack
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Create one GitHub PR per local change:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
jj-stack submit
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Revise the changes locally with `jj` and rerun `jj-stack submit` whenever the stack is ready to
|
|
86
|
+
refresh. Use `jj-stack list` to see every tracked stack in the repo.
|
|
87
|
+
|
|
88
|
+
## Mental model
|
|
89
|
+
|
|
90
|
+
The local `jj` DAG determines which changes form a stack and their order. On GitHub, each change
|
|
91
|
+
gets a stable PR branch and a PR; every PR targets the PR branch below it, except the
|
|
92
|
+
bottom PR, which targets trunk by default:
|
|
93
|
+
|
|
94
|
+
```text
|
|
95
|
+
jj-stack/add-ui-... -> PR #3 (base: jj-stack/add-api-...)
|
|
96
|
+
jj-stack/add-api-... -> PR #2 (base: jj-stack/refactor-model-...)
|
|
97
|
+
jj-stack/refactor-model-... -> PR #1 (base: main)
|
|
98
|
+
main -> trunk
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The PR branches normally stay out of your local bookmark view. When you rewrite a change,
|
|
102
|
+
`jj-stack` updates that change's existing PR branch and PR, along with the PR branches and PRs for
|
|
103
|
+
dependent changes.
|
|
104
|
+
|
|
105
|
+
## Everyday workflow
|
|
106
|
+
|
|
107
|
+
1. Write code as a series of local `jj` changes.
|
|
108
|
+
2. Run `jj-stack submit`.
|
|
109
|
+
3. Revise, add, remove, or reorder the changes locally as reviews come in.
|
|
110
|
+
4. Run `jj-stack submit` again to refresh GitHub.
|
|
111
|
+
5. Run `jj-stack merge` when the changes at the bottom are ready.
|
|
112
|
+
6. After a queued or externally initiated merge finishes, run
|
|
113
|
+
`jj-stack sync <head-change-id>`.
|
|
114
|
+
|
|
115
|
+
`view`, `submit`, `merge`, and `sync` accept a change ID when you need to select a stack other
|
|
116
|
+
than the one ending at the working copy.
|
|
117
|
+
|
|
118
|
+
See the [user guide](https://www.serpentine.com/software/jj-stack/) for drafts, descriptions,
|
|
119
|
+
merge queues, cleanup, and working with multiple stacks.
|
|
120
|
+
|
|
121
|
+
## Learn more
|
|
122
|
+
|
|
123
|
+
- [Mental model](https://www.serpentine.com/software/jj-stack/mental-model/)
|
|
124
|
+
- [Quick start](https://www.serpentine.com/software/jj-stack/quick-start/)
|
|
125
|
+
- [Everyday workflows](https://www.serpentine.com/software/jj-stack/guides/submit-and-update/)
|
|
126
|
+
- [Configuration](https://www.serpentine.com/software/jj-stack/reference/configuration/)
|
|
127
|
+
- [Writing PR descriptions](https://www.serpentine.com/software/jj-stack/reference/descriptions/)
|
|
128
|
+
- [Troubleshooting](https://www.serpentine.com/software/jj-stack/troubleshooting/)
|
|
129
|
+
- [`jj-stack` and `gh stack`](https://www.serpentine.com/software/jj-stack/gh-stack/)
|
|
130
|
+
- [JSON output](https://www.serpentine.com/software/jj-stack/reference/json-output/)
|
|
131
|
+
- [Automation and exit codes](https://www.serpentine.com/software/jj-stack/reference/automation/)
|
|
132
|
+
|
|
133
|
+
The built-in help is the canonical flag reference:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
jj-stack --help
|
|
137
|
+
jj-stack <command> --help
|
|
138
|
+
jj-stack help --all
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Coding agent integration
|
|
142
|
+
|
|
143
|
+
Install the bundled skill to teach coding agents to work with local `jj` stacks and refresh their
|
|
144
|
+
GitHub PRs safely:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
gh skill install bos/jj-stack jj-stack
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
See the [skill source](https://github.com/bos/jj-stack/blob/main/skills/jj-stack/SKILL.md). In
|
|
151
|
+
my evaluations with Codex and Claude Code, agents with the `jj-stack` skill succeeded in 11/12
|
|
152
|
+
scenarios versus 6/12 without it, with one critical error versus four, using 60% fewer failed
|
|
153
|
+
command attempts and 18% fewer tool calls. (The critical error was due to Claude Haiku
|
|
154
|
+
understanding a rule and ignoring it. I haven't figured out how to get smaller Claude models to
|
|
155
|
+
behave better, and I don't personally use them.)
|
|
156
|
+
|
|
157
|
+
## Performance
|
|
158
|
+
|
|
159
|
+
Although `jj-stack` is written in Python, this does not significantly affect its speed.
|
|
160
|
+
The real determinants of its performance are the GitHub API and the `jj` command.
|
|
161
|
+
|
|
162
|
+
The GitHub API is *slow*; a single roundtrip takes many hundreds of milliseconds. `jj-stack`
|
|
163
|
+
reduces its impact with:
|
|
164
|
+
|
|
165
|
+
- GraphQL batch requests where possible
|
|
166
|
+
- concurrent use of the GitHub REST API
|
|
167
|
+
- periodic audits that its queries are minimal in extent
|
|
168
|
+
|
|
169
|
+
In pursuit of good performance, `jj-stack` also batches calls to `jj` and minimizes the amount
|
|
170
|
+
of work those calls must do.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
jj_stack/__init__.py,sha256=xOj0QztgnLYl1oYIcmniN9_TAJ3HyzDVt-Uh-6p9gjQ,273
|
|
2
|
+
jj_stack/__main__.py,sha256=DEO1C6HgNEWuIgPZByIoPR8yQ90HHdEo869dPvTK13A,175
|
|
3
|
+
jj_stack/bootstrap.py,sha256=GY9D8TOu4jvGNEZdbpNykPOdrIFFgaHO0itlYyYoGho,7050
|
|
4
|
+
jj_stack/cli.py,sha256=1m6412HdrQCisL1wY2XPAdMuOe-CtM106w1YkvXUvx0,42227
|
|
5
|
+
jj_stack/cli_help.py,sha256=HUIPBDEq4NmQ23Kw1gkrYswDD0SBMKTUfQXL-OUnAV0,23519
|
|
6
|
+
jj_stack/completion.py,sha256=CeEacS1xJURqvsYyfICCdupDx0eFsiIjC6B2ruqwiHs,16224
|
|
7
|
+
jj_stack/concurrency.py,sha256=HNQ164WFtBWNvRc0HQWiOCZQh2TuJ-StLiI_xSdw3CA,2932
|
|
8
|
+
jj_stack/config.py,sha256=Y3efFsSSD7_IsHXC6ToSIFu6tHkpw5yAF8G1aM_252A,5653
|
|
9
|
+
jj_stack/console.py,sha256=eUQxWaj8YItAZnDKEvvto4Nha4Ya9ukfE8eBBwihkmE,22158
|
|
10
|
+
jj_stack/errors.py,sha256=Sv5UOpwYA_AtC_PPrGyYWYYA-hTrUjQVOu4bBBqAzOI,4340
|
|
11
|
+
jj_stack/formatting.py,sha256=EKjpdB1NZpUHisvQvZ40XX4-0hImKfkfzZZAuc27JZA,2582
|
|
12
|
+
jj_stack/identifiers.py,sha256=NoELDuHtwv0-7oqGUThhSfWfrz_WpbXSJfdmBpljJ5o,187
|
|
13
|
+
jj_stack/pr_branch_namespace.py,sha256=ShPXnWyK43N2d8v88-6a192aIUbppeq6FHJGnokrSF0,2548
|
|
14
|
+
jj_stack/ui.py,sha256=DDSEqkXorLiENp6z5QG0smFdX2nbLQEbatEjt_F57f4,6538
|
|
15
|
+
jj_stack/commands/__init__.py,sha256=QAweKA6qt-08PEvBiZ6DirzntN3ScfoEzbuyzbdS_Mo,46
|
|
16
|
+
jj_stack/commands/_cleanup_actions.py,sha256=FG00gbVY9WZGY_FwPuPaErNCDeB_YDH-I65FxNeOq2s,12789
|
|
17
|
+
jj_stack/commands/_json_status.py,sha256=-VErmTJR0LKMGNWzwJx1ihR1oK1Q8sd0eFd-JYTRULA,2442
|
|
18
|
+
jj_stack/commands/checkout.py,sha256=CB7kcEgP8eUDGQUzsfgH0GZpb_YaWbGqzToWC8jrW4Y,24315
|
|
19
|
+
jj_stack/commands/doctor.py,sha256=JMp6dvzKxCs-IP-DHu37t-wuV9YMHxcAs-wdbUPFM8c,11747
|
|
20
|
+
jj_stack/commands/in_use.py,sha256=7HcXwL5XVdHwxIAhbyp0KcFfwep2GFikNp5NhfRa5LE,1150
|
|
21
|
+
jj_stack/commands/list_.py,sha256=F5FhSDYtd5t9hDFoo5guUJkB5qnhmLWl4DvE-J8L3iA,21609
|
|
22
|
+
jj_stack/commands/relink.py,sha256=L_D3dG-yC33v6i5qjVPKKCgLGapvq4IlaqEJ7bhjKgY,7070
|
|
23
|
+
jj_stack/commands/sync.py,sha256=6NkCww3hMjq6TUiHBr0pLRZ-rt86qCUMC0Gt7wP2A48,20620
|
|
24
|
+
jj_stack/commands/sync_apply.py,sha256=hqXcoiU2ySSA8YxpPoQcG4lGF-wBj97hzmNOd-oXPp4,16700
|
|
25
|
+
jj_stack/commands/unstack.py,sha256=7eegRmybr2RhKnLNx4fsZ2orKbBeYP5Dh3ig-Q5Qhz0,10267
|
|
26
|
+
jj_stack/commands/view.py,sha256=Xr4CvNk_v4lHO63z7cjwSn_14Ji_HgqUFxLBIeVg2A8,37865
|
|
27
|
+
jj_stack/commands/cleanup/command.py,sha256=Juv8nECxjdjDLdmEC2bAPP0lOrlczEzWRJApKVLiUTU,23592
|
|
28
|
+
jj_stack/commands/cleanup/shared.py,sha256=u3ckXkoWN9fv_OLpP7kfAczuBPHFwUBG1Mz2ocdfYh0,1768
|
|
29
|
+
jj_stack/commands/cleanup/stale.py,sha256=T7svin7aMD3p-Gk__QqvVdvLWfpctTGfYVmlProfyIY,2677
|
|
30
|
+
jj_stack/commands/merge/__init__.py,sha256=8cE_1kLL6LcIELntAgqMNx6iF3rqeSf_YuWlvbtb-CY,44
|
|
31
|
+
jj_stack/commands/merge/command.py,sha256=Ocx-KS_tgrZazEEMqSmMG9wBRbb6pPPwIB_GYCTpsBw,13844
|
|
32
|
+
jj_stack/commands/merge/github_stack.py,sha256=2zL9XBs5LV0mjkJ6WiwsshaS2QFsgS0Z5XLQs9-4q2s,11656
|
|
33
|
+
jj_stack/commands/merge/models.py,sha256=fAxDnmCA4D40GY0qYnlGQAo7yRnAhlQW2fOFKF1FNQ8,2507
|
|
34
|
+
jj_stack/commands/merge/plan.py,sha256=UF1KuZ4wU8fwgVNgy0sLa7SVdE81-tDerSo0dyIFrko,3456
|
|
35
|
+
jj_stack/commands/merge/preconditions.py,sha256=X2oY3ESH0uE7gUrV2orpUUygOgqrh18XmhR5JL__PB4,6375
|
|
36
|
+
jj_stack/commands/merge/render.py,sha256=QWvIzOcEISfNmDStzs_isldZ3OFJwBDiXoPvHtNHvHg,1838
|
|
37
|
+
jj_stack/commands/submit/__init__.py,sha256=gqdUEEZBkAyp926OnuvZysHeM5kBCuwtQafpUb8nan8,45
|
|
38
|
+
jj_stack/commands/submit/auto_close.py,sha256=GIX1Bhajj09S-s_NjEECLtCwYcrbiilYdJX0MNqelzE,3422
|
|
39
|
+
jj_stack/commands/submit/changes.py,sha256=ovAOxvBpVYa_-Mt8Kc29633DIZ3dW4K3jO5bc1suFao,3745
|
|
40
|
+
jj_stack/commands/submit/command.py,sha256=8xNeqMnN26YHoyg3pFVi3G10KjYYUotHkpUU9f0mwBc,28712
|
|
41
|
+
jj_stack/commands/submit/descriptions.py,sha256=MIK9W0hIctijK0lHnTlg6aI-iGqHrSXR8xYYySNAPWM,19946
|
|
42
|
+
jj_stack/commands/submit/github_stack.py,sha256=kSoWem8c5CBNyXsu8MhaDaN7pxn5RIlchENoYk8z3dw,5456
|
|
43
|
+
jj_stack/commands/submit/inputs.py,sha256=vmxrtjcWxHnl6Z-5mEo1iiq7eel8RZU9DfR_piSRht8,5801
|
|
44
|
+
jj_stack/commands/submit/models.py,sha256=EjF7eNN0rhunBjl2hvanftY6Ilwsda_bjEC39mfDp50,5669
|
|
45
|
+
jj_stack/commands/submit/overview_comments.py,sha256=Neie5Kp1CLCIYvfyTucW77iAXkScdgcWTNbzWfv9IOE,5751
|
|
46
|
+
jj_stack/commands/submit/prs.py,sha256=3USaQLXOhvIpsByR67ROuz1pnYp0-n_uSKnsX6BQxb8,16099
|
|
47
|
+
jj_stack/commands/submit/render.py,sha256=9UdWcksfx5FlkHhHlo_Fy_00dz6NryZGgmEaH4xvyck,3899
|
|
48
|
+
jj_stack/github/__init__.py,sha256=wLJPTInhzISydB4lQ-cWXOhx6nEeXBqn2jaEgKQKmrY,34
|
|
49
|
+
jj_stack/github/auth.py,sha256=UTyldI9Dlyx4rVksv9x_aw27h50VvFuvWoBZaFKrVH0,790
|
|
50
|
+
jj_stack/github/client.py,sha256=7AHkjhgW0Waf9w1AsEY-M7PUs2AohJ8gkHYvZouw9Po,34761
|
|
51
|
+
jj_stack/github/error_messages.py,sha256=v0Prwh9DXER_vftBHYhvDeu6uf9GODB3_TmLlXtQh08,2912
|
|
52
|
+
jj_stack/github/overview_comments.py,sha256=gV85sYRzMFnZ8-ePGaPkuNScFw-OgitbtbTMU-X35eU,1031
|
|
53
|
+
jj_stack/github/pr_refs.py,sha256=b3X5W7CbShe2Om2_HEPpFlpHVAGGdMcDctg1n6Jt_90,1923
|
|
54
|
+
jj_stack/github/resolution.py,sha256=eb6AC4qOovSNLyTbDf-Y1-SmXgLCfozh3Ux67u7ROnI,7213
|
|
55
|
+
jj_stack/github/stack_availability.py,sha256=B1XAwmYniU37zRGaw6fxDMb808Q_GJMfurv-27yEt5s,883
|
|
56
|
+
jj_stack/jj/__init__.py,sha256=3Q1JPAUWM_WNSVTYSSVdTQeZ9ajoKYHxyDoeoyGYNJs,45
|
|
57
|
+
jj_stack/jj/cli_args.py,sha256=Wnp-r29ZuA9_sCstwiCJG9ejxiM6MCY4SaknpDgOYLM,794
|
|
58
|
+
jj_stack/jj/client.py,sha256=rvZUdT0Tn0fW7NZnJcFG4e2b8O1Z9k_XsSDxLKjbb2I,53443
|
|
59
|
+
jj_stack/jj/colors.py,sha256=0mB6YmrQMyeSrMErIFc7bz-1VSRzuOggXLRCJEc6ToU,6570
|
|
60
|
+
jj_stack/models/__init__.py,sha256=OOw4B0fALOH6NqMpFWP-Yx2ZZYp3lN3-TbyLbICwKCo,56
|
|
61
|
+
jj_stack/models/git.py,sha256=L-HoS_s4uxL9Z-0gXmDOSCz4Z2j32BfI1EtmXEq1GkM,294
|
|
62
|
+
jj_stack/models/github.py,sha256=5BstIzymtRzsQORdT0ZNnNdFqEGRcA8NE5V8Pj_y_pM,7258
|
|
63
|
+
jj_stack/models/stack.py,sha256=ro5eVHaC3bIi65Qk5zXLXc1FcloHRXBkETJbYZh1Dk4,2203
|
|
64
|
+
jj_stack/models/tracking.py,sha256=lQzt0EgveBfRGS5tMpmOIiXjfwUbxI-PdD7iCCJYCiw,3601
|
|
65
|
+
jj_stack/stack/__init__.py,sha256=Rw0c_kAYZvVBubOIozFLcyh0Ef84qY_IouL3gXpnHrw,48
|
|
66
|
+
jj_stack/stack/change_status.py,sha256=DAaxaYumviki7ZYx9PAY8HJ82gZTgd9-xE55E-dWb6I,6461
|
|
67
|
+
jj_stack/stack/convergence.py,sha256=Ocf5lFPnIlGYUInlOdXo8lqe6xeEVHCMgMgctOZhJJ4,19542
|
|
68
|
+
jj_stack/stack/convergence_models.py,sha256=GsChOEWmoYV5RmUzrBzI3HxBcw82MX3MVwvJzCfc-Qo,1624
|
|
69
|
+
jj_stack/stack/convergence_observation.py,sha256=ul_RO6germ4qY6CYxDURJ8kRLVn0Ofi3ULln4fnXAbY,5322
|
|
70
|
+
jj_stack/stack/github_stack_safety.py,sha256=xMsD4ymdXZZUcJDnlHTd5e_1UTsHWvHfGJ_fdGX2xLE,5561
|
|
71
|
+
jj_stack/stack/global_convergence.py,sha256=drVstw0hTH234FCAXWw74r_Gbq5Em5cfyPxmXFIKgWA,8477
|
|
72
|
+
jj_stack/stack/path.py,sha256=TpjfV2v49-YEFW5AfZuTnPVY7R1QGo0U5fKoUGBfdj4,8145
|
|
73
|
+
jj_stack/stack/pr_branches.py,sha256=NCEyjWhpx6YUlAqCukzkZywDFY098P_8KE62c4eaihg,4229
|
|
74
|
+
jj_stack/stack/pr_facts.py,sha256=efbUaHQIluKpKAfttCzcgdtdBkO4Kc0iQ2Nty7j4MLM,6059
|
|
75
|
+
jj_stack/stack/repo.py,sha256=iY_TuaKcTm71rxyAgeUAQgDRZlJ2FSz9Poir1hNXObs,3699
|
|
76
|
+
jj_stack/stack/selected.py,sha256=n53M8o4HwF9qliBd92NgVoU0n_CLn8gvSMJjmu-bCrc,11741
|
|
77
|
+
jj_stack/stack/selection.py,sha256=XxHnw1VFPPXzr-iNZGJgWtLRdG1L1a_QAoCz_7OmXSE,3784
|
|
78
|
+
jj_stack/stack/status.py,sha256=bf_y_a4a5nJLB93tJwx_B7MoCUjQHHdj9fmFA_y9zTk,23199
|
|
79
|
+
jj_stack/stack/trunk_evidence.py,sha256=o9agVfp2ZS9BW2abgtOTb-Hk6ETL5gzIXcNQC929UBc,6003
|
|
80
|
+
jj_stack/state/__init__.py,sha256=_DS82nymsX27tydWjlq0Pge_QUTqF29tlhAxwH-18Vs,38
|
|
81
|
+
jj_stack/state/operation_lock.py,sha256=j4dp19q9z6aTYCRVUuEIhzVJiAznIXTNBNcq5CJVbas,6180
|
|
82
|
+
jj_stack/state/store.py,sha256=WzVQKaN8K74_FMbw9w6Hfi4GznNvoEZIKbwvXGWpLzI,9259
|
|
83
|
+
jj_stack-0.1.0.dist-info/METADATA,sha256=dcszfiLdcvNGjkpmW4o_Rr_gdhgmF2ceD6-3dSHxASo,6052
|
|
84
|
+
jj_stack-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
85
|
+
jj_stack-0.1.0.dist-info/entry_points.txt,sha256=bl_q9c4duNoHqZM-mtN-3ln-W5E7rD6-b_MUMSgVst0,47
|
|
86
|
+
jj_stack-0.1.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
87
|
+
jj_stack-0.1.0.dist-info/licenses/NOTICE,sha256=getthGQb0vFKoS8T4m7GGkTcHhztrzjT71PusRrVjBc,41
|
|
88
|
+
jj_stack-0.1.0.dist-info/RECORD,,
|