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 +4 -4
- data/CHANGELOG.md +13 -0
- data/exe/ace-nav +1 -1
- data/lib/ace/support/nav/cli/commands/resolve.rb +35 -0
- data/lib/ace/support/nav/models/protocol_source.rb +12 -1
- data/lib/ace/support/nav/molecules/config_loader.rb +2 -0
- data/lib/ace/support/nav/molecules/protocol_scanner.rb +2 -0
- data/lib/ace/support/nav/molecules/source_registry.rb +13 -4
- data/lib/ace/support/nav/organisms/command_delegator.rb +9 -1
- data/lib/ace/support/nav/organisms/navigation_engine.rb +31 -0
- data/lib/ace/support/nav/version.rb +1 -1
- metadata +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 360819ca45c3cd71302707c5bc82a4a6dc6cf867d02ca96796ba04c24ddc4f45
|
|
4
|
+
data.tar.gz: 35feb7c2fb3c43a357fe25f9aab9ec3577d853ed4ea8224cfc4fbd09a3c69a6f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
@@ -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.
|
|
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 =
|
|
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)
|
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.
|
|
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-
|
|
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
|