ace-support-cli 0.6.9 → 0.6.10

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: cbcd26854c1dcfe8c16d4024cd0c2b4c7a16796dce24f1da392495a5dae0b470
4
- data.tar.gz: a4928a2682ed05127e9bd463b4c1df18303693e17c650b05ac4a746480b6ba2b
3
+ metadata.gz: 97fff71bfa5c46c0a82bae86872692ae5488919550b2c0c9afce4e4385a5bc64
4
+ data.tar.gz: c6e73139b02112a21a917af26942365a1ffcae3687990a643382a335c0b6fcc6
5
5
  SHA512:
6
- metadata.gz: 1abcf19ddb8f690818a501259094292aa38213e74ab4bda4f22fd42240f9724fd5051e4547c23ee8795bfdeb59b45d44166fdd878ac227c9df61e38af6f2a2f1
7
- data.tar.gz: 3554cd2b86ce51600d20b39a1c4164728990ddb9e1e8dcb095d3b00265f46dc346e0440f9ff645eb97168043542f55b097edd937bfb004e2b85f40417c0f525b
6
+ metadata.gz: 2a8e8bef070892011cd79dbab1b679f6d7362921e294752869d5801c2c7c1e65019897ee5f0f14dbfbbbc3280cf1230a68f82f2d3b3a9b761096b51b7f9893f4
7
+ data.tar.gz: 79687ddb896af4f69e20a9506c586df519486c632ef3a346aa0c5ae20fdd5d6dc8e3871d46efe4b3d82feb0751ab6a6002de39d487304ce9b9cf89ed3138fc44
data/CHANGELOG.md CHANGED
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.6.10] - 2026-10-20
8
+
9
+ ### Added
10
+ - Explicit installed toolchain policy: installed executable selection, loaded closure verification, child environment and runtime-origin evidence. Default auto preserves source development.
11
+
7
12
  ## [0.6.9] - 2026-09-29
8
13
 
9
14
  ### Fixed
data/README.md CHANGED
@@ -45,3 +45,28 @@ Verification commands:
45
45
  ---
46
46
 
