smart_box 0.1.1 → 0.1.3

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: fdd527c54db2a72e0d40528a4fb248a712f06a8f28bd11e7b81c995963e1a4e1
4
- data.tar.gz: b277525806bbccc5ad7a979733d0e9fcaa7ad483f8c8910e5d5105ea78f94712
3
+ metadata.gz: f8f0e87dfaff0710f069764678ec19a953f4c36af7e922f8dada50d7fd8f8f45
4
+ data.tar.gz: 5cd61b69354b74a0e164c95f5d4cfa62f7939f51588e03bf25e599308c81f950
5
5
  SHA512:
6
- metadata.gz: 61675f220c4dda1cae98b9091201057351b9abd65dfe4d4c4c906aa4be4e926e0091619079034a88fdb8209c17e7fc434014b432bb8397b869fcddd413c40ca2
7
- data.tar.gz: b14f1b4116d23c01e540eac1275a5048e8decc279034ee33fde8f5b374314402fb2ec0a291a20762cef9e553a1c992425bdb9c19509f2b7bc51ec21371b8c268
6
+ metadata.gz: 0b988ffef7e71110778cdcba10131ff4d306185d307019a719bd94ed3e2218013b2290cb349df1879f2f337cedd8fa5322028dec85f7614e0b7686ca351139d6
7
+ data.tar.gz: b7f739bde88aa83ff2170cf2c7ec1bd13228663452dbde2737443b0058d5d17a2fdc4d05bb03cbfbce5f1092d7390dd40fdeb693fa466f08ea43999b959134cf
data/CHANGELOG.md ADDED
@@ -0,0 +1,45 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.3] - 2026-09-14
9
+
10
+ ### Changed
11
+
12
+ - Point gemspec homepage/source/changelog/bug-tracker URIs at the real
13
+ repository (github.com/zhuangbiaowei/smart_box).
14
+ - Ship the MIT `LICENSE` and `CHANGELOG.md` in the gem.
15
+
16
+ ## [0.1.2] - 2026-09-14
17
+
18
+ ### Fixed
19
+
20
+ - Eliminate process-wide `Dir.chdir` conflicts. All parent-process `Dir.chdir`
21
+ blocks are replaced with subprocess-level `chdir:` options, so Ruby can no
22
+ longer raise `conflicting chdir during another chdir block` when boxes run in
23
+ background threads.
24
+ - Enforce command timeouts. When a `timeout` is set, commands now run via
25
+ `Open3.popen3(pgroup: true)` and the whole process group is terminated
26
+ (TERM, grace period, then KILL) on expiry, returning exit code 124.
27
+
28
+ ## [0.1.1] - 2026-09-13
29
+
30
+ ### Fixed
31
+
32
+ - Use `require_relative` in `cli.rb` to avoid gem conflict warnings.
33
+
34
+ ## [0.1.0] - 2026-09-13
35
+
36
+ ### Added
37
+
38
+ - Initial MVP: copy and git-worktree modes, command execution with dangerous
39
+ command detection and logging, checkpoints, rollback, diff, export-patch,
40
+ apply, and discard.
41
+
42
+ [0.1.3]: https://github.com/zhuangbiaowei/smart_box/compare/v0.1.2...v0.1.3
43
+ [0.1.2]: https://github.com/zhuangbiaowei/smart_box/compare/v0.1.1...v0.1.2
44
+ [0.1.1]: https://github.com/zhuangbiaowei/smart_box/compare/v0.1.0...v0.1.1
45
+ [0.1.0]: https://github.com/zhuangbiaowei/smart_box/releases/tag/v0.1.0
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SmartBox Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/lib/smart_box/box.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "fileutils"
4
4
  require "time"
5
+ require "open3"
5
6
  require_relative "errors"
6
7
  require_relative "metadata"
7
8
  require_relative "modes/copy_mode"
@@ -127,12 +128,11 @@ module SmartBox
127
128
  def git_status_short
128
129
  return [] unless Dir.exist?(@workspace_path)
129
130
 
130
- Dir.chdir(@workspace_path) do
131
- out = `git status --porcelain 2>/dev/null`
132
- return [] unless $?.success?
131
+ out, _err, status = Open3.capture3("git", "status", "--porcelain",
132
+ chdir: @workspace_path)
133
+ return [] unless status.success?
133
134
 
134
- out.lines.map(&:chomp).reject(&:empty?)
135
- end
135
+ out.lines.map(&:chomp).reject(&:empty?)
136
136
  end
137
137
 
138
138
  def run(command, env: {}, timeout: nil, allow_dangerous: false)
@@ -149,10 +149,9 @@ module SmartBox
149
149
  def checkpoint(name)
150
150
  raise Error, "Workspace does not exist" unless Dir.exist?(@workspace_path)
151
151
 
152
- Dir.chdir(@workspace_path) do
153
- system("git", "add", "-A", out: File::NULL, err: File::NULL)
154
- system("git", "commit", "--allow-empty", "-m", name, out: File::NULL, err: File::NULL)
155
- end
152
+ system("git", "add", "-A", chdir: @workspace_path, out: File::NULL, err: File::NULL)
153
+ system("git", "commit", "--allow-empty", "-m", name,
154
+ chdir: @workspace_path, out: File::NULL, err: File::NULL)
156
155
 
157
156
  commit = git_latest_commit
158
157
  cp_id = "cp-#{format('%03d', (@metadata.checkpoints.size + 1))}"
@@ -181,10 +180,10 @@ module SmartBox
181
180
  cp = @metadata.checkpoints.detect { |c| c["id"] == checkpoint_id }
182
181
  raise CheckpointNotFoundError, "Checkpoint '#{checkpoint_id}' not found" unless cp
183
182
 
184
- Dir.chdir(@workspace_path) do
185
- system("git", "reset", "--hard", cp["git_commit"], out: File::NULL, err: File::NULL)
186
- system("git", "clean", "-fd", out: File::NULL, err: File::NULL)
187
- end
183
+ system("git", "reset", "--hard", cp["git_commit"],
184
+ chdir: @workspace_path, out: File::NULL, err: File::NULL)
185
+ system("git", "clean", "-fd",
186
+ chdir: @workspace_path, out: File::NULL, err: File::NULL)
188
187
 
189
188
  @metadata.load!
190
189
  @metadata.updated_at = Time.now.utc.iso8601
@@ -196,25 +195,25 @@ module SmartBox
196
195
  def diff(from: nil, to: nil)
197
196
  raise Error, "Workspace does not exist" unless Dir.exist?(@workspace_path)
198
197
 
199
- Dir.chdir(@workspace_path) do
200
- # Stage everything so untracked files appear in diff
201
- system("git", "add", "-A", out: File::NULL, err: File::NULL)
198
+ system("git", "add", "-A", chdir: @workspace_path, out: File::NULL, err: File::NULL)
202
199
 
203
- from_commit = if from
204
- resolve_checkpoint_commit(from)
205
- else
206
- init = @metadata.checkpoints.first
207
- init ? init["git_commit"] : "HEAD~1"
208
- end
200
+ from_commit = if from
201
+ resolve_checkpoint_commit(from)
202
+ else
203
+ init = @metadata.checkpoints.first
204
+ init ? init["git_commit"] : "HEAD~1"
205
+ end
209
206
 
210
- to_commit = to ? resolve_checkpoint_commit(to) : nil
207
+ to_commit = to ? resolve_checkpoint_commit(to) : nil
211
208
 
212
- if to_commit
213
- `git diff #{from_commit} #{to_commit} 2>/dev/null`
214
- else
215
- `git diff --cached #{from_commit} 2>/dev/null`
216
- end
209
+ if to_commit
210
+ out, _err, _st = Open3.capture3("git", "diff", from_commit, to_commit,
211
+ chdir: @workspace_path)
212
+ else
213
+ out, _err, _st = Open3.capture3("git", "diff", "--cached", from_commit,
214
+ chdir: @workspace_path)
217
215
  end
216
+ out.to_s
218
217
  end
219
218
 
220
219
  def export_patch(output:, from: nil, to: nil)
@@ -249,36 +248,29 @@ module SmartBox
249
248
  backup_patch_path = File.join(@box_dir, "patches", "backup.patch")
250
249
  FileUtils.mkdir_p(File.join(@box_dir, "patches"))
251
250
 
252
- Dir.chdir(@source_path) do
253
- backup = `git diff 2>/dev/null`
254
- File.write(backup_patch_path, backup) unless backup.empty?
255
- end
251
+ backup, _err, _st = Open3.capture3("git", "diff", chdir: @source_path)
252
+ File.write(backup_patch_path, backup) unless backup.empty?
256
253
 
257
254
  # Apply the patch
258
- Dir.chdir(@source_path) do
259
- IO.popen(["git", "apply", "-v"], "w") do |io|
260
- io.write(patch_content)
261
- end
262
-
263
- unless $?.success?
264
- raise PatchApplyError, "Failed to apply patch. The source project may have conflicts."
265
- end
266
-
267
- # Show what changed
268
- result_diff = `git diff 2>/dev/null`
269
- result_diff
255
+ _apply_out, _apply_err, status =
256
+ Open3.capture3("git", "apply", "-v",
257
+ stdin_data: patch_content, chdir: @source_path)
258
+
259
+ unless status.success?
260
+ raise PatchApplyError, "Failed to apply patch. The source project may have conflicts."
270
261
  end
262
+
263
+ # Show what changed
264
+ result_diff, _err2, _st2 = Open3.capture3("git", "diff", chdir: @source_path)
265
+ result_diff
271
266
  else
272
267
  # Non-git source: apply patch with patch command
273
268
  backup_patch_path = File.join(@box_dir, "patches", "backup.diff")
274
269
  FileUtils.mkdir_p(File.join(@box_dir, "patches"))
275
270
  File.write(backup_patch_path, "backup not available for non-git source")
276
271
 
277
- Dir.chdir(@source_path) do
278
- IO.popen(["patch", "-p1", "-N", "-r", "/dev/null"], "w") do |io|
279
- io.write(patch_content)
280
- end
281
- end
272
+ Open3.capture3("patch", "-p1", "-N", "-r", "/dev/null",
273
+ stdin_data: patch_content, chdir: @source_path)
282
274
 
283
275
  "Patch applied to non-git source at #{@source_path}"
284
276
  end
@@ -299,7 +291,10 @@ module SmartBox
299
291
 
300
292
  def source_clean?
301
293
  return true unless Dir.exist?(File.join(@source_path, ".git"))
302
- Dir.chdir(@source_path) { `git status --porcelain 2>/dev/null`.strip.empty? }
294
+
295
+ out, _err, _st = Open3.capture3("git", "status", "--porcelain",
296
+ chdir: @source_path)
297
+ out.strip.empty?
303
298
  end
304
299
 
305
300
  private
@@ -338,27 +333,27 @@ module SmartBox
338
333
  # wrongly return "none". File.exist? is true for both files and dirs.
339
334
  return "none" unless File.exist?(File.join(@workspace_path, ".git"))
340
335
 
341
- Dir.chdir(@workspace_path) do
342
- `git rev-parse HEAD 2>/dev/null`.strip
343
- end
336
+ out, _err, _st = Open3.capture3("git", "rev-parse", "HEAD",
337
+ chdir: @workspace_path)
338
+ out.strip
344
339
  end
345
340
 
346
341
  def source_git_commit
347
342
  git_dir = File.join(@source_path, ".git")
348
343
  return "none" unless Dir.exist?(git_dir)
349
344
 
350
- Dir.chdir(@source_path) do
351
- `git rev-parse HEAD 2>/dev/null`.strip
352
- end
345
+ out, _err, _st = Open3.capture3("git", "rev-parse", "HEAD",
346
+ chdir: @source_path)
347
+ out.strip
353
348
  end
354
349
 
355
350
  def source_git_branch
356
351
  git_dir = File.join(@source_path, ".git")
