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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d1d00b5898842a32e456087716d26bdb5b1c28e35f04e44b9d73acf8726d78f7
4
- data.tar.gz: 25464398637a5bcf58c4e85f8babb57134cd1d3463e0325bb445b983328c7e09
3
+ metadata.gz: 19527d7bf5bd6af3f94eec7673a0b60ebedc3080adc1308ba23e477b874af8f1
4
+ data.tar.gz: 1a29bde8db351846148a7832d57f6cd2736e3d88941267f21a2699a05a982131
5
5
  SHA512:
6
- metadata.gz: 306e519a17b426111cfe62d5ea845abd4b2c1e77534749864cb6b44cb9dd802126d85ab1025a1a89e8ba26f0ff08591f701f13c7c05aba75de2985ea629c14f6
7
- data.tar.gz: e7e2f33022fcbc9c670821bff1025709c86cd5900ba52e229150ce4847ff8403fc304888117d942e0c616256af1a6616060dd45d5fe4f0ddf041e963a3a47ed8
6
+ metadata.gz: cbf4077b51bd7a4dca8cb9ef8eafc53afe3ce0c2068d954f565e7b9d76cb524350b0c4254c417456e4dc0d8dcd0554ae895767cfcce46b9f4f4abe31a5d5222a
7
+ data.tar.gz: df450cc45d5d36007c9f629e1c6b296425c49510240e652e89593fb0c02181d4f0547b27d423bfec70a7c381b80ba4c56551fea8523eaa28447669e5d392026c
@@ -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
- def initialize(config:, api: Api.new(server: config.server), out: $stdout, name: Socket.gethostname,
13
- sleeper: ->(seconds) { sleep(seconds) }, opener: ->(url) { Login.open_browser(url) }, clock: -> { Time.now })
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 companion never
28
- # touches the clipboard.
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 " Your code: #{code}"
45
+ @out.puts @copier.call(code) ? "! One-time code (#{code}) copied to clipboard" : "! One-time code: #{code}"
32
46
  @out.puts
33
- @out.puts "Enter it at #{uri}, press Pair, then check the device details and press Approve."
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
- @opener.call(uri)
36
- poll(auth)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Diffbroker
4
- VERSION = "0.15.0"
4
+ VERSION = "0.16.0"
5
5
  end
@@ -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
- @runner.git(repo_path, "fetch", "--quiet", remote, "+refs/pull/#{number}/head:refs/diffbroker/pr-#{number}")
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/)
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "diffbroker",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Skills for working on Diff Broker agent tasks: the diffbroker MCP tools, write rules and report_task."
5
5
  }
@@ -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 similar in length and grammar, so the right one doesn't stand out. Diff Broker shuffles them.
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: diffbroker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yuri Sidorov (@newstler)