jj-octo 0.4.0 → 0.5.0
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.
- package/README.md +22 -8
- package/dist/jj-octo.js +35 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# jj-octo
|
|
2
2
|
|
|
3
|
-
A [jj](https://github.com/jj-vcs/jj) subcommand for
|
|
3
|
+
A [jj](https://github.com/jj-vcs/jj) subcommand for GitHub that supports stacked pull requests and works in Claude Code cloud sessions.
|
|
4
4
|
|
|
5
5
|
> [!WARNING]
|
|
6
6
|
> `jj-octo` is in early development, and its commands may change. So far it is largely vibe coded.
|
|
@@ -30,13 +30,27 @@ Then run `jj-octo init` once in each checkout. It makes the checkout a jj reposi
|
|
|
30
30
|
|
|
31
31
|
## GitHub access
|
|
32
32
|
|
|
33
|
-
`jj-octo` reads the repository from the `origin` remote.
|
|
33
|
+
`jj-octo` reads the repository from the `origin` remote.
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
By default, it reads its token from `GH_TOKEN` or `GITHUB_TOKEN`, and otherwise runs `gh auth token`.
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
With `jj-octo.github-auth` set to `git-proxy`, it sends a placeholder token instead. It sends requests through git's proxy for github.com, and trusts git's certificate authorities for it in place of Bun's own. [In a self-hosted environment](#in-a-self-hosted-environment) describes when to use it.
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
## In Claude Code cloud sessions
|
|
40
|
+
|
|
41
|
+
A Claude Code [cloud session](https://code.claude.com/docs/en/claude-code-on-the-web) runs in an Anthropic-hosted environment, or in your organization's self-hosted environment. Each needs its own setup.
|
|
42
|
+
|
|
43
|
+
The session's GitHub proxy can append a "Generated by Claude Code" footer to each pull request body that it creates or edits. `jj gh submit` would read the footer as an edit made on GitHub, and write it into the change. To have `submit` leave the footer out when it compares a pull request's body with its change's description, set:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
jj config set --repo jj-octo.ignore-claude-code-footer true
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### In an Anthropic-hosted environment
|
|
50
|
+
|
|
51
|
+
There, the [GitHub proxy](https://code.claude.com/docs/en/cloud-environments#github-proxy) carries all GitHub traffic. It serves GitHub API requests only for the repositories attached to the session, and rejects GraphQL requests. That's why `jj-octo` uses only GitHub's REST API, while `gh pr` and other tools that depend on GraphQL fail there.
|
|
52
|
+
|
|
53
|
+
mise reads this repository's releases through the GitHub API, so it can't install them unless the session has this repository attached. Install the npm package instead:
|
|
40
54
|
|
|
41
55
|
```toml
|
|
42
56
|
[tools]
|
|
@@ -48,11 +62,11 @@ version = "X.Y.Z"
|
|
|
48
62
|
allow_low_downloads = true
|
|
49
63
|
```
|
|
50
64
|
|
|
51
|
-
The session sets `HTTPS_PROXY`, `NODE_EXTRA_CA_CERTS
|
|
65
|
+
The session sets `HTTPS_PROXY`, `NODE_EXTRA_CA_CERTS`, and `GH_TOKEN` to the placeholder `proxy-injected`. The GitHub proxy replaces the placeholder with your real credentials. So `jj-octo` works there with its default settings.
|
|
52
66
|
|
|
53
|
-
|
|
67
|
+
### In a self-hosted environment
|
|
54
68
|
|
|
55
|
-
A self-hosted runner started with `--use-anthropic-git-proxy` sends git through Anthropic
|
|
69
|
+
A [self-hosted runner](https://code.claude.com/docs/en/self-hosted-environments-deploy#use-the-anthropic-git-proxy) started with `--use-anthropic-git-proxy` sends git through the Anthropic git proxy. That proxy adds the GitHub credentials of the session's creator. The session then holds no GitHub token, and only git's config names the proxy. There, have `jj-octo` use the same proxy:
|
|
56
70
|
|
|
57
71
|
```
|
|
58
72
|
jj config set --repo jj-octo.github-auth git-proxy
|
package/dist/jj-octo.js
CHANGED
|
@@ -18239,8 +18239,8 @@ var layer14 = /* @__PURE__ */ layerMergedContext(/* @__PURE__ */ succeed6(fetch)
|
|
|
18239
18239
|
// package.json
|
|
18240
18240
|
var package_default = {
|
|
18241
18241
|
name: "jj-octo",
|
|
18242
|
-
version: "0.
|
|
18243
|
-
description: "A jj subcommand for
|
|
18242
|
+
version: "0.5.0",
|
|
18243
|
+
description: "A jj subcommand for GitHub that supports stacked pull requests and works in Claude Code cloud sessions",
|
|
18244
18244
|
license: "MIT",
|
|
18245
18245
|
repository: {
|
|
18246
18246
|
type: "git",
|
|
@@ -18839,9 +18839,13 @@ var descriptionFrom = ({
|
|
|
18839
18839
|
}) => body === "" ? title : `${title}
|
|
18840
18840
|
|
|
18841
18841
|
${body}`;
|
|
18842
|
-
var
|
|
18842
|
+
var claudeCodeFooter = /\n*---\n_Generated by \[Claude Code\]\(https:\/\/claude\.ai\/code[^\s)]*\)_\s*$/u;
|
|
18843
|
+
var normalizedDescription = (description, { ignoreClaudeCodeFooter }) => {
|
|
18844
|
+
const text = description.replaceAll(`\r
|
|
18843
18845
|
`, `
|
|
18844
|
-
`)
|
|
18846
|
+
`);
|
|
18847
|
+
return descriptionFrom(titleAndBody(ignoreClaudeCodeFooter ? text.replace(claudeCodeFooter, "") : text));
|
|
18848
|
+
};
|
|
18845
18849
|
var descriptionSyncFor = ({
|
|
18846
18850
|
local,
|
|
18847
18851
|
onGitHub,
|
|
@@ -18859,18 +18863,39 @@ var descriptionSyncFor = ({
|
|
|
18859
18863
|
// src/cli/gh/stack/descriptions.ts
|
|
18860
18864
|
class DescriptionConflict extends TaggedError2("DescriptionConflict") {
|
|
18861
18865
|
}
|
|
18866
|
+
var ignoreClaudeCodeFooterKey = "jj-octo.ignore-claude-code-footer";
|
|
18867
|
+
var ignoresClaudeCodeFooter = gen2(function* () {
|
|
18868
|
+
const jj = yield* Jj;
|
|
18869
|
+
const value = (yield* jj.read(["config", "get", ignoreClaudeCodeFooterKey]).pipe(orElseSucceed2(() => ""))).trim();
|
|
18870
|
+
switch (value) {
|
|
18871
|
+
case "":
|
|
18872
|
+
case "false": {
|
|
18873
|
+
return false;
|
|
18874
|
+
}
|
|
18875
|
+
case "true": {
|
|
18876
|
+
return true;
|
|
18877
|
+
}
|
|
18878
|
+
default: {
|
|
18879
|
+
return yield* new JjFailed({
|
|
18880
|
+
message: `${ignoreClaudeCodeFooterKey} is ${JSON.stringify(value)}; set it to true or false`
|
|
18881
|
+
});
|
|
18882
|
+
}
|
|
18883
|
+
}
|
|
18884
|
+
});
|
|
18862
18885
|
var descriptionsOf = (commits) => commits.length === 0 ? succeed6(new Map) : readLog(commits.join(" | "), String.raw`commit_id ++ "\t" ++ description.escape_json() ++ "\n"`).pipe(map7((output) => new Map(lines2(output).map((line) => {
|
|
18863
18886
|
const [commit = "", json = '""'] = line.split("\t");
|
|
18864
18887
|
const parsed = JSON.parse(json);
|
|
18865
18888
|
return [commit, typeof parsed === "string" ? parsed : ""];
|
|
18866
18889
|
}))));
|
|
18867
|
-
var syncDescription = ({ bookmark, description }, pullRequest, { overwriteDescriptions, pushedDescription }) => gen2(function* () {
|
|
18890
|
+
var syncDescription = ({ bookmark, description }, pullRequest, { ignoreClaudeCodeFooter, overwriteDescriptions, pushedDescription }) => gen2(function* () {
|
|
18868
18891
|
const git = yield* Git;
|
|
18869
18892
|
const jj = yield* Jj;
|
|
18870
18893
|
const describe = (text) => jj.run(["describe", "-r", bookmarkRevset(bookmark), "-m", text]);
|
|
18871
|
-
const local = normalizedDescription(description);
|
|
18872
|
-
const onGitHub = normalizedDescription(descriptionFrom(pullRequest)
|
|
18873
|
-
|
|
18894
|
+
const local = normalizedDescription(description, { ignoreClaudeCodeFooter });
|
|
18895
|
+
const onGitHub = normalizedDescription(descriptionFrom(pullRequest), {
|
|
18896
|
+
ignoreClaudeCodeFooter
|
|
18897
|
+
});
|
|
18898
|
+
const pushed = normalizedDescription(pushedDescription, { ignoreClaudeCodeFooter });
|
|
18874
18899
|
return yield* value4(descriptionSyncFor({ local, onGitHub, pushed })).pipe(tag3("InSync", () => succeed6(none2())), tag3("TakeGitHub", () => describe(onGitHub).pipe(andThen2(error(`took the description of #${pullRequest.number} from GitHub`)), as2(none2()))), tag3("SendLocal", () => succeed6(some2(local))), tag3("Merge", () => gen2(function* () {
|
|
18875
18900
|
if (overwriteDescriptions) {
|
|
18876
18901
|
return some2(local);
|
|
@@ -18920,12 +18945,14 @@ var publish = (stack, { draft, overwriteDescriptions }) => gen2(function* () {
|
|
|
18920
18945
|
const found = yield* all2(stack.map((change, index) => github.openPullRequest(change.bookmark).pipe(map7((pullRequest) => ({ base: bases[index] ?? trunk, change, pullRequest })))), { concurrency: "unbounded" });
|
|
18921
18946
|
const targets = yield* bookmarkTargets;
|
|
18922
18947
|
const pushedDescriptions = yield* descriptionsOf(bookmarks.flatMap((bookmark) => targets.get(bookmark)?.origin ?? []));
|
|
18948
|
+
const ignoreClaudeCodeFooter = yield* ignoresClaudeCodeFooter;
|
|
18923
18949
|
const changes = [];
|
|
18924
18950
|
for (const entry of found) {
|
|
18925
18951
|
const pushed = targets.get(entry.change.bookmark)?.origin;
|
|
18926
18952
|
changes.push({
|
|
18927
18953
|
...entry,
|
|
18928
18954
|
description: isNone2(entry.pullRequest) ? none2() : yield* syncDescription(entry.change, entry.pullRequest.value, {
|
|
18955
|
+
ignoreClaudeCodeFooter,
|
|
18929
18956
|
overwriteDescriptions,
|
|
18930
18957
|
pushedDescription: (pushed === undefined ? undefined : pushedDescriptions.get(pushed)) ?? ""
|
|
18931
18958
|
})
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jj-octo",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A jj subcommand for
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "A jj subcommand for GitHub that supports stacked pull requests and works in Claude Code cloud sessions",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|