ace-support-nav 0.28.6 → 0.28.8

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: 1948cb2b1085585f1a3ccd40d9fddd0dd130bad2ede2991bb7922ee3dba68143
4
- data.tar.gz: 4d822e1cb78a2b43fe641d1a963cc68df74b97cda80004ce87a7520851793cef
3
+ metadata.gz: 360819ca45c3cd71302707c5bc82a4a6dc6cf867d02ca96796ba04c24ddc4f45
4
+ data.tar.gz: 35feb7c2fb3c43a357fe25f9aab9ec3577d853ed4ea8224cfc4fbd09a3c69a6f
5
5
  SHA512:
6
- metadata.gz: 98e6c2d42e54a1b6f272689238ffe9371256446127e22c26120489043e850b036d8438164b3fd4b64caa871391caddda8b6d2fb87222e6a485a6c02e8479902e
7
- data.tar.gz: 0b937920845694bf9268b5591793e8f73d51a01fe5f1590fdd26c1e62d5c2437e869a951f012bf37f0ff8bda2a780cf6ef74084d2d07435089affa0df496776f
6
+ metadata.gz: 3657cb2a716864179ba9f342d4cc005aa5fc6838a08a831b710a8cc8280fb405d7bc51f413c8485825d87b4462d3e05385cd70c9a82d1e390d9206fe1b9f1133
7
+ data.tar.gz: 1d7e62975dcc3d28e0f4a5e765909a892d9b391a5fed1858425389faf82a393cebe698940e6fa8d48bb8f1b0b915eb62e4b14d99ac5f23aed2de323c3be5c380
data/CHANGELOG.md CHANGED
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.28.8] - 2026-10-20
11
+
12
+ ### Fixed
13
+ - Honor `ACE_TOOLCHAIN=installed` for published-gem execution and context propagation without source-runtime fallback; retain default auto development behavior.
14
+
15
+ ## [0.28.7] - 2026-09-27
16
+
17
+ ### Technical
18
+ - Resolve temporary test paths through symlinks.
19
+
20
+ ### Added
21
+ - `ace-nav resolve <uri> --why` prints every duplicate candidate in resolution order with its source, priority, and origin, marks the winner, and states the source-priority rule, making override precedence observable without planting probe files.
22
+
10
23
 
11
24
  ## [0.28.6] - 2026-09-02
12
25
 
data/exe/ace-nav CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "bundler/setup"
4
+ require "bundler/setup" unless ENV["ACE_TOOLCHAIN"] == "installed"
5
5
  require "ace/support/nav"
6
6
  require "ace/support/cli"
7
7
 
@@ -39,6 +39,9 @@ module Ace
39
39
  # Display content
40
40
  $ ace-nav resolve wfi://setup --content
41
41
 
42
+ # Explain precedence: all candidates + the rule that picked the winner
43
+ $ ace-nav resolve wfi://setup --why
44
+
42
45
  # Wildcard patterns auto-route to list
43
46
  $ ace-nav resolve wfi://*
44
47
 
@@ -57,6 +60,8 @@ module Ace
57
60
 
58
61
  By default, displays resolved path
59
62
  Use --content to display resource content
63
+ Use --why to print all candidates in resolution order plus the
64
+ rule that picked the winner (useful when duplicates exist)
60
65
  Exit codes: 0 (success), 1 (error)
61
66
  DESC
62
67
 
@@ -71,6 +76,7 @@ module Ace
71
76
 
72
77
  option :path, type: :boolean, desc: "Display resource path"
73
78
  option :content, type: :boolean, desc: "Display resource content"
79
+ option :why, type: :boolean, desc: "Explain precedence: print all candidates in resolution order and the winner"
74
80
  option :tree, type: :boolean, desc: "Display resources in tree format (passed through to cmd protocols)"
75
81
  option :verbose, type: :boolean, aliases: %w[-v], desc: "Show verbose output"
76
82
  option :quiet, type: :boolean, aliases: %w[-q], desc: "Suppress non-essential output"
@@ -103,11 +109,18 @@ module Ace
103
109
  if @uri.include?("://")
104
110
  protocol = @uri.split("://").first
105
111
  if @engine.cmd_protocol?(protocol)
112
+ if @options[:why]
113
+ raise Ace::Support::Cli::Error.new("--why is not available for cmd protocols (#{protocol} delegates to an external command; there are no local candidates)")
114
+ end
106
115
  delegator = Organisms::CommandDelegator.new
107
116
  return delegator.delegate(@uri, @options)
108
117
  end
109
118
  end
110
119
 
120
+ if @options[:why]
121
+ return print_explanation(@engine.explain(@uri))
122
+ end
123
+
111
124
  result = @engine.resolve(@uri, content: @options[:content], verbose: @options[:verbose])
112
125
 
113
126
  if result.nil?
@@ -127,6 +140,28 @@ module Ace
127
140
 