47
47
  Part of [ACE](https://github.com/cs3b/ace)
48
+
49
+ ## Installed toolchain
50
+
51
+ `ACE_TOOLCHAIN=installed` selects installed RubyGems executables and rejects a
52
+ loaded path-gem closure. The default `auto` keeps source development. Shared
53
+ `Ace::Support::Cli::Toolchain` resolves executables, cleans Bundler/Ruby source
54
+ injection from child environments, supplies post-shell startup environment for
55
+ Herdr and reports loaded gem origins. It does not grant permissions or replace
56
+ project domain configuration.
57
+
58
+ Installed navigation uses packaged protocol definitions and generic document
59
+ sources; Assign excludes `skill_source_paths`, `workflow_source_paths` and
60
+ repository package discovery in this mode. Coworker briefs expose the selected
61
+ mode and document origin. Missing installed dependencies fail rather than using
62
+ project binstubs. The flag is an execution policy, not a sandbox against code
63
+ that deliberately clears it.
64
+
65
+ For a user-owned installed runtime, start from its Ruby/GEM_HOME/GEM_PATH and run
66
+ `ACE_TOOLCHAIN=installed ace-coworker start --config <project-run-config> --format json`.
67
+ Inspect `toolchain.mode`, loaded gem paths/versions and `instructions_source`;
68
+ `which` alone is not loaded-closure evidence. Commissioned installed runs retain
69
+ `toolchain: installed` in their existing run config; source-mode resume is rejected.
70
+ Herdr installs the selected Ruby/gem environment after shell startup, including
71
+ reused shell panes. Changing an already-running agent's environment is not
72
+ supported: start a fresh executor with the selected toolchain instead.
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "parser"
4
+ require_relative "toolchain"
4
5
 
5
6
  module Ace
6
7
  module Support
@@ -12,6 +13,7 @@ module Ace
12
13
  end
13
14
 
14
15
  def call(args: ARGV)
16
+ Toolchain.verify_loaded!
15
17
  if root_help_request?(args)
16
18
  puts Ace::Support::Cli::Help::Usage.new(@registry, program_name: resolve_program_name).render
17
19
  return 0
@@ -20,6 +22,7 @@ module Ace
20
22
  command_target, remaining, command_name = resolve_target(args)
21
23
  command_class = command_target.is_a?(Class) ? command_target : command_target.class
22
24
  parsed = @parser_class.new(command_class, command_name: command_name).parse(remaining)
25
+ Toolchain.verify_loaded!
23
26
  result = if command_target.is_a?(Class)
24
27
  command_target.new.call(**parsed)
25
28
  else
@@ -33,6 +36,8 @@ module Ace
33
36
  raise Ace::Support::Cli::Error.new(e.message)
34
37
  rescue Ace::Support::Cli::CommandNotFoundError => e
35
38
  raise Ace::Support::Cli::Error.new(e.message)
39
+ rescue Toolchain::Error => e
40
+ raise Ace::Support::Cli::Error.new(e.message)
36
41
  end
37
42
 
38
43
  private
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubygems"
4
+ require "shellwords"
5
+
6
+ module Ace
7
+ module Support
8
+ module Cli
9
+ # Selection policy, not authority: project data/configuration remain usable.
10
+ module Toolchain
11
+ class Error < StandardError; end
12
+
13
+ module_function
14
+
15
+ def installed?
16
+ mode = ENV.fetch("ACE_TOOLCHAIN", "auto")
17
+ raise Error, "unknown ACE_TOOLCHAIN #{mode.inspect}; use auto or installed" unless %w[auto installed].include?(mode)
18
+
19
+ mode == "installed"
20
+ end
21
+
22
+ def installed_spec?(spec)
23
+ return false unless spec.loaded_from && File.file?(spec.loaded_from)
24
+
25
+ root = File.realpath(spec.base_dir)
26
+ gem_path = File.realpath(spec.full_gem_path)
27
+ specification = File.realpath(spec.loaded_from)
28
+ gem_path.start_with?("#{root}/gems/") &&
29
+ specification.start_with?("#{root}/specifications/")
30
+ rescue Errno::ENOENT
31
+ false
32
+ end
33
+
34
+ def verify_loaded!
35
+ return unless installed?
36
+
37
+ Gem.loaded_specs.each_value do |spec|
38
+ next if installed_spec?(spec) || spec.default_gem?
39
+
40
+ raise Error, "installed toolchain loaded source dependency #{spec.name}: #{spec.full_gem_path}"
41
+ end
42
+ roots = Gem.loaded_specs.values.select { |spec| installed_spec?(spec) }
43
+ .map { |spec| "#{File.realpath(spec.full_gem_path)}/" }
44
+ $LOADED_FEATURES.grep(%r{/lib/ace/}).each do |feature|
45
+ actual = File.realpath(feature)
46
+ next if roots.any? { |root| actual.start_with?(root) }
47
+
48
+ raise Error, "installed toolchain loaded source code outside its gem closure: #{actual}"
49
+ end
50
+ end
51
+
52
+ def executable(name, project_root: nil, prefer_project: true)
53
+ unless installed?
54
+ candidate = File.join(project_root, "bin", name) if project_root && prefer_project
55
+ return candidate if candidate && File.executable?(candidate)
56
+
57
+ return name
58
+ end
59
+
60
+ verify_loaded!
61
+ spec = Gem::Specification.find_all.select do |entry|
62
+ entry.executables.include?(name) && installed_spec?(entry)
63
+ end.max_by(&:version)
64
+ raise Error, "installed executable #{name} unavailable; install its published gem" unless spec
65
+
66
+ path = File.join(spec.full_gem_path, spec.bindir, name)
67
+ unless File.executable?(path) && File.realpath(path).start_with?("#{File.realpath(spec.full_gem_path)}/")
68
+ raise Error, "installed executable #{name} missing or outside its gem: #{path}"
69
+ end
70
+ path
71
+ end
72
+
73
+ # Bundler/Ruby source injection must not leak into subprocesses. Domain
74
+ # environment (including credentials) is inherited, never inspected here.
75
+ def child_environment
76
+ return {} unless installed?
77
+
78
+ ENV.keys.grep(/\ABUNDLER?_/).to_h { |key| [key, nil] }.merge(
79
+ "RUBYOPT" => nil, "RUBYLIB" => nil, "ACE_TOOLCHAIN" => "installed",
80
+ "GEM_HOME" => Gem.dir, "GEM_PATH" => Gem.path.join(File::PATH_SEPARATOR),
81
+ "PATH" => [Gem.bindir, File.dirname(Gem.ruby), ENV.fetch("PATH", "")].join(File::PATH_SEPARATOR)
82
+ )
83
+ end
84
+
85
+ def shell_setup
86
+ return "" unless installed?
87
+
88
+ names = (ENV.keys.grep(/\ABUNDLER?_/) +
89
+ %w[BUNDLE_GEMFILE BUNDLE_PATH BUNDLE_BIN_PATH BUNDLER_SETUP RUBYOPT RUBYLIB]).uniq
90
+ path = [Gem.bindir, File.dirname(Gem.ruby), ENV.fetch("PATH", "")].join(File::PATH_SEPARATOR)
91
+ values = {"ACE_TOOLCHAIN" => "installed", "GEM_HOME" => Gem.dir,
92
+ "GEM_PATH" => Gem.path.join(File::PATH_SEPARATOR), "PATH" => path}
93
+ "unset #{names.map { |name| Shellwords.escape(name) }.join(" ")}; export " +
94
+ values.map { |name, value| "#{name}=#{Shellwords.escape(value)}" }.join(" ") + "; "
95
+ end
96
+
97
+ def shell_command(name, *args)
98
+ shell_setup + Shellwords.join([executable(name), *args])
99
+ end
100
+
101
+ def evidence
102
+ {"mode" => installed? ? "installed" : "auto",
103
+ "gems" => Gem.loaded_specs.values.select { |spec| spec.name.start_with?("ace-") }
104
+ .to_h do |spec|
105
+ [spec.name, {"version" => spec.version.to_s, "path" => spec.full_gem_path,
106
+ "executables" => spec.executables.to_h { |name| [name, File.join(spec.full_gem_path, spec.bindir, name)] }}]
107
+ end}
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -3,7 +3,7 @@
3
3
  module Ace
4
4
  module Support
5
5
  module Cli
6
- VERSION = "0.6.9"
6
+ VERSION = "0.6.10"
7
7
  end
8
8
  end
9
9
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "cli/version"
4
+ require_relative "cli/toolchain"
4
5
  require_relative "cli/errors"
5
6
  require_relative "cli/error"
6
7
  require_relative "cli/standard_options"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-support-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.9
4
+ version: 0.6.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-09-30 00:00:00.000000000 Z
10
+ date: 2026-10-20 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: Provides command DSL, option parsing, registry routing, and runner primitives
13
13
  for ACE CLI tools.
@@ -42,6 +42,7 @@ files:
42
42
  - lib/ace/support/cli/registry_dsl.rb
43
43
  - lib/ace/support/cli/runner.rb
44
44
  - lib/ace/support/cli/standard_options.rb
45
+ - lib/ace/support/cli/toolchain.rb
45
46
  - lib/ace/support/cli/version.rb
46
47
  homepage: https://github.com/cs3b/ace
47
48
  licenses: