diffbroker 0.15.0 → 0.16.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.
- checksums.yaml +4 -4
- data/lib/diffbroker/login.rb +32 -8
- data/lib/diffbroker/version.rb +1 -1
- data/lib/diffbroker/worktree.rb +12 -1
- data/plugin/.claude-plugin/plugin.json +1 -1
- data/plugin/skills/diffbroker-explain/SKILL.md +3 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 19527d7bf5bd6af3f94eec7673a0b60ebedc3080adc1308ba23e477b874af8f1
|
|
4
|
+
data.tar.gz: 1a29bde8db351846148a7832d57f6cd2736e3d88941267f21a2699a05a982131
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cbf4077b51bd7a4dca8cb9ef8eafc53afe3ce0c2068d954f565e7b9d76cb524350b0c4254c417456e4dc0d8dcd0554ae895767cfcce46b9f4f4abe31a5d5222a
|
|
7
|
+
data.tar.gz: df450cc45d5d36007c9f629e1c6b296425c49510240e652e89593fb0c02181d4f0547b27d423bfec70a7c381b80ba4c56551fea8523eaa28447669e5d392026c
|
data/lib/diffbroker/login.rb
CHANGED
|
@@ -9,14 +9,28 @@ module Diffbroker
|
|
|
9
9
|
system(Diffbroker.platform == "macos" ? "open" : "xdg-open", url, out: File::NULL, err: File::NULL)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
# Best effort: true when a clipboard tool took the text.
|
|
13
|
+
def self.copy_to_clipboard(text)
|
|
14
|
+
tool = case Diffbroker.platform
|
|
15
|
+
when "macos" then %w[pbcopy]
|
|
16
|
+
else [ %w[wl-copy], %w[xclip -selection clipboard], %w[xsel --clipboard --input] ].find { system("which", _1.first, out: File::NULL, err: File::NULL) }
|
|
17
|
+
end
|
|
18
|
+
tool ? IO.popen(tool, "w") { _1.write(text) } && $?.success? : false
|
|
19
|
+
rescue SystemCallError
|
|
20
|
+
false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(config:, api: Api.new(server: config.server), out: $stdout, name: Socket.gethostname, input: $stdin,
|
|
24
|
+
sleeper: ->(seconds) { sleep(seconds) }, opener: ->(url) { Login.open_browser(url) },
|
|
25
|
+
copier: ->(text) { Login.copy_to_clipboard(text) }, clock: -> { Time.now })
|
|
14
26
|
@config = config
|
|
15
27
|
@api = api
|
|
16
28
|
@out = out
|
|
29
|
+
@input = input
|
|
17
30
|
@name = name
|
|
18
31
|
@sleeper = sleeper
|
|
19
32
|
@opener = opener
|
|
33
|
+
@copier = copier
|
|
20
34
|
@clock = clock
|
|
21
35
|
end
|
|
22
36
|
|
|
@@ -24,20 +38,30 @@ module Diffbroker
|
|
|
24
38
|
auth = @api.post("/device_authorizations", { client_name: @name, platform: Diffbroker.platform })
|
|
25
39
|
code = auth.fetch("user_code")
|
|
26
40
|
uri = auth.fetch("verification_uri")
|
|
27
|
-
# No pre-filled link: nothing in a URL may select a code, the user types it on the plain page. The
|
|
28
|
-
#
|
|
41
|
+
# No pre-filled link: nothing in a URL may select a code, the user types (or pastes) it on the plain page. The browser
|
|
42
|
+
# opens only when the user presses Enter, never by itself; without a terminal it does not open at all.
|
|
29
43
|
@out.puts "Pairing #{@name} with Diff Broker."
|
|
30
44
|
@out.puts
|
|
31
|
-
@out.puts "
|
|
45
|
+
@out.puts @copier.call(code) ? "! One-time code (#{code}) copied to clipboard" : "! One-time code: #{code}"
|
|
32
46
|
@out.puts
|
|
33
|
-
|
|
47
|
+
if interactive?
|
|
48
|
+
@out.puts "Press Enter to open #{uri} in your browser, press Pair, then check the device details and press Approve."
|
|
49
|
+
enter = Thread.new { @opener.call(uri) if @input.gets }
|
|
50
|
+
else
|
|
51
|
+
@out.puts "Open #{uri}, enter the code, press Pair, then check the device details and press Approve."
|
|
52
|
+
end
|
|
34
53
|
@out.puts "Only enter a code you see here, in your own terminal."
|
|
35
|
-
|
|
36
|
-
|
|
54
|
+
begin
|
|
55
|
+
poll(auth)
|
|
56
|
+
ensure
|
|
57
|
+
enter&.kill
|
|
58
|
+
end
|
|
37
59
|
end
|
|
38
60
|
|
|
39
61
|
private
|
|
40
62
|
|
|
63
|
+
def interactive? = @input.respond_to?(:tty?) && @input.tty?
|
|
64
|
+
|
|
41
65
|
def poll(auth)
|
|
42
66
|
interval = auth.fetch("interval", 5).to_i
|
|
43
67
|
deadline = @clock.call + auth.fetch("expires_in", 600).to_i
|
data/lib/diffbroker/version.rb
CHANGED
data/lib/diffbroker/worktree.rb
CHANGED
|
@@ -25,7 +25,7 @@ module Diffbroker
|
|
|
25
25
|
dir = File.join(@root, full_name.downcase.tr("/", "-"), "pr-#{number}-#{kind}")
|
|
26
26
|
|
|
27
27
|
remote = GitContext.remote_for(repo_path, full_name)
|
|
28
|
-
|
|
28
|
+
fetch_pull!(repo_path, remote, full_name, number)
|
|
29
29
|
unless @runner.git_success?(repo_path, "cat-file", "-e", "#{head_sha}^{commit}")
|
|
30
30
|
raise Error, "Head #{head_sha[0, 7]} is not in the fetched PR; the PR may have moved. Start the task again from the card."
|
|
31
31
|
end
|
|
@@ -105,6 +105,17 @@ module Diffbroker
|
|
|
105
105
|
@runner.git_success?(repo_path, "branch", "--quiet", "--unset-upstream", local)
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
+
# git's refusal is several lines and the server keeps only the last, so it becomes one line: what was fetched, git's own
|
|
109
|
+
# words and the command that shows the same failure in a terminal. The usual cause is the background service lacking the
|
|
110
|
+
# credentials (SSH key, credential helper) this machine's terminal has.
|
|
111
|
+
def fetch_pull!(repo_path, remote, full_name, number)
|
|
112
|
+
@runner.git(repo_path, "fetch", "--quiet", remote, "+refs/pull/#{number}/head:refs/diffbroker/pr-#{number}")
|
|
113
|
+
rescue Error => e
|
|
114
|
+
reason = e.message.sub(/\A.*? failed: /m, "").split.join(" ")
|
|
115
|
+
raise Error, "Could not fetch #{full_name}##{number} from #{remote} (#{reason}). " \
|
|
116
|
+
"Check that `git -C #{repo_path} fetch #{remote}` works on this computer, then `diffbroker restart`."
|
|
117
|
+
end
|
|
118
|
+
|
|
108
119
|
def validate!(full_name:, number:, head_sha:, kind:)
|
|
109
120
|
raise Error, "Invalid head SHA #{head_sha.inspect}" unless head_sha.to_s.match?(SHA)
|
|
110
121
|
raise Error, "Invalid PR number #{number.inspect}" unless number.to_s.match?(/\A[1-9]\d{0,9}\z/)
|
|
@@ -114,7 +114,9 @@ What a newcomer needs first …
|
|
|
114
114
|
- 1–5 questions in the quiz section, each with a unique `key` (letters, digits, `_` or `-`), unique across quiz and delta too.
|
|
115
115
|
- 2–5 options, **exactly one** `[x]`. Every option has a one-line explanation after ` | ` that says why it is right or wrong.
|
|
116
116
|
- The question needs the **substance** of the change: behaviour, a consequence, a reason, a failure mode. No trivia (file names, line counts, author names), no trick wording, no "all of the above".
|
|
117
|
-
- Options are
|
|
117
|
+
- Options are comparable in length, grammar, specificity and confidence, so the right one doesn't stand out. Readers (and models) pick the longest, most qualified option, so never make the right one conspicuously longer or more precise than the wrong ones: shorten it, or add the same kind of detail to the wrong ones. Diff Broker refuses a question whose right option is much longer than every other. Diff Broker shuffles the order, so don't try to place the right answer yourself; just don't write options in a habitual order either.
|
|
118
|
+
- Every wrong option is plausible and tied to a real misunderstanding of the change. No joke answers, impossible claims or "all/none of the above".
|
|
119
|
+
- Nothing before the answer is chosen gives it away: no hint in the question's wording, no option that repeats a phrase from the question while the others don't.
|
|
118
120
|
- Readers pass at 80% on first tries. A question the verifier marks `unsupported` (the code contradicts the marked answer or does not show it) is not scored, so make each answer provable from the code. `uncertain` only shows as a caveat; the question stays scored.
|
|
119
121
|
|
|
120
122
|
## Style
|