128
141
  private
129
142
 
143
+ # Print a precedence explanation produced by NavigationEngine#explain
144
+ def print_explanation(explanation)
145
+ if explanation[:candidates].empty?
146
+ raise Ace::Support::Cli::Error.new("Resource not found: #{@uri}")
147
+ end
148
+
149
+ if @options[:verbose]
150
+ require "json"
151
+ puts JSON.pretty_generate(explanation)
152
+ return
153
+ end
154
+
155
+ puts "uri: #{explanation[:uri]}"
156
+ puts "rule: #{explanation[:rule]}"
157
+ puts "candidates (resolution order):"
158
+ explanation[:candidates].each do |candidate|
159
+ origin = candidate[:origin] || candidate[:source_type]
160
+ puts " #{candidate[:position]}. [priority #{candidate[:priority]}] #{candidate[:source]} (#{origin}) → #{candidate[:path]}"
161
+ puts " ← winner" if candidate[:winner]
162
+ end
163
+ end
164
+
130
165
  # Normalize bare protocol names (e.g., "wfi") to protocol:// format
131
166
  # This allows users to type "ace-nav wfi" instead of "ace-nav wfi://"
132
167
  def normalize_protocol_shorthand(uri)
@@ -33,8 +33,19 @@ module Ace
33
33
 
34
34
  # Get relative path from config, or use default
35
35
  relative = @config&.dig("relative_path") || "handbook/workflow-instructions"
36
+ return File.join(gem_dir, relative) unless Ace::Support::Cli::Toolchain.installed?
36
37
 
37
- File.join(gem_dir, relative)
38
+ candidate = File.expand_path(relative, gem_dir)
39
+ if Ace::Support::Cli::Toolchain.installed?
40
+ unless Ace::Support::Cli::Toolchain.installed_spec?(spec) &&
41
+ candidate.start_with?("#{File.expand_path(gem_dir)}/")
42
+ raise Ace::Support::Cli::Toolchain::Error, "generic document source is outside installed gem #{@name}"
43
+ end
44
+ if File.exist?(candidate) && !File.realpath(candidate).start_with?("#{File.realpath(gem_dir)}/")
45
+ raise Ace::Support::Cli::Toolchain::Error, "generic document source escapes installed gem #{@name}"
46
+ end
47
+ end
48
+ candidate
38
49
  rescue Gem::LoadError => e
39
50
  # Gem not found, return a placeholder path
40
51
  warn "Gem '#{@name}' not found: #{e.message}" if ENV["VERBOSE"]
@@ -89,6 +89,8 @@ module Ace
89
89
  @protocols[key] = protocol_data if key
90
90
  end
91
91
 
92
+ return @protocols if Ace::Support::Cli::Toolchain.installed?
93
+
92
94
  # 1. Load from user ~/.ace/nav/protocols/
93
95
  load_directory_protocols(File.expand_path("~/.ace/nav/protocols")).each do |protocol_data|
94
96
  key = protocol_data["protocol"]
@@ -352,6 +352,8 @@ module Ace
352
352
  # Remove @ prefix if present
353
353
  name = alias_name.start_with?("@") ? alias_name[1..] : alias_name
354
354
 
355
+ return nil if Ace::Support::Cli::Toolchain.installed? && %w[project local user global].include?(name)
356
+
355
357
  # Handle special aliases
356
358
  case name
357
359
  when "project", "local"
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "yaml"
4
+ require "ace/support/cli/toolchain"
4
5
  require "date"
5
6
  require "pathname"
6
7
  require "rubygems"
@@ -36,10 +37,10 @@ module Ace
36
37
  sources = []
37
38
 
38
39
  # Discover from user directory
39
- sources.concat(discover_user_sources(protocol))
40
+ sources.concat(discover_user_sources(protocol)) unless Ace::Support::Cli::Toolchain.installed?
40
41
 
41
42
  # Discover from project hierarchy
42
- sources.concat(discover_project_sources(protocol))
43
+ sources.concat(discover_project_sources(protocol)) unless Ace::Support::Cli::Toolchain.installed?
43
44
 
44
45
  # Discover packaged defaults from installed ace-* gems
45
46
  sources.concat(discover_gem_default_sources(protocol))
@@ -112,12 +113,13 @@ module Ace
112
113
 
113
114
  Gem::Specification.each do |spec|
114
115
  next unless spec.name.start_with?("ace-")
116
+ next if Ace::Support::Cli::Toolchain.installed? && !Ace::Support::Cli::Toolchain.installed_spec?(spec)
115
117
 
116
118
  sources_dir = File.join(spec.gem_dir, sources_dir_suffix)
117
119
  next unless Dir.exist?(sources_dir)
118
120
 
119
121
  Dir.glob(File.join(sources_dir, "*.yml")).each do |source_file|
120
- source = load_source_file(source_file, "gem-default")
122
+ source = load_source_file(source_file, "gem-default", gem_root: spec.gem_dir)
121
123
  sources << source if source
122
124
  end
123
125
  end
@@ -125,12 +127,19 @@ module Ace
125
127
  sources
126
128
  end
127
129
 
128
- def load_source_file(file_path, origin)
130
+ def load_source_file(file_path, origin, gem_root: nil)
129
131
  data = YAML.safe_load_file(file_path, permitted_classes: [Date], aliases: true)
130
132
  return nil unless data.is_a?(Hash)
131
133
 
132
134
  # Expand environment variables in path (only for non-gem types)
133
135
  path = (data["type"] == "gem") ? nil : expand_path(data["path"]) if data["path"]
136
+ if Ace::Support::Cli::Toolchain.installed? && data["type"] != "gem"
137
+ return nil unless gem_root && data["path"]
138
+
139
+ path = File.expand_path(data["path"], gem_root)
140
+ return nil unless File.directory?(path) &&
141
+ File.realpath(path).start_with?("#{File.realpath(gem_root)}/")
142
+ end
134
143
 
135
144
  # Log warning if path is provided for gem type
136
145
  if data["type"] == "gem" && data["path"]
@@ -100,8 +100,16 @@ module Ace
100
100
  # @param command_parts [Array<String>] Command parts for safe execution
101
101
  # @return [Integer] Exit code (0 for success, 1 for failure)
102
102
  def execute_command(command_parts)
103
+ if Ace::Support::Cli::Toolchain.installed?
104
+ command_parts = command_parts.dup
105
+ command_parts[0] = Ace::Support::Cli::Toolchain.executable(command_parts.fetch(0))
106
+ end
103
107
  # Use system with array argument for safety (no shell interpolation)
104
- success = system(*command_parts)
108
+ success = if Ace::Support::Cli::Toolchain.installed?
109
+ system(Ace::Support::Cli::Toolchain.child_environment, *command_parts)
110
+ else
111
+ system(*command_parts)
112
+ end
105
113
 
106
114
  # Return exit code
107
115
  if success.nil?
@@ -17,6 +17,15 @@ module Ace
17
17
  @path_normalizer = path_normalizer || Atoms::PathNormalizer.new
18
18
  end
19
19
 
20
+ # Resolution rule applied when multiple sources register the same resource.
21
+ # Sources are ordered by ascending priority (lower number = higher precedence)
22
+ # and the first source containing the resource wins.
23
+ RESOLUTION_RULE = "Candidates are tried in ascending source-priority order " \
24
+ "(lower priority number wins); the first source containing the resource is " \
25
+ "the winner and remaining duplicates are ignored. Registration defaults: " \
26
+ "project sources 10, user sources 50, gem defaults 100 - individual source " \
27
+ "registrations may pin other values.".freeze
28
+
20
29
  # Resolve a single resource URI to a path
21
30
  def resolve(uri_string, options = {})
22
31
  resource = @resource_resolver.resolve(uri_string)
@@ -31,6 +40,28 @@ module Ace
31
40
  end
32
41
  end
33
42
 
43
+ # Explain resolution precedence for a URI
44
+ # Returns every candidate in resolution order plus the rule that picked the winner
45
+ def explain(uri_string)
46
+ candidates = @resource_resolver.resolve_pattern(uri_string)
47
+
48
+ {
49
+ uri: uri_string,
50
+ rule: RESOLUTION_RULE,
51
+ candidates: candidates.each_with_index.map do |resource, index|
52
+ {
53
+ position: index + 1,
54
+ winner: index.zero?,
55
+ path: resource.path,
56
+ source: resource.source.alias_name,
57
+ source_type: resource.source.type.to_s,
58
+ priority: resource.source.priority,
59
+ origin: resource.source.respond_to?(:origin) ? resource.source.origin : nil
60
+ }
61
+ end
62
+ }
63
+ end
64
+
34
65
  # List resources matching a pattern
35
66
  def list(uri_pattern, options = {})
36
67
  resources = @resource_resolver.resolve_pattern(uri_pattern)
@@ -3,7 +3,7 @@
3
3
  module Ace
4
4
  module Support
5
5
  module Nav
6
- VERSION = "0.28.6"
6
+ VERSION = "0.28.8"
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-support-nav
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.28.6
4
+ version: 0.28.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-09-05 00:00:00.000000000 Z
10
+ date: 2026-10-20 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: ace-support-cli
@@ -16,6 +16,9 @@ dependencies:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
18
  version: '0.6'
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: 0.6.10
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -23,6 +26,9 @@ dependencies:
23
26
  - - "~>"
24
27
  - !ruby/object:Gem::Version
25
28
  version: '0.6'
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 0.6.10
26
32
  - !ruby/object:Gem::Dependency
27
33
  name: ace-support-core
28
34
  requirement: !ruby/object:Gem::Requirement