ace-review 0.49.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.
- checksums.yaml +7 -0
- data/.ace-defaults/nav/protocols/guide-sources/ace-review.yml +10 -0
- data/.ace-defaults/nav/protocols/prompt-sources/ace-review.yml +36 -0
- data/.ace-defaults/nav/protocols/tmpl-sources/ace-review.yml +10 -0
- data/.ace-defaults/nav/protocols/wfi-sources/ace-review.yml +19 -0
- data/.ace-defaults/review/config.yml +79 -0
- data/.ace-defaults/review/presets/code-fit.yml +64 -0
- data/.ace-defaults/review/presets/code-shine.yml +44 -0
- data/.ace-defaults/review/presets/code-valid.yml +39 -0
- data/.ace-defaults/review/presets/docs.yml +42 -0
- data/.ace-defaults/review/presets/spec.yml +37 -0
- data/CHANGELOG.md +1780 -0
- data/LICENSE +21 -0
- data/README.md +42 -0
- data/Rakefile +14 -0
- data/exe/ace-review +27 -0
- data/exe/ace-review-feedback +17 -0
- data/handbook/guides/code-review-process.g.md +234 -0
- data/handbook/prompts/base/sections.md +23 -0
- data/handbook/prompts/base/system.md +60 -0
- data/handbook/prompts/focus/architecture/atom.md +30 -0
- data/handbook/prompts/focus/architecture/reflection.md +60 -0
- data/handbook/prompts/focus/frameworks/rails.md +40 -0
- data/handbook/prompts/focus/frameworks/vue-firebase.md +45 -0
- data/handbook/prompts/focus/languages/ruby.md +50 -0
- data/handbook/prompts/focus/phase/correctness.md +51 -0
- data/handbook/prompts/focus/phase/polish.md +43 -0
- data/handbook/prompts/focus/phase/quality.md +42 -0
- data/handbook/prompts/focus/quality/performance.md +48 -0
- data/handbook/prompts/focus/quality/security.md +47 -0
- data/handbook/prompts/focus/scope/docs.md +38 -0
- data/handbook/prompts/focus/scope/spec.md +58 -0
- data/handbook/prompts/focus/scope/tests.md +36 -0
- data/handbook/prompts/format/compact.md +12 -0
- data/handbook/prompts/format/detailed.md +39 -0
- data/handbook/prompts/format/standard.md +16 -0
- data/handbook/prompts/guidelines/icons.md +19 -0
- data/handbook/prompts/guidelines/tone.md +21 -0
- data/handbook/prompts/synthesis-review-reports.system.md +318 -0
- data/handbook/prompts/synthesize-feedback.system.md +147 -0
- data/handbook/skills/as-review-apply-feedback/SKILL.md +39 -0
- data/handbook/skills/as-review-package/SKILL.md +36 -0
- data/handbook/skills/as-review-pr/SKILL.md +38 -0
- data/handbook/skills/as-review-run/SKILL.md +30 -0
- data/handbook/skills/as-review-verify-feedback/SKILL.md +31 -0
- data/handbook/templates/review-tasks/task-review-summary.template.md +148 -0
- data/handbook/workflow-instructions/review/apply-feedback.wf.md +212 -0
- data/handbook/workflow-instructions/review/package.wf.md +16 -0
- data/handbook/workflow-instructions/review/pr.wf.md +284 -0
- data/handbook/workflow-instructions/review/run.wf.md +262 -0
- data/handbook/workflow-instructions/review/verify-feedback.wf.md +286 -0
- data/lib/ace/review/atoms/context_limit_resolver.rb +162 -0
- data/lib/ace/review/atoms/diff_boundary_finder.rb +133 -0
- data/lib/ace/review/atoms/feedback_id_generator.rb +66 -0
- data/lib/ace/review/atoms/feedback_slug_generator.rb +61 -0
- data/lib/ace/review/atoms/feedback_state_validator.rb +98 -0
- data/lib/ace/review/atoms/pr_comment_formatter.rb +325 -0
- data/lib/ace/review/atoms/preset_validator.rb +103 -0
- data/lib/ace/review/atoms/priority_filter.rb +115 -0
- data/lib/ace/review/atoms/retry_with_backoff.rb +75 -0
- data/lib/ace/review/atoms/slug_generator.rb +50 -0
- data/lib/ace/review/atoms/token_estimator.rb +86 -0
- data/lib/ace/review/cli/commands/feedback/create.rb +173 -0
- data/lib/ace/review/cli/commands/feedback/list.rb +280 -0
- data/lib/ace/review/cli/commands/feedback/resolve.rb +109 -0
- data/lib/ace/review/cli/commands/feedback/session_discovery.rb +70 -0
- data/lib/ace/review/cli/commands/feedback/show.rb +177 -0
- data/lib/ace/review/cli/commands/feedback/skip.rb +125 -0
- data/lib/ace/review/cli/commands/feedback/verify.rb +149 -0
- data/lib/ace/review/cli/commands/feedback.rb +79 -0
- data/lib/ace/review/cli/commands/review.rb +378 -0
- data/lib/ace/review/cli/feedback_cli.rb +71 -0
- data/lib/ace/review/cli.rb +103 -0
- data/lib/ace/review/errors.rb +146 -0
- data/lib/ace/review/models/feedback_item.rb +216 -0
- data/lib/ace/review/models/review_options.rb +208 -0
- data/lib/ace/review/models/reviewer.rb +181 -0
- data/lib/ace/review/molecules/context_composer.rb +123 -0
- data/lib/ace/review/molecules/context_extractor.rb +159 -0
- data/lib/ace/review/molecules/feedback_directory_manager.rb +183 -0
- data/lib/ace/review/molecules/feedback_file_reader.rb +178 -0
- data/lib/ace/review/molecules/feedback_file_writer.rb +210 -0
- data/lib/ace/review/molecules/feedback_synthesizer.rb +588 -0
- data/lib/ace/review/molecules/gh_cli_executor.rb +124 -0
- data/lib/ace/review/molecules/gh_comment_poster.rb +205 -0
- data/lib/ace/review/molecules/gh_comment_resolver.rb +199 -0
- data/lib/ace/review/molecules/gh_pr_comment_fetcher.rb +408 -0
- data/lib/ace/review/molecules/gh_pr_fetcher.rb +240 -0
- data/lib/ace/review/molecules/llm_executor.rb +142 -0
- data/lib/ace/review/molecules/multi_model_executor.rb +278 -0
- data/lib/ace/review/molecules/nav_prompt_resolver.rb +145 -0
- data/lib/ace/review/molecules/pr_task_spec_resolver.rb +58 -0
- data/lib/ace/review/molecules/preset_manager.rb +494 -0
- data/lib/ace/review/molecules/prompt_composer.rb +76 -0
- data/lib/ace/review/molecules/prompt_resolver.rb +168 -0
- data/lib/ace/review/molecules/strategies/adaptive_strategy.rb +193 -0
- data/lib/ace/review/molecules/strategies/chunked_strategy.rb +459 -0
- data/lib/ace/review/molecules/strategies/full_strategy.rb +114 -0
- data/lib/ace/review/molecules/subject_extractor.rb +315 -0
- data/lib/ace/review/molecules/subject_filter.rb +199 -0
- data/lib/ace/review/molecules/subject_strategy.rb +96 -0
- data/lib/ace/review/molecules/task_report_saver.rb +161 -0
- data/lib/ace/review/molecules/task_resolver.rb +48 -0
- data/lib/ace/review/organisms/feedback_manager.rb +386 -0
- data/lib/ace/review/organisms/review_manager.rb +1059 -0
- data/lib/ace/review/version.rb +7 -0
- data/lib/ace/review.rb +135 -0
- metadata +351 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ace/support/fs"
|
|
4
|
+
|
|
5
|
+
module Ace
|
|
6
|
+
module Review
|
|
7
|
+
module CLI
|
|
8
|
+
module Commands
|
|
9
|
+
module FeedbackSubcommands
|
|
10
|
+
# Shared session discovery logic for feedback CLI commands
|
|
11
|
+
#
|
|
12
|
+
# This module provides common methods for resolving review sessions
|
|
13
|
+
# from options or finding the latest session in the cache.
|
|
14
|
+
module SessionDiscovery
|
|
15
|
+
# Resolve feedback path from session context
|
|
16
|
+
#
|
|
17
|
+
# Priority:
|
|
18
|
+
# 1. --session flag (explicit session directory)
|
|
19
|
+
# 2. Most recent session in cache directory (default)
|
|
20
|
+
#
|
|
21
|
+
# @param options [Hash] Command options
|
|
22
|
+
# @return [String, nil] Base path for feedback directory
|
|
23
|
+
def resolve_feedback_path(options)
|
|
24
|
+
# Check explicit session flag first
|
|
25
|
+
if options[:session]
|
|
26
|
+
session_path = File.expand_path(options[:session])
|
|
27
|
+
return session_path if Dir.exist?(session_path)
|
|
28
|
+
|
|
29
|
+
raise Ace::Support::Cli::Error.new("Session not found: #{session_path}")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Default: use most recent session
|
|
33
|
+
find_latest_session
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Resolve session directory from options
|
|
37
|
+
#
|
|
38
|
+
# @param options [Hash] Command options
|
|
39
|
+
# @return [String, nil] Session directory path
|
|
40
|
+
def resolve_session_dir(options)
|
|
41
|
+
resolve_feedback_path(options)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Find the most recent session directory
|
|
45
|
+
#
|
|
46
|
+
# @return [String, nil] Path to latest session or nil
|
|
47
|
+
def find_latest_session
|
|
48
|
+
sessions = find_all_sessions
|
|
49
|
+
sessions.first
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Find all session directories
|
|
53
|
+
#
|
|
54
|
+
# @return [Array<String>] All session directory paths, sorted by mtime (newest first)
|
|
55
|
+
def find_all_sessions
|
|
56
|
+
root = Ace::Support::Fs::Molecules::ProjectRootFinder.find_or_current
|
|
57
|
+
cache_dir = File.join(root, ".ace-local", "review", "sessions")
|
|
58
|
+
cache_dir = File.join(root, ".cache", "ace-review", "sessions") unless Dir.exist?(cache_dir)
|
|
59
|
+
return [] unless Dir.exist?(cache_dir)
|
|
60
|
+
|
|
61
|
+
Dir.glob(File.join(cache_dir, "review-*"))
|
|
62
|
+
.select { |p| File.directory?(p) }
|
|
63
|
+
.sort_by { |p| -File.mtime(p).to_i }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ace/support/cli"
|
|
4
|
+
require "ace/core"
|
|
5
|
+
require_relative "session_discovery"
|
|
6
|
+
|
|
7
|
+
module Ace
|
|
8
|
+
module Review
|
|
9
|
+
module CLI
|
|
10
|
+
module Commands
|
|
11
|
+
module FeedbackSubcommands
|
|
12
|
+
# ace-support-cli Command class for feedback show
|
|
13
|
+
#
|
|
14
|
+
# Displays detailed information about a feedback item.
|
|
15
|
+
class Show < Ace::Support::Cli::Command
|
|
16
|
+
include Ace::Support::Cli::Base
|
|
17
|
+
include SessionDiscovery
|
|
18
|
+
|
|
19
|
+
desc <<~DESC.strip
|
|
20
|
+
Show detailed information about a feedback item
|
|
21
|
+
|
|
22
|
+
Displays full content including finding, context, research notes,
|
|
23
|
+
and resolution (if any). Supports partial ID matching with minimum
|
|
24
|
+
3 characters.
|
|
25
|
+
DESC
|
|
26
|
+
|
|
27
|
+
example [
|
|
28
|
+
"abc123 # Show by full ID (latest session)",
|
|
29
|
+
"abc # Show by partial ID (min 3 chars)",
|
|
30
|
+
"abc123 --session .ace-local/review/sessions/review-xyz # From specific session"
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
argument :id, required: true, desc: "Feedback ID (minimum 3 characters for partial match)"
|
|
34
|
+
option :session, type: :string, desc: "Session directory containing feedback"
|
|
35
|
+
|
|
36
|
+
# Standard options
|
|
37
|
+
option :quiet, type: :boolean, aliases: %w[-q], desc: "Suppress non-essential output"
|
|
38
|
+
option :verbose, type: :boolean, aliases: %w[-v], desc: "Show verbose output"
|
|
39
|
+
option :debug, type: :boolean, aliases: %w[-d], desc: "Show debug output"
|
|
40
|
+
|
|
41
|
+
def call(id:, **options)
|
|
42
|
+
# Validate ID length
|
|
43
|
+
if id.length < 3
|
|
44
|
+
raise Ace::Support::Cli::Error.new("ID must be at least 3 characters for matching.")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Resolve feedback path from session context
|
|
48
|
+
base_path = resolve_feedback_path(options)
|
|
49
|
+
|
|
50
|
+
unless base_path
|
|
51
|
+
raise Ace::Support::Cli::Error.new("No session found. Run a review first or use --session to specify path.")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
debug_log("Feedback base path: #{base_path}", options)
|
|
55
|
+
|
|
56
|
+
# Find item by ID (supports partial matching)
|
|
57
|
+
item = find_item_by_id(base_path, id, include_archived: true)
|
|
58
|
+
|
|
59
|
+
unless item
|
|
60
|
+
raise Ace::Support::Cli::Error.new("Feedback item not found: #{id}")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
display_item(item)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
# Find item by ID with partial matching
|
|
69
|
+
#
|
|
70
|
+
# @param base_path [String] Base path for feedback directory
|
|
71
|
+
# @param id [String] Full or partial ID
|
|
72
|
+
# @param include_archived [Boolean] Whether to search archived items
|
|
73
|
+
# @return [Models::FeedbackItem, nil] Found item or nil
|
|
74
|
+
def find_item_by_id(base_path, id, include_archived: false)
|
|
75
|
+
manager = Organisms::FeedbackManager.new
|
|
76
|
+
dir_manager = manager.directory_manager
|
|
77
|
+
file_reader = manager.file_reader
|
|
78
|
+
|
|
79
|
+
# Search in active directory
|
|
80
|
+
feedback_dir = dir_manager.feedback_path(base_path)
|
|
81
|
+
item = search_directory_for_id(feedback_dir, id, file_reader)
|
|
82
|
+
return item if item
|
|
83
|
+
|
|
84
|
+
# Search in archive if requested
|
|
85
|
+
if include_archived
|
|
86
|
+
archive_dir = dir_manager.archive_path(base_path)
|
|
87
|
+
item = search_directory_for_id(archive_dir, id, file_reader) if Dir.exist?(archive_dir)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
item
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Search a directory for an item matching the ID
|
|
94
|
+
def search_directory_for_id(directory, id, file_reader)
|
|
95
|
+
return nil unless Dir.exist?(directory)
|
|
96
|
+
|
|
97
|
+
# Find files matching ID pattern (supports partial)
|
|
98
|
+
pattern = File.join(directory, "#{id}*.s.md")
|
|
99
|
+
files = Dir.glob(pattern)
|
|
100
|
+
|
|
101
|
+
# If multiple matches, require more specific ID
|
|
102
|
+
if files.length > 1
|
|
103
|
+
raise Ace::Support::Cli::Error.new(
|
|
104
|
+
"Multiple items match '#{id}': #{files.map { |f| File.basename(f).split("-").first }.join(", ")}. " \
|
|
105
|
+
"Please provide more characters."
|
|
106
|
+
)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
return nil if files.empty?
|
|
110
|
+
|
|
111
|
+
result = file_reader.read(files.first)
|
|
112
|
+
result[:success] ? result[:feedback_item] : nil
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Display item details
|
|
116
|
+
def display_item(item)
|
|
117
|
+
puts "=" * 60
|
|
118
|
+
puts "Feedback: #{item.id}"
|
|
119
|
+
puts "=" * 60
|
|
120
|
+
puts
|
|
121
|
+
puts "Title: #{item.title}"
|
|
122
|
+
puts "Status: #{status_with_icon(item.status)}"
|
|
123
|
+
puts "Priority: #{item.priority}"
|
|
124
|
+
puts "Reviewer: #{item.reviewer}"
|
|
125
|
+
puts "Created: #{item.created}"
|
|
126
|
+
puts "Updated: #{item.updated}"
|
|
127
|
+
puts
|
|
128
|
+
|
|
129
|
+
if item.files && !item.files.empty?
|
|
130
|
+
puts "Files:"
|
|
131
|
+
item.files.each { |f| puts " - #{f}" }
|
|
132
|
+
puts
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
if item.finding
|
|
136
|
+
puts "--- Finding ---"
|
|
137
|
+
puts item.finding
|
|
138
|
+
puts
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
if item.context
|
|
142
|
+
puts "--- Context ---"
|
|
143
|
+
puts item.context
|
|
144
|
+
puts
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
if item.research
|
|
148
|
+
puts "--- Research ---"
|
|
149
|
+
puts item.research
|
|
150
|
+
puts
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
if item.resolution
|
|
154
|
+
puts "--- Resolution ---"
|
|
155
|
+
puts item.resolution
|
|
156
|
+
puts
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Status with emoji icon
|
|
161
|
+
def status_with_icon(status)
|
|
162
|
+
icon = case status
|
|
163
|
+
when "draft" then "[D]"
|
|
164
|
+
when "pending" then "[P]"
|
|
165
|
+
when "invalid" then "[X]"
|
|
166
|
+
when "skip" then "[S]"
|
|
167
|
+
when "done" then "[+]"
|
|
168
|
+
else "[?]"
|
|
169
|
+
end
|
|
170
|
+
"#{icon} #{status}"
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ace/support/cli"
|
|
4
|
+
require "ace/core"
|
|
5
|
+
require_relative "session_discovery"
|
|
6
|
+
|
|
7
|
+
module Ace
|
|
8
|
+
module Review
|
|
9
|
+
module CLI
|
|
10
|
+
module Commands
|
|
11
|
+
module FeedbackSubcommands
|
|
12
|
+
# ace-support-cli Command class for feedback skip
|
|
13
|
+
#
|
|
14
|
+
# Skips a feedback item (marks as not applicable).
|
|
15
|
+
class Skip < Ace::Support::Cli::Command
|
|
16
|
+
include Ace::Support::Cli::Base
|
|
17
|
+
include SessionDiscovery
|
|
18
|
+
|
|
19
|
+
desc <<~DESC.strip
|
|
20
|
+
Skip a feedback item (DEPRECATED - use verify --skip)
|
|
21
|
+
|
|
22
|
+
[DEPRECATED] This command is deprecated. Use: verify --skip --research "..."
|
|
23
|
+
|
|
24
|
+
Marks a draft or pending feedback item as skipped and archives it.
|
|
25
|
+
Use when the finding is correct but you are not fixing it in this context.
|
|
26
|
+
|
|
27
|
+
Examples of when to skip:
|
|
28
|
+
- Design decision: Intentionally choosing this approach
|
|
29
|
+
- Deferred: Correct issue, but tracking in a separate task
|
|
30
|
+
- Duplicate: Already covered by another feedback item
|
|
31
|
+
|
|
32
|
+
For false positives (incorrect findings), use: verify --invalid
|
|
33
|
+
DESC
|
|
34
|
+
|
|
35
|
+
example [
|
|
36
|
+
"[DEPRECATED] Use: verify --skip instead",
|
|
37
|
+
"abc123 # Skip without reason",
|
|
38
|
+
'abc123 --reason "Design: using polling for simplicity" # Design decision',
|
|
39
|
+
'abc123 --reason "Tracked in task 253" # Deferred to separate task',
|
|
40
|
+
'abc123 --reason "Duplicate of abc120" # Already covered',
|
|
41
|
+
"abc123 --session .ace-local/review/sessions/review-xyz"
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
argument :id, required: true, desc: "Feedback ID"
|
|
45
|
+
option :reason, type: :string, desc: "Reason for skipping (aliased to research)"
|
|
46
|
+
option :research, type: :string, desc: "Research notes (preferred over --reason)"
|
|
47
|
+
option :session, type: :string, desc: "Session directory containing feedback"
|
|
48
|
+
|
|
49
|
+
# Standard options
|
|
50
|
+
option :quiet, type: :boolean, aliases: %w[-q], desc: "Suppress non-essential output"
|
|
51
|
+
option :verbose, type: :boolean, aliases: %w[-v], desc: "Show verbose output"
|
|
52
|
+
option :debug, type: :boolean, aliases: %w[-d], desc: "Show debug output"
|
|
53
|
+
|
|
54
|
+
def call(id:, **options)
|
|
55
|
+
# Show deprecation warning (unless quiet)
|
|
56
|
+
unless quiet?(options)
|
|
57
|
+
puts "[DEPRECATED] 'skip' command is deprecated. Use: verify --skip --research \"...\""
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Map --reason to --research for consistency
|
|
61
|
+
research = options[:research] || options[:reason]
|
|
62
|
+
|
|
63
|
+
# Resolve feedback path from session context
|
|
64
|
+
base_path = resolve_feedback_path(options)
|
|
65
|
+
|
|
66
|
+
unless base_path
|
|
67
|
+
raise Ace::Support::Cli::Error.new("No session found. Run a review first or use --session to specify path.")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
debug_log("Feedback base path: #{base_path}", options)
|
|
71
|
+
|
|
72
|
+
# Find item first for partial ID matching
|
|
73
|
+
resolved_id = resolve_full_id(base_path, id)
|
|
74
|
+
|
|
75
|
+
unless resolved_id
|
|
76
|
+
raise Ace::Support::Cli::Error.new("Feedback item not found: #{id}")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Skip the item using the new verify method with skip: true
|
|
80
|
+
manager = Organisms::FeedbackManager.new
|
|
81
|
+
result = manager.verify(
|
|
82
|
+
base_path,
|
|
83
|
+
resolved_id,
|
|
84
|
+
skip: true,
|
|
85
|
+
research: research
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if result[:success]
|
|
89
|
+
puts "Feedback #{resolved_id} skipped and archived."
|
|
90
|
+
puts "Research: #{research}" if research && !quiet?(options)
|
|
91
|
+
else
|
|
92
|
+
raise Ace::Support::Cli::Error.new(result[:error])
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
# Resolve partial ID to full ID
|
|
99
|
+
def resolve_full_id(base_path, partial_id)
|
|
100
|
+
manager = Organisms::FeedbackManager.new
|
|
101
|
+
feedback_dir = manager.directory_manager.feedback_path(base_path)
|
|
102
|
+
return nil unless Dir.exist?(feedback_dir)
|
|
103
|
+
|
|
104
|
+
# Find files matching ID pattern
|
|
105
|
+
pattern = File.join(feedback_dir, "#{partial_id}*.s.md")
|
|
106
|
+
files = Dir.glob(pattern)
|
|
107
|
+
|
|
108
|
+
if files.length > 1
|
|
109
|
+
raise Ace::Support::Cli::Error.new(
|
|
110
|
+
"Multiple items match '#{partial_id}': #{files.map { |f| File.basename(f).split("-").first }.join(", ")}. " \
|
|
111
|
+
"Please provide more characters."
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
return nil if files.empty?
|
|
116
|
+
|
|
117
|
+
# Extract full ID from filename
|
|
118
|
+
File.basename(files.first).split("-").first
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ace/support/cli"
|
|
4
|
+
require "ace/core"
|
|
5
|
+
require_relative "session_discovery"
|
|
6
|
+
|
|
7
|
+
module Ace
|
|
8
|
+
module Review
|
|
9
|
+
module CLI
|
|
10
|
+
module Commands
|
|
11
|
+
module FeedbackSubcommands
|
|
12
|
+
# ace-support-cli Command class for feedback verify
|
|
13
|
+
#
|
|
14
|
+
# Verifies a draft feedback item by marking it as valid or invalid.
|
|
15
|
+
class Verify < Ace::Support::Cli::Command
|
|
16
|
+
include Ace::Support::Cli::Base
|
|
17
|
+
include SessionDiscovery
|
|
18
|
+
|
|
19
|
+
desc <<~DESC.strip
|
|
20
|
+
Verify a draft feedback item
|
|
21
|
+
|
|
22
|
+
Marks a draft feedback item as valid, invalid, or skipped.
|
|
23
|
+
|
|
24
|
+
Use --valid when: The finding is correct and needs to be fixed
|
|
25
|
+
Use --invalid when: The finding is a false positive (factually incorrect)
|
|
26
|
+
Use --skip when: The finding is correct but not being fixed
|
|
27
|
+
|
|
28
|
+
Examples of --invalid (false positive):
|
|
29
|
+
- Claimed code doesn't exist, but it does
|
|
30
|
+
- Claimed missing validation, but it exists elsewhere
|
|
31
|
+
- Claimed issue in CI, but code doesn't run in CI
|
|
32
|
+
|
|
33
|
+
Examples of --skip (correct but not fixing):
|
|
34
|
+
- Design decision: Intentionally choosing this approach
|
|
35
|
+
- Deferred: Correct issue, but tracking in a separate task
|
|
36
|
+
- Duplicate: Already covered by another feedback item
|
|
37
|
+
DESC
|
|
38
|
+
|
|
39
|
+
example [
|
|
40
|
+
"abc123 --valid # Correct issue, needs fix",
|
|
41
|
+
"abc123 --invalid # False positive (incorrect)",
|
|
42
|
+
"abc123 --skip # Correct but not fixing",
|
|
43
|
+
'abc123 --valid --research "Confirmed: missing null check at line 42"',
|
|
44
|
+
'abc123 --invalid --research "False positive: validation exists in middleware"',
|
|
45
|
+
'abc123 --skip --research "Design: using polling for simplicity"',
|
|
46
|
+
'abc123 --skip --research "Tracked in task 253"',
|
|
47
|
+
'abc123 --skip --research "Duplicate of abc120"',
|
|
48
|
+
"abc123 --valid --session .ace-local/review/sessions/review-xyz"
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
argument :id, required: true, desc: "Feedback ID"
|
|
52
|
+
option :valid, type: :boolean, desc: "Mark as valid (moves to pending)"
|
|
53
|
+
option :invalid, type: :boolean, desc: "Mark as invalid (archives)"
|
|
54
|
+
option :skip, type: :boolean, desc: "Mark as skipped (archives)"
|
|
55
|
+
option :research, type: :string, desc: "Add research notes (what we learned/decided)"
|
|
56
|
+
option :session, type: :string, desc: "Session directory containing feedback"
|
|
57
|
+
|
|
58
|
+
# Standard options
|
|
59
|
+
option :quiet, type: :boolean, aliases: %w[-q], desc: "Suppress non-essential output"
|
|
60
|
+
option :verbose, type: :boolean, aliases: %w[-v], desc: "Show verbose output"
|
|
61
|
+
option :debug, type: :boolean, aliases: %w[-d], desc: "Show debug output"
|
|
62
|
+
|
|
63
|
+
def call(id:, **options)
|
|
64
|
+
# Validate: must specify exactly one of --valid, --invalid, or --skip
|
|
65
|
+
mode_count = [options[:valid], options[:invalid], options[:skip]].count { |v| v }
|
|
66
|
+
|
|
67
|
+
if mode_count > 1
|
|
68
|
+
raise Ace::Support::Cli::Error.new("Cannot specify multiple modes. Use exactly one of: --valid, --invalid, --skip.")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
unless mode_count == 1
|
|
72
|
+
raise Ace::Support::Cli::Error.new("Must specify exactly one of: --valid, --invalid, --skip.")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Resolve feedback path from session context
|
|
76
|
+
base_path = resolve_feedback_path(options)
|
|
77
|
+
|
|
78
|
+
unless base_path
|
|
79
|
+
raise Ace::Support::Cli::Error.new("No session found. Run a review first or use --session to specify path.")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
debug_log("Feedback base path: #{base_path}", options)
|
|
83
|
+
|
|
84
|
+
# Find item first for partial ID matching
|
|
85
|
+
resolved_id = resolve_full_id(base_path, id)
|
|
86
|
+
|
|
87
|
+
unless resolved_id
|
|
88
|
+
raise Ace::Support::Cli::Error.new("Feedback item not found: #{id}")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Verify the item
|
|
92
|
+
manager = Organisms::FeedbackManager.new
|
|
93
|
+
result = manager.verify(
|
|
94
|
+
base_path,
|
|
95
|
+
resolved_id,
|
|
96
|
+
valid: if options[:valid] == true
|
|
97
|
+
true
|
|
98
|
+
else
|
|
99
|
+
((options[:invalid] == true) ? false : nil)
|
|
100
|
+
end,
|
|
101
|
+
skip: (options[:skip] == true) ? true : nil,
|
|
102
|
+
research: options[:research]
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if result[:success]
|
|
106
|
+
status = if options[:valid]
|
|
107
|
+
"valid (pending)"
|
|
108
|
+
elsif options[:invalid]
|
|
109
|
+
"invalid (archived)"
|
|
110
|
+
else
|
|
111
|
+
"skipped (archived)"
|
|
112
|
+
end
|
|
113
|
+
puts "Feedback #{resolved_id} marked as #{status}."
|
|
114
|
+
puts "Research: #{options[:research]}" if options[:research] && !quiet?(options)
|
|
115
|
+
else
|
|
116
|
+
raise Ace::Support::Cli::Error.new(result[:error])
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
# Resolve partial ID to full ID
|
|
123
|
+
def resolve_full_id(base_path, partial_id)
|
|
124
|
+
manager = Organisms::FeedbackManager.new
|
|
125
|
+
feedback_dir = manager.directory_manager.feedback_path(base_path)
|
|
126
|
+
return nil unless Dir.exist?(feedback_dir)
|
|
127
|
+
|
|
128
|
+
# Find files matching ID pattern
|
|
129
|
+
pattern = File.join(feedback_dir, "#{partial_id}*.s.md")
|
|
130
|
+
files = Dir.glob(pattern)
|
|
131
|
+
|
|
132
|
+
if files.length > 1
|
|
133
|
+
raise Ace::Support::Cli::Error.new(
|
|
134
|
+
"Multiple items match '#{partial_id}': #{files.map { |f| File.basename(f).split("-").first }.join(", ")}. " \
|
|
135
|
+
"Please provide more characters."
|
|
136
|
+
)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
return nil if files.empty?
|
|
140
|
+
|
|
141
|
+
# Extract full ID from filename
|
|
142
|
+
File.basename(files.first).split("-").first
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ace/support/cli"
|
|
4
|
+
require "ace/core"
|
|
5
|
+
|
|
6
|
+
module Ace
|
|
7
|
+
module Review
|
|
8
|
+
module CLI
|
|
9
|
+
module Commands
|
|
10
|
+
# Base feedback command that shows help for subcommands
|
|
11
|
+
#
|
|
12
|
+
# This handles the base "feedback" command:
|
|
13
|
+
# - No arguments: Show help with available subcommands
|
|
14
|
+
#
|
|
15
|
+
# All subcommands (list, show, verify, skip, resolve) are handled
|
|
16
|
+
# by nested ace-support-cli commands in CLI::Commands::FeedbackSubcommands:: namespace.
|
|
17
|
+
class Feedback < Ace::Support::Cli::Command
|
|
18
|
+
include Ace::Support::Cli::Base
|
|
19
|
+
|
|
20
|
+
desc <<~DESC.strip
|
|
21
|
+
Manage feedback items from code reviews
|
|
22
|
+
|
|
23
|
+
SYNTAX:
|
|
24
|
+
ace-review feedback <subcommand> [options]
|
|
25
|
+
|
|
26
|
+
SUBCOMMANDS:
|
|
27
|
+
|
|
28
|
+
Use 'ace-review feedback <subcommand> --help' for details:
|
|
29
|
+
- create: Create feedback items from review reports
|
|
30
|
+
- list: List feedback items with optional filters
|
|
31
|
+
- show: Show feedback item details
|
|
32
|
+
- verify: Verify a draft feedback item (mark valid/invalid/skip)
|
|
33
|
+
- resolve: Resolve a pending feedback item
|
|
34
|
+
|
|
35
|
+
EXAMPLES:
|
|
36
|
+
|
|
37
|
+
# Create feedback from most recent session
|
|
38
|
+
$ ace-review feedback create
|
|
39
|
+
|
|
40
|
+
# List all pending feedback items
|
|
41
|
+
$ ace-review feedback list --status pending
|
|
42
|
+
|
|
43
|
+
# Show a specific feedback item
|
|
44
|
+
$ ace-review feedback show abc123
|
|
45
|
+
|
|
46
|
+
# Verify a draft item as valid
|
|
47
|
+
$ ace-review feedback verify abc123 --valid
|
|
48
|
+
|
|
49
|
+
# Resolve a pending item
|
|
50
|
+
$ ace-review feedback resolve abc123 --resolution "Fixed in commit def456"
|
|
51
|
+
DESC
|
|
52
|
+
|
|
53
|
+
example [
|
|
54
|
+
"create # Create from most recent session",
|
|
55
|
+
"list --status pending # List pending items",
|
|
56
|
+
"show abc123 # Show item details",
|
|
57
|
+
"verify abc123 --valid # Mark as valid",
|
|
58
|
+
"verify abc123 --skip # Skip (not applicable)",
|
|
59
|
+
'resolve abc123 --resolution "Fixed"'
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
def call(**options)
|
|
63
|
+
# Show help when no subcommand is provided
|
|
64
|
+
puts "Usage: ace-review feedback <subcommand> [options]"
|
|
65
|
+
puts
|
|
66
|
+
puts "Subcommands:"
|
|
67
|
+
puts " create Create feedback items from review reports"
|
|
68
|
+
puts " list List feedback items"
|
|
69
|
+
puts " show Show feedback item details"
|
|
70
|
+
puts " verify Verify a draft feedback item (valid/invalid/skip)"
|
|
71
|
+
puts " resolve Resolve a pending feedback item"
|
|
72
|
+
puts
|
|
73
|
+
puts "Run 'ace-review feedback <subcommand> --help' for more information."
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|