357
352
  return "none" unless Dir.exist?(git_dir)
358
353
 
359
- Dir.chdir(@source_path) do
360
- `git rev-parse --abbrev-ref HEAD 2>/dev/null`.strip
361
- end
354
+ out, _err, _st = Open3.capture3("git", "rev-parse", "--abbrev-ref", "HEAD",
355
+ chdir: @source_path)
356
+ out.strip
362
357
  end
363
358
 
364
359
  def resolve_checkpoint_commit(checkpoint_id)
@@ -93,13 +93,14 @@ module SmartBox
93
93
  end
94
94
 
95
95
  def init_git!
96
- Dir.chdir(@workspace_path) do
97
- system("git", "init", out: File::NULL, err: File::NULL)
98
- system("git", "config", "user.email", "smart_box@localhost", out: File::NULL, err: File::NULL)
99
- system("git", "config", "user.name", "smart_box", out: File::NULL, err: File::NULL)
100
- system("git", "add", "-A", out: File::NULL, err: File::NULL)
101
- system("git", "commit", "-m", "smart_box initial checkpoint", out: File::NULL, err: File::NULL)
102
- end
96
+ system("git", "init", chdir: @workspace_path, out: File::NULL, err: File::NULL)
97
+ system("git", "config", "user.email", "smart_box@localhost",
98
+ chdir: @workspace_path, out: File::NULL, err: File::NULL)
99
+ system("git", "config", "user.name", "smart_box",
100
+ chdir: @workspace_path, out: File::NULL, err: File::NULL)
101
+ system("git", "add", "-A", chdir: @workspace_path, out: File::NULL, err: File::NULL)
102
+ system("git", "commit", "-m", "smart_box initial checkpoint",
103
+ chdir: @workspace_path, out: File::NULL, err: File::NULL)
103
104
  end
104
105
  end
105
106
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "open3"
4
+
3
5
  module SmartBox
4
6
  module Modes
5
7
  class GitWorktreeMode
@@ -46,25 +48,23 @@ module SmartBox
46
48
  end
47
49
 
48
50
  def source_clean?
49
- Dir.chdir(@source_path) do
50
- `git status --porcelain 2>/dev/null`.strip.empty?
51
- end
51
+ out, _err, _st = Open3.capture3("git", "status", "--porcelain",
52
+ chdir: @source_path)
53
+ out.strip.empty?
52
54
  end
53
55
 
54
56
  def ensure_branch!
55
- Dir.chdir(@source_path) do
56
- branches = `git branch --list #{@branch_name} 2>/dev/null`.strip
57
- if branches.empty?
58
- system("git", "branch", @branch_name, out: File::NULL, err: File::NULL)
59
- end
57
+ branches, _err, _st = Open3.capture3("git", "branch", "--list", @branch_name,
58
+ chdir: @source_path)
59
+ if branches.strip.empty?
60
+ system("git", "branch", @branch_name,
61
+ chdir: @source_path, out: File::NULL, err: File::NULL)
60
62
  end
61
63
  end
62
64
 
63
65
  def create_worktree!
64
- Dir.chdir(@source_path) do
65
- system("git", "worktree", "add", @workspace_path, @branch_name,
66
- out: File::NULL, err: File::NULL)
67
- end
66
+ system("git", "worktree", "add", @workspace_path, @branch_name,
67
+ chdir: @source_path, out: File::NULL, err: File::NULL)
68
68
 
69
69
  unless Dir.exist?(@workspace_path)
70
70
  raise SmartBox::Error, "Failed to create git worktree at #{@workspace_path}"
@@ -72,27 +72,22 @@ module SmartBox
72
72
  end
73
73
 
74
74
  def init_checkpoint!
75
- Dir.chdir(@workspace_path) do
76
- system("git", "add", "-A", out: File::NULL, err: File::NULL)
77
- system("git", "commit", "--allow-empty", "-m", "smart_box initial checkpoint",
78
- out: File::NULL, err: File::NULL)
79
- end
75
+ system("git", "add", "-A", chdir: @workspace_path, out: File::NULL, err: File::NULL)
76
+ system("git", "commit", "--allow-empty", "-m", "smart_box initial checkpoint",
77
+ chdir: @workspace_path, out: File::NULL, err: File::NULL)
80
78
  end
81
79
 
82
80
  def remove_worktree!
83
- Dir.chdir(@source_path) do
84
- system("git", "worktree", "remove", @workspace_path, "--force",
85
- out: File::NULL, err: File::NULL)
86
- system("git", "branch", "-D", @branch_name,
87
- out: File::NULL, err: File::NULL)
88
- end
81
+ system("git", "worktree", "remove", @workspace_path, "--force",
82
+ chdir: @source_path, out: File::NULL, err: File::NULL)
83
+ system("git", "branch", "-D", @branch_name,
84
+ chdir: @source_path, out: File::NULL, err: File::NULL)
89
85
  end
90
86
 
91
87
  def worktree_exists?
92
- Dir.chdir(@source_path) do
93
- worktrees = `git worktree list --porcelain 2>/dev/null`
94
- worktrees.include?(@workspace_path)
95
- end
88
+ worktrees, _err, _st = Open3.capture3("git", "worktree", "list", "--porcelain",
89
+ chdir: @source_path)
90
+ worktrees.include?(@workspace_path)
96
91
  end
97
92
  end
98
93
  end
@@ -4,6 +4,7 @@ require "open3"
4
4
  require "json"
5
5
  require "time"
6
6
  require "shellwords"
7
+ require "fileutils"
7
8
  require_relative "command_result"
8
9
  require_relative "errors"
9
10
 
@@ -21,6 +22,11 @@ module SmartBox
21
22
  /\bchown\s+-R\b/
22
23
  ].freeze
23
24
 
25
+ # Exit code used when a command exceeds its timeout (GNU timeout convention).
26
+ TIMEOUT_EXIT_CODE = 124
27
+ # Grace period in seconds between TERM and KILL when terminating a process group.
28
+ KILL_GRACE_SECONDS = 2
29
+
24
30
  attr_reader :box_id, :workspace_path, :logs_dir
25
31
 
26
32
  def initialize(box_id:, workspace_path:, logs_dir:)
@@ -69,17 +75,25 @@ module SmartBox
69
75
  stderr = ""
70
76
  exit_code = nil
71
77
 
72
- Dir.chdir(@workspace_path) do
73
- args = Shellwords.split(command)
74
- if env && !env.empty?
75
- stdout, stderr, status = Open3.capture3(env, *args, chdir: @workspace_path)
76
- else
77
- stdout, stderr, status = Open3.capture3(*args, chdir: @workspace_path)
78
+ args = Shellwords.split(command)
79
+ # Never switch the parent process directory: all execution is confined to
80
+ # the workspace via Open3's `chdir:` option. This avoids Ruby's
81
+ # process-wide `conflicting chdir during another chdir block` error when
82
+ # boxes run in background threads alongside other threads in the process.
83
+ if timeout && timeout.to_f > 0
84
+ stdout, stderr, exit_code = execute_with_timeout(args, env, timeout.to_f)
85
+ else
86
+ begin
87
+ if env && !env.empty?
88
+ stdout, stderr, status = Open3.capture3(env, *args, chdir: @workspace_path)
89
+ else
90
+ stdout, stderr, status = Open3.capture3(*args, chdir: @workspace_path)
91
+ end
92
+ exit_code = status.exitstatus
93
+ rescue Errno::ENOENT => e
94
+ stderr = "Command not found: #{e.message}"
95
+ exit_code = 127
78
96
  end
79
- exit_code = status.exitstatus
80
- rescue Errno::ENOENT => e
81
- stderr = "Command not found: #{e.message}"
82
- exit_code = 127
83
97
  end
84
98
 
85
99
  ended_at = Time.now
@@ -98,6 +112,55 @@ module SmartBox
98
112
  )
99
113
  end
100
114
 
115
+ # Runs a command with a hard timeout by spawning it in its own process group
116
+ # (pgroup: true) and killing the whole group on timeout. This kills child
117
+ # processes too (e.g. find/rsync), unlike Ruby's Timeout.timeout.
118
+ def execute_with_timeout(args, env, timeout_s)
119
+ popen_args = env && !env.empty? ? [env, *args] : args
120
+ Open3.popen3(*popen_args, chdir: @workspace_path, pgroup: true) do |stdin, out, err, wait_thr|
121
+ stdin.close
122
+
123
+ # Drain stdout/stderr concurrently to avoid pipe buffer deadlocks.
124
+ out_reader = Thread.new { out.read }
125
+ err_reader = Thread.new { err.read }
126
+
127
+ waiter = Thread.new { wait_thr.join }
128
+ timed_out = false
129
+ unless waiter.join(timeout_s)
130
+ timed_out = true
131
+ kill_process_group!(wait_thr)
132
+ end
133
+ waiter.kill if waiter.alive?
134
+
135
+ stdout = out_reader.value
136
+ stderr = err_reader.value
137
+ exit_code = timed_out ? TIMEOUT_EXIT_CODE : wait_thr.value.exitstatus
138
+
139
+ if timed_out
140
+ marker = "[timeout] command exceeded #{format('%g', timeout_s)}s and was killed (exit #{TIMEOUT_EXIT_CODE})"
141
+ stderr = stderr.to_s.empty? ? marker : "#{stderr.rstrip}\n#{marker}"
142
+ end
143
+
144
+ [stdout, stderr, exit_code]
145
+ end
146
+ rescue Errno::ENOENT => e
147
+ ["", "Command not found: #{e.message}", 127]
148
+ end
149
+
150
+ # Sends TERM to the whole process group, waits a grace period, then KILL.
151
+ def kill_process_group!(wait_thr)
152
+ %w[TERM KILL].each_with_index do |signal, idx|
153
+ break unless wait_thr.alive?
154
+
155
+ begin
156
+ Process.kill(signal, -wait_thr.pid)
157
+ rescue Errno::ESRCH, Errno::EPERM
158
+ break # Process group is already gone (or we lack permission).
159
+ end
160
+ wait_thr.join(KILL_GRACE_SECONDS) if idx.zero?
161
+ end
162
+ end
163
+
101
164
  def write_logs(command, result, idx)
102
165
  write_human_log(command, result)
103
166
  write_jsonl_log(command, result, idx)
@@ -1,3 +1,3 @@
1
1
  module SmartBox
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.3"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_box
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - SmartBox Team
@@ -19,6 +19,8 @@ executables:
19
19
  extensions: []
20
20
  extra_rdoc_files: []
21
21
  files:
22
+ - CHANGELOG.md
23
+ - LICENSE
22
24
  - README.md
23
25
  - bin/smart_box
24
26
  - lib/smart_box.rb
@@ -31,11 +33,13 @@ files:
31
33
  - lib/smart_box/modes/git_worktree_mode.rb
32
34
  - lib/smart_box/runner.rb
33
35
  - lib/smart_box/version.rb
36
+ homepage: https://github.com/zhuangbiaowei/smart_box
34
37
  licenses:
35
38
  - MIT
36
39
  metadata:
37
- source_code_uri: https://github.com/smart-box/smart_box
38
- changelog_uri: https://github.com/smart-box/smart_box/blob/main/CHANGELOG.md
40
+ source_code_uri: https://github.com/zhuangbiaowei/smart_box
41
+ changelog_uri: https://github.com/zhuangbiaowei/smart_box/blob/master/CHANGELOG.md
42
+ bug_tracker_uri: https://github.com/zhuangbiaowei/smart_box/issues
39
43
  rdoc_options: []
40
44
  require_paths:
41
45
  - lib
@@ -50,7 +54,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
50
54
  - !ruby/object:Gem::Version
51
55
  version: '0'
52
56
  requirements: []
53
- rubygems_version: 4.0.13
57
+ rubygems_version: 4.0.16
54
58
  specification_version: 4
55
59
  summary: A local reversible sandbox system for agent task execution
56
60
  test_files: []