binpacker 0.1.0 → 0.3.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.
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Binpacker
4
+ # Registry for the gem-shipped, user-facing agent skills. Skills live in the
5
+ # gem's top-level skills/ directory, each as skills/<name>/SKILL.md.
6
+ # See docs/adr/0003-agent-driven-install-and-skills.md.
7
+ module Skills
8
+ ROOT = File.expand_path("../../skills", __dir__)
9
+
10
+ module_function
11
+
12
+ # [{ name:, path: }], sorted by name.
13
+ def list
14
+ Dir.glob(File.join(ROOT, "*", "SKILL.md")).sort.map do |file|
15
+ { name: File.basename(File.dirname(file)), path: file }
16
+ end
17
+ end
18
+
19
+ def exist?(name)
20
+ !path(name).nil?
21
+ end
22
+
23
+ # Absolute path of a skill's SKILL.md, or nil when unknown.
24
+ def path(name)
25
+ file = File.join(ROOT, name, "SKILL.md")
26
+ File.file?(file) ? file : nil
27
+ end
28
+
29
+ # The SKILL.md body for a skill.
30
+ def body(name)
31
+ file = path(name)
32
+ raise Error, "unknown skill: #{name}" unless file
33
+ File.read(file)
34
+ end
35
+ end
36
+ end
@@ -2,7 +2,6 @@
2
2
 
3
3
  module Binpacker
4
4
  Test = Struct.new(:file, :name, keyword_init: true) do
5
- # Composite key used as timing lookup and identity.
6
5
  def key
7
6
  [file, name]
8
7
  end
@@ -15,7 +14,6 @@ module Binpacker
15
14
  @exclude = config.test_exclude
16
15
  end
17
16
 
18
- # Returns Array<Test>. Concrete strategies subclass this.
19
17
  def enumerate
20
18
  raise NotImplementedError
21
19
  end
@@ -27,21 +25,77 @@ module Binpacker
27
25
  @exclude.any? { |ex| File.fnmatch?(ex, f) }
28
26
  }
29
27
  end
28
+
29
+ def add_project_load_paths
30
+ %w[lib test].each do |path|
31
+ expanded = File.expand_path(path)
32
+ $LOAD_PATH.unshift(expanded) if Dir.exist?(expanded) && !$LOAD_PATH.include?(expanded)
33
+ end
34
+ end
30
35
  end
31
36
 
32
37
  class RSpecDiscovery < TestDiscovery
33
38
  def enumerate
34
- glob_files.map { |f| Test.new(file: f, name: f) }
39
+ if @config.respond_to?(:test_granularity) && @config.test_granularity == "example"
40
+ enumerate_examples
41
+ else
42
+ glob_files.map { |f| Test.new(file: f, name: f) }
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def enumerate_examples
49
+ files = glob_files
50
+ return [] if files.empty?
51
+
52
+ examples = run_dry_run(files)
53
+ examples.map { |ex|
54
+ Test.new(
55
+ file: ex["file_path"],
56
+ name: ex["full_description"] || ex["description"]
57
+ )
58
+ }
59
+ end
60
+
61
+ def run_dry_run(files)
62
+ cmd = ["rspec", "--dry-run", "--format", "json", *files, { err: File::NULL }]
63
+ out = IO.popen(cmd) { |io| io.read }
64
+ JSON.parse(out)["examples"] || []
65
+ rescue JSON::ParserError
66
+ raise DiscoveryError, "failed to parse rspec --dry-run output"
35
67
  end
36
68
  end
37
69
 
38
70
  class MinitestDiscovery < TestDiscovery
39
- # For Minitest, we can't easily enumerate test names without running them.
40
- # Instead, treat each file as a single test unit, with the file path as the name.
41
71
  def enumerate
42
- glob_files.map { |f|
43
- Test.new(file: f, name: f)
44
- }
72
+ begin
73
+ require "minitest"
74
+ rescue LoadError
75
+ raise DiscoveryError,
76
+ "minitest is not available. binpacker supports rspec and minitest; " \
77
+ "test-unit and other frameworks are not supported."
78
+ end
79
+ add_project_load_paths
80
+ def Minitest.autorun; end
81
+ Minitest.seed = 42
82
+
83
+ tests = []
84
+ glob_files.each do |file|
85
+ begin
86
+ klasses_before = Minitest::Runnable.runnables.dup
87
+ load File.expand_path(file)
88
+
89
+ (Minitest::Runnable.runnables - klasses_before).each do |klass|
90
+ klass.runnable_methods.each do |method_name|
91
+ tests << Test.new(file: file, name: "#{klass}##{method_name}")
92
+ end
93
+ end
94
+ rescue => e
95
+ $stderr.puts "minitest discovery: failed to load #{file}: #{e.message}"
96
+ end
97
+ end
98
+ tests
45
99
  end
46
100
  end
47
101
  end
@@ -44,6 +44,11 @@ module Binpacker
44
44
  Pathname(path).cleanpath.to_s.sub(/\A\.\//, "")
45
45
  end
46
46
 
47
+ # True when a measured Weight already exists for this Test.
48
+ def measured?(file:, name:)
49
+ load_raw.key?([normalize_path(file), name])
50
+ end
51
+
47
52
  def weight_for(file:, name:)
48
53
  measured = load_raw
49
54
  measured.fetch([file, name]) { filesize_weight(file) }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Binpacker
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
@@ -6,13 +6,14 @@ module Binpacker
6
6
  class Worker
7
7
  attr_reader :id, :status, :example_count, :passed_count
8
8
 
9
- def initialize(id, runner_class, passthrough: [])
9
+ def initialize(id, runner_class, passthrough: [], quiet: false)
10
10
  @id = id
11
11
  @runner_class = runner_class
12
12
  @passthrough = passthrough
13
+ @quiet = quiet
13
14
  @status = :created
14
15
  @timings = []
15
- @exit_code = nil
16
+ @exit_code = 0
16
17
  @example_count = 0
17
18
  @passed_count = 0
18
19
  end
@@ -27,6 +28,7 @@ module Binpacker
27
28
  @pid = Process.spawn(
28
29
  RbConfig.ruby, worker_script,
29
30
  "--runner", @runner_class.runner_name,
31
+ "--id", @id.to_s,
30
32
  *passthrough_args,
31
33
  in: @stdin_r, out: @stdout_w, err: @stderr_w,
32
34
  close_others: true
@@ -35,7 +37,7 @@ module Binpacker
35
37
  @stdin_r.close; @stdout_w.close; @stderr_w.close
36
38
 
37
39
  @stderr_thread = Thread.new do
38
- @stderr_r.each_line { |line| $stderr.write line }
40
+ @stderr_r.each_line { |line| $stderr.write line unless @quiet }
39
41
  end
40
42
 
41
43
  ready_line = read_line(timeout: 30)
@@ -59,13 +61,57 @@ module Binpacker
59
61
  @stdin_w.puts JSON.generate({ file: test.file, name: test.name })
60
62
  end
61
63
 
64
+ def batch_done
65
+ @stdin_w.puts JSON.generate({ type: "done" })
66
+ end
67
+
62
68
  def signal_done
63
69
  @stdin_w.puts JSON.generate({ type: "done" })
64
70
  @stdin_w.close
65
71
  end
66
72
 
73
+ def wait_for_batch
74
+ deadline = Time.now + 300
75
+ while Time.now < deadline
76
+ begin
77
+ line = read_line(timeout: 1)
78
+ return nil unless line
79
+ next unless line.strip.start_with?("{")
80
+ data = JSON.parse(line.strip)
81
+
82
+ if data["type"] == "timing"
83
+ @timings << { file: data["file"], name: data["name"], time: data["time"] }
84
+ elsif data["type"] == "batch_result"
85
+ @exit_code = data["passed"] ? 0 : 1
86
+ @example_count += data["total"] || 0
87
+ @passed_count += data["passed_count"] || 0
88
+ return {
89
+ timings: [],
90
+ exit_code: @exit_code,
91
+ examples: data["total"] || 0,
92
+ passed: data["passed_count"] || 0
93
+ }
94
+ elsif data["type"] == "result"
95
+ @exit_code = data["exit_code"]
96
+ @passed = data["passed"]
97
+ @example_count = data["total"] || 0
98
+ @passed_count = data["passed_count"] || 0
99
+ return {
100
+ timings: [],
101
+ exit_code: @exit_code,
102
+ examples: @example_count,
103
+ passed: @passed_count
104
+ }
105
+ end
106
+ rescue JSON::ParserError
107
+ end
108
+ end
109
+ nil
110
+ end
111
+
67
112
  def collect_results
68
113
  @status = :running
114
+
69
115
  @stdout_r.each_line do |line|
70
116
  data = JSON.parse(line.strip)
71
117
  case data["type"]
@@ -76,6 +122,13 @@ module Binpacker
76
122
  @passed = data["passed"]
77
123
  @example_count = data["total"] || 0
78
124
  @passed_count = data["passed_count"] || 0
125
+ break
126
+ when "batch_result"
127
+ @exit_code = data["passed"] ? 0 : 1
128
+ @passed = data["passed"]
129
+ @example_count = data["total"] || 0
130
+ @passed_count = data["passed_count"] || 0
131
+ break
79
132
  when "output"
80
133
  $stdout.write data["text"] if data["text"]
81
134
  end
@@ -109,7 +162,9 @@ module Binpacker
109
162
 
110
163
  def passthrough_args
111
164
  return [] if @passthrough.empty?
112
- @passthrough.flat_map { |arg| ["--rspec-arg", arg] }
165
+
166
+ worker_arg = @runner_class.runner_name == "minitest" ? "--minitest-arg" : "--rspec-arg"
167
+ @passthrough.flat_map { |arg| [worker_arg, arg] }
113
168
  end
114
169
 
115
170
  def kill!
data/lib/binpacker.rb CHANGED
@@ -9,7 +9,11 @@ require_relative "binpacker/scheduler"
9
9
  require_relative "binpacker/worker"
10
10
  require_relative "binpacker/test_runner"
11
11
  require_relative "binpacker/calibration"
12
+ require_relative "binpacker/report"
13
+ require_relative "binpacker/skills"
14
+ require_relative "binpacker/project_state"
12
15
  require_relative "binpacker/orchestrator"
16
+ require_relative "binpacker/progress"
13
17
 
14
18
  module Binpacker
15
19
  Error = Class.new(StandardError)
@@ -0,0 +1,6 @@
1
+ module Binpacker
2
+ class Calibration
3
+ def initialize: (untyped) -> void
4
+ def run: (untyped, ?incremental: bool) -> Array[untyped]
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ module Binpacker
2
+ class CLI
3
+ def self.start: (untyped) -> nil
4
+ def initialize: (untyped) -> void
5
+ def run: () -> nil
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ module Binpacker
2
+ class Config
3
+ def initialize: (?profile: untyped, ?config_path: untyped) -> void
4
+ def worker_count: () -> Integer
5
+ def test_runner: () -> String
6
+ def timing_file: () -> String
7
+ def test_pattern: () -> String
8
+ def test_exclude: () -> Array[String]
9
+ def test_granularity: () -> String
10
+ def report_file: () -> String?
11
+ def profile: () -> String
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ module Binpacker
2
+ class Orchestrator
3
+ def initialize: (untyped, ?passthrough: untyped, ?quiet: untyped, ?report_path: untyped) -> void
4
+ def run: () -> { passed: untyped, total: untyped, passed_count: untyped, timings: Array[untyped], empty_filter: untyped }
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Binpacker
2
+ class ProgressDisplay
3
+ def initialize: (untyped, ?tty: untyped) -> void
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ module Binpacker
2
+ class ProjectState
3
+ CONFIG_FILE: String
4
+ DEFAULT_TIMING_FILE: String
5
+ SUPPORTED_FRAMEWORKS: Array[String]
6
+ TESTUNIT_HINT: Regexp
7
+
8
+ def config_present?: () -> bool
9
+ def timing_present?: () -> bool
10
+ def framework: () -> String?
11
+ def supported_framework?: () -> bool
12
+ def ci_wired?: () -> bool
13
+ def recommendation: () -> String
14
+ def to_h: () -> Hash[Symbol, untyped]
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ module Binpacker
2
+ class Report
3
+ SCHEMA: Integer
4
+ DRIFT_LIMIT: Integer
5
+
6
+ def initialize: (profile: untyped, algorithm: untyped, predicted_loads: untyped, worker_stats: untyped, all_timings: untyped, timings: untyped) -> void
7
+ def to_h: () -> Hash[Symbol, untyped]
8
+ def write: (untyped) -> untyped
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ module Binpacker
2
+ class Scheduler
3
+ def self.for: (untyped) -> (Binpacker::LptScheduler | Binpacker::MultifitScheduler)
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ module Binpacker
2
+ module Skills
3
+ ROOT: String
4
+
5
+ def self.list: () -> Array[Hash[Symbol, String]]
6
+ def self.exist?: (untyped) -> bool
7
+ def self.path: (untyped) -> String?
8
+ def self.body: (untyped) -> String
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ module Binpacker
2
+ def key: () -> [untyped, String]
3
+ class TestDiscovery
4
+ def initialize: (untyped) -> void
5
+ def enumerate: () -> Array[Binpacker::Test]
6
+ end
7
+ class RSpecDiscovery < TestDiscovery
8
+ def enumerate: () -> Array[Binpacker::Test]
9
+ end
10
+ class MinitestDiscovery < TestDiscovery
11
+ def enumerate: () -> Array[Binpacker::Test]
12
+ end
13
+ class Test
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ module Binpacker
2
+ class TestRunner
3
+ def self.runner_name: () -> bot
4
+ def self.for: (untyped) -> (singleton(Binpacker::MinitestRunner) | singleton(Binpacker::RSpecRunner))
5
+ end
6
+ class RSpecRunner
7
+ def self.runner_name: () -> "rspec"
8
+ def self.discovery_command: (untyped) -> Array[untyped]
9
+ end
10
+ class MinitestRunner
11
+ def self.runner_name: () -> "minitest"
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ module Binpacker
2
+ class Timing
3
+ def initialize: (untyped) -> void
4
+ def load_raw: () -> (Hash[[String, untyped], untyped] | {})
5
+ def load_per_file: () -> (Hash[String, untyped] | {})
6
+ def normalize_path: (untyped) -> String
7
+ def measured?: (file: untyped, name: untyped) -> bool
8
+ class Entry
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ module Binpacker
2
+ class Worker
3
+ def initialize: (untyped, untyped, ?passthrough: untyped, ?quiet: untyped) -> void
4
+ def start: () -> Binpacker::Worker
5
+ def send_test: (untyped) -> nil
6
+ def batch_done: () -> nil
7
+ def wait_for_batch: () -> (nil | { timings: [], exit_code: 0 | 1, examples: untyped, passed: untyped } | { timings: [], exit_code: untyped, examples: untyped, passed: untyped })
8
+ def collect_results: () -> :finished
9
+ def timings: () -> Array[untyped]
10
+ def status: () -> (:crashed | :created | :error | :finished | :ready | :running)
11
+ end
12
+ end
@@ -0,0 +1,6 @@
1
+ module Binpacker
2
+ class WorkerQueue
3
+ def initialize: (untyped, ?untyped) -> void
4
+ def empty?: () -> (false | true)
5
+ end
6
+ end
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: binpacker-improve
3
+ description: Tune an already-configured binpacker project from real CI data — fetch recent run reports, analyze the gap between predicted and actual durations, and propose binpacker.yml adjustments. Use when binpacker is set up and running in CI and the user wants to reduce makespan, fix worker imbalance, or refresh stale timing data. Propose-only — never apply changes without confirmation.
4
+ ---
5
+
6
+ # Binpacker Improve
7
+
8
+ A recurring, **propose-only** workflow. It reads real CI data and proposes config changes; it never edits `binpacker.yml`, commits, or pushes without explicit user confirmation.
9
+
10
+ Run `binpacker describe` first. If it recommends `binpacker-setup`, the project isn't configured yet — use that skill instead.
11
+
12
+ ## 1. Gather CI data
13
+
14
+ Fetch recent run reports (see `docs/design/run-report-format.md` for the schema) from CI artifacts:
15
+
16
+ ```sh
17
+ gh run list --workflow <ci-workflow> --limit 10
18
+ gh run download <run-id> --name <report-artifact>
19
+ ```
20
+
21
+ Collect several recent reports so you can see trends, not just one run. Each report holds `predicted_makespan`, `actual_makespan`, per-worker predicted/actual durations, `balance`, and the worst-drift Tests.
22
+
23
+ ## 2. Analyze
24
+
25
+ Look for patterns across the reports:
26
+
27
+ - **Persistent worker skew** — the same worker is consistently slowest → the partition or worker count is off.
28
+ - **Predicted vs actual gap** — `actual_makespan` far above `predicted_makespan` → timing data is stale; the scheduler is planning against wrong Weights.
29
+ - **Recurring high-drift files** — the same files top the `drift` list every run → their timing data is stale or they are outliers.
30
+ - **Cache-miss frequency** — if CI logs show frequent timing-cache misses (e.g. after dependency bumps), the runtime cache isn't helping cold starts.
31
+
32
+ ## 3. Propose
33
+
34
+ Present a concrete `binpacker.yml` diff and/or commands, with the evidence behind each change. Typical levers:
35
+
36
+ - **`workers`** — raise/lower the `ci` worker count when actual makespan shows consistent under- or over-utilisation.
37
+ - **`scheduler.algorithm`** — `lpt` ↔ `multifit`. Prefer `multifit` for tighter balance; only suggest `lpt` if `multifit` isn't helping.
38
+ - **`scheduler.steal_enabled`** — enable when static balance is poor but runtime stealing would recover it.
39
+ - **Re-calibrate** — when drift is high or timing is stale, recommend `binpacker calibrate --incremental` to refresh the drifting Tests.
40
+ - **Commit a baseline** — when the cache misses often (dependency-bump churn) or timings have stabilised, recommend committing `binpacker.timings` as a durable baseline (cold-start floor). See `docs/adr/0005-two-tier-timing-persistence.md`.
41
+
42
+ ## 4. Confirm and apply
43
+
44
+ - Show the diff. Explain the expected effect (e.g. "should cut max deviation from 6% toward <3%").
45
+ - Apply only what the user approves. Committing a baseline or editing config both require confirmation.
46
+ - If the user wants, open a PR with `gh`; otherwise leave the working tree changes for them to review.
47
+
48
+ ## Done when
49
+
50
+ - The user has seen the predicted-vs-actual analysis with evidence.
51
+ - A concrete, confirmed set of changes (or an explicit "no change needed") has been reached.
52
+ - Any commit / PR was made only with confirmation.
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: binpacker-setup
3
+ description: Bring a project from zero to a working parallel test run with binpacker — detect the framework, generate config, calibrate timing data, validate a parallel run, and wire CI. Use when binpacker is installed but not yet set up (no binpacker.yml, or no timing data), or when the user asks to set up / configure / adopt binpacker.
4
+ ---
5
+
6
+ # Binpacker Setup
7
+
8
+ A one-time workflow that takes a project from zero to a working parallel test run. Run `binpacker describe` first — if it recommends `binpacker-improve`, the project is already set up and you want that skill instead.
9
+
10
+ Assess state as you go and **ask the user at each decision point** rather than assuming. Do not run destructive or outward-facing steps (committing, opening a PR) without explicit confirmation.
11
+
12
+ ## 1. Detect
13
+
14
+ - Confirm binpacker is installed: `binpacker --version`.
15
+ - Run `binpacker describe` to read current state (config present? timing data? framework? CI wired?).
16
+ - Determine the test framework (rspec / minitest) and the test glob. If `binpacker describe` reports one, trust it; otherwise inspect the repo.
17
+
18
+ ## 2. Config
19
+
20
+ If `binpacker.yml` is absent, create it with `binpacker init` (it auto-detects the framework and writes `default` + `ci` profiles). Then review the generated file with the user and adjust:
21
+
22
+ - `workers` — `auto` locally; a fixed number (e.g. `4`) for the `ci` profile matching the CI runner.
23
+ - `test_pattern` / `test_exclude` — match the project's real layout.
24
+ - `scheduler.algorithm` — `multifit` (default) unless there's a reason to use `lpt`.
25
+ - `scheduler.steal_enabled` — `true` to let idle workers steal at runtime.
26
+ - `report_file` (on the `ci` profile) — e.g. `binpacker-report.json`, so CI always emits a run report.
27
+
28
+ ## 3. Calibrate
29
+
30
+ Seed timing data so the scheduler has real Weights:
31
+
32
+ ```sh
33
+ binpacker calibrate
34
+ ```
35
+
36
+ If some timing data already exists, measure only the gaps:
37
+
38
+ ```sh
39
+ binpacker calibrate --incremental
40
+ ```
41
+
42
+ ## 4. Validate
43
+
44
+ Run the suite in parallel and confirm it is green and balanced:
45
+
46
+ ```sh
47
+ binpacker run
48
+ ```
49
+
50
+ Check the summary's `Balance: max deviation`. A large deviation on the first run is expected before timing data accumulates; note it and move on.
51
+
52
+ ## 5. Wire CI
53
+
54
+ Add (or extend) a CI workflow so the suite runs under binpacker. The workflow should:
55
+
56
+ - Install binpacker (via the project's dependency manager or `gem install binpacker`).
57
+ - Restore and save `binpacker.timings` with `actions/cache` (key includes OS + a hash of the test files). This is the runtime timing layer.
58
+ - Run `binpacker run --profile ci` (which emits the run report via `report_file`).
59
+ - Upload the run report with `actions/upload-artifact` so `binpacker-improve` can read it later.
60
+
61
+ Propose the workflow YAML and let the user review it before writing.
62
+
63
+ ## 6. Handoff
64
+
65
+ Only after the user confirms:
66
+
67
+ - Create a branch, commit `binpacker.yml`, the CI workflow, and (if the user wants a committed baseline) `binpacker.timings`.
68
+ - Open a PR with `gh`, summarising what was set up.
69
+ - After CI runs, point the user at `binpacker-improve` to tune from real CI data.
70
+
71
+ ## Done when
72
+
73
+ - `binpacker run` passes locally across workers.
74
+ - `binpacker.yml` reflects the project's framework, layout, and CI worker count.
75
+ - CI runs `binpacker run` and uploads a run report.
76
+ - The user has confirmed any commit / PR.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: binpacker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - megurine
@@ -9,8 +9,9 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
- description: A test runner wrapper that manages worker processes and distributes tests
13
- among them using LPT scheduling with optional work-stealing.
12
+ description: A test runner wrapper that models test distribution as an identical-machines
13
+ scheduling problem and assigns tests to worker processes using LPT scheduling, with
14
+ optional work-stealing.
14
15
  executables:
15
16
  - binpacker
16
17
  extensions: []
@@ -24,13 +25,33 @@ files:
24
25
  - lib/binpacker/cli.rb
25
26
  - lib/binpacker/config.rb
26
27
  - lib/binpacker/orchestrator.rb
28
+ - lib/binpacker/progress.rb
29
+ - lib/binpacker/project_state.rb
30
+ - lib/binpacker/report.rb
27
31
  - lib/binpacker/scheduler.rb
32
+ - lib/binpacker/skills.rb
28
33
  - lib/binpacker/test_discovery.rb
29
34
  - lib/binpacker/test_runner.rb
30
35
  - lib/binpacker/timing.rb
31
36
  - lib/binpacker/version.rb
32
37
  - lib/binpacker/worker.rb
33
38
  - lib/binpacker/worker_queue.rb
39
+ - sig/binpacker/calibration.rbs
40
+ - sig/binpacker/cli.rbs
41
+ - sig/binpacker/config.rbs
42
+ - sig/binpacker/orchestrator.rbs
43
+ - sig/binpacker/progress.rbs
44
+ - sig/binpacker/project_state.rbs
45
+ - sig/binpacker/report.rbs
46
+ - sig/binpacker/scheduler.rbs
47
+ - sig/binpacker/skills.rbs
48
+ - sig/binpacker/test_discovery.rbs
49
+ - sig/binpacker/test_runner.rbs
50
+ - sig/binpacker/timing.rbs
51
+ - sig/binpacker/worker.rbs
52
+ - sig/binpacker/worker_queue.rbs
53
+ - skills/binpacker-improve/SKILL.md
54
+ - skills/binpacker-setup/SKILL.md
34
55
  homepage: https://github.com/rigortype/binpacker
35
56
  licenses:
36
57
  - MPL-2.0
@@ -53,6 +74,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
53
74
  requirements: []
54
75
  rubygems_version: 4.0.10
55
76
  specification_version: 4
56
- summary: Minimize CI test-suite makespan by solving an identical-machines scheduling
57
- problem
77
+ summary: Reduce CI test-suite makespan via identical-machines scheduling
58
78
  test_files: []