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.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/.ace-defaults/nav/protocols/guide-sources/ace-review.yml +10 -0
  3. data/.ace-defaults/nav/protocols/prompt-sources/ace-review.yml +36 -0
  4. data/.ace-defaults/nav/protocols/tmpl-sources/ace-review.yml +10 -0
  5. data/.ace-defaults/nav/protocols/wfi-sources/ace-review.yml +19 -0
  6. data/.ace-defaults/review/config.yml +79 -0
  7. data/.ace-defaults/review/presets/code-fit.yml +64 -0
  8. data/.ace-defaults/review/presets/code-shine.yml +44 -0
  9. data/.ace-defaults/review/presets/code-valid.yml +39 -0
  10. data/.ace-defaults/review/presets/docs.yml +42 -0
  11. data/.ace-defaults/review/presets/spec.yml +37 -0
  12. data/CHANGELOG.md +1780 -0
  13. data/LICENSE +21 -0
  14. data/README.md +42 -0
  15. data/Rakefile +14 -0
  16. data/exe/ace-review +27 -0
  17. data/exe/ace-review-feedback +17 -0
  18. data/handbook/guides/code-review-process.g.md +234 -0
  19. data/handbook/prompts/base/sections.md +23 -0
  20. data/handbook/prompts/base/system.md +60 -0
  21. data/handbook/prompts/focus/architecture/atom.md +30 -0
  22. data/handbook/prompts/focus/architecture/reflection.md +60 -0
  23. data/handbook/prompts/focus/frameworks/rails.md +40 -0
  24. data/handbook/prompts/focus/frameworks/vue-firebase.md +45 -0
  25. data/handbook/prompts/focus/languages/ruby.md +50 -0
  26. data/handbook/prompts/focus/phase/correctness.md +51 -0
  27. data/handbook/prompts/focus/phase/polish.md +43 -0
  28. data/handbook/prompts/focus/phase/quality.md +42 -0
  29. data/handbook/prompts/focus/quality/performance.md +48 -0
  30. data/handbook/prompts/focus/quality/security.md +47 -0
  31. data/handbook/prompts/focus/scope/docs.md +38 -0
  32. data/handbook/prompts/focus/scope/spec.md +58 -0
  33. data/handbook/prompts/focus/scope/tests.md +36 -0
  34. data/handbook/prompts/format/compact.md +12 -0
  35. data/handbook/prompts/format/detailed.md +39 -0
  36. data/handbook/prompts/format/standard.md +16 -0
  37. data/handbook/prompts/guidelines/icons.md +19 -0
  38. data/handbook/prompts/guidelines/tone.md +21 -0
  39. data/handbook/prompts/synthesis-review-reports.system.md +318 -0
  40. data/handbook/prompts/synthesize-feedback.system.md +147 -0
  41. data/handbook/skills/as-review-apply-feedback/SKILL.md +39 -0
  42. data/handbook/skills/as-review-package/SKILL.md +36 -0
  43. data/handbook/skills/as-review-pr/SKILL.md +38 -0
  44. data/handbook/skills/as-review-run/SKILL.md +30 -0
  45. data/handbook/skills/as-review-verify-feedback/SKILL.md +31 -0
  46. data/handbook/templates/review-tasks/task-review-summary.template.md +148 -0
  47. data/handbook/workflow-instructions/review/apply-feedback.wf.md +212 -0
  48. data/handbook/workflow-instructions/review/package.wf.md +16 -0
  49. data/handbook/workflow-instructions/review/pr.wf.md +284 -0
  50. data/handbook/workflow-instructions/review/run.wf.md +262 -0
  51. data/handbook/workflow-instructions/review/verify-feedback.wf.md +286 -0
  52. data/lib/ace/review/atoms/context_limit_resolver.rb +162 -0
  53. data/lib/ace/review/atoms/diff_boundary_finder.rb +133 -0
  54. data/lib/ace/review/atoms/feedback_id_generator.rb +66 -0
  55. data/lib/ace/review/atoms/feedback_slug_generator.rb +61 -0
  56. data/lib/ace/review/atoms/feedback_state_validator.rb +98 -0
  57. data/lib/ace/review/atoms/pr_comment_formatter.rb +325 -0
  58. data/lib/ace/review/atoms/preset_validator.rb +103 -0
  59. data/lib/ace/review/atoms/priority_filter.rb +115 -0
  60. data/lib/ace/review/atoms/retry_with_backoff.rb +75 -0
  61. data/lib/ace/review/atoms/slug_generator.rb +50 -0
  62. data/lib/ace/review/atoms/token_estimator.rb +86 -0
  63. data/lib/ace/review/cli/commands/feedback/create.rb +173 -0
  64. data/lib/ace/review/cli/commands/feedback/list.rb +280 -0
  65. data/lib/ace/review/cli/commands/feedback/resolve.rb +109 -0
  66. data/lib/ace/review/cli/commands/feedback/session_discovery.rb +70 -0
  67. data/lib/ace/review/cli/commands/feedback/show.rb +177 -0
  68. data/lib/ace/review/cli/commands/feedback/skip.rb +125 -0
  69. data/lib/ace/review/cli/commands/feedback/verify.rb +149 -0
  70. data/lib/ace/review/cli/commands/feedback.rb +79 -0
  71. data/lib/ace/review/cli/commands/review.rb +378 -0
  72. data/lib/ace/review/cli/feedback_cli.rb +71 -0
  73. data/lib/ace/review/cli.rb +103 -0
  74. data/lib/ace/review/errors.rb +146 -0
  75. data/lib/ace/review/models/feedback_item.rb +216 -0
  76. data/lib/ace/review/models/review_options.rb +208 -0
  77. data/lib/ace/review/models/reviewer.rb +181 -0
  78. data/lib/ace/review/molecules/context_composer.rb +123 -0
  79. data/lib/ace/review/molecules/context_extractor.rb +159 -0
  80. data/lib/ace/review/molecules/feedback_directory_manager.rb +183 -0
  81. data/lib/ace/review/molecules/feedback_file_reader.rb +178 -0
  82. data/lib/ace/review/molecules/feedback_file_writer.rb +210 -0
  83. data/lib/ace/review/molecules/feedback_synthesizer.rb +588 -0
  84. data/lib/ace/review/molecules/gh_cli_executor.rb +124 -0
  85. data/lib/ace/review/molecules/gh_comment_poster.rb +205 -0
  86. data/lib/ace/review/molecules/gh_comment_resolver.rb +199 -0
  87. data/lib/ace/review/molecules/gh_pr_comment_fetcher.rb +408 -0
  88. data/lib/ace/review/molecules/gh_pr_fetcher.rb +240 -0
  89. data/lib/ace/review/molecules/llm_executor.rb +142 -0
  90. data/lib/ace/review/molecules/multi_model_executor.rb +278 -0
  91. data/lib/ace/review/molecules/nav_prompt_resolver.rb +145 -0
  92. data/lib/ace/review/molecules/pr_task_spec_resolver.rb +58 -0
  93. data/lib/ace/review/molecules/preset_manager.rb +494 -0
  94. data/lib/ace/review/molecules/prompt_composer.rb +76 -0
  95. data/lib/ace/review/molecules/prompt_resolver.rb +168 -0
  96. data/lib/ace/review/molecules/strategies/adaptive_strategy.rb +193 -0
  97. data/lib/ace/review/molecules/strategies/chunked_strategy.rb +459 -0
  98. data/lib/ace/review/molecules/strategies/full_strategy.rb +114 -0
  99. data/lib/ace/review/molecules/subject_extractor.rb +315 -0
  100. data/lib/ace/review/molecules/subject_filter.rb +199 -0
  101. data/lib/ace/review/molecules/subject_strategy.rb +96 -0
  102. data/lib/ace/review/molecules/task_report_saver.rb +161 -0
  103. data/lib/ace/review/molecules/task_resolver.rb +48 -0
  104. data/lib/ace/review/organisms/feedback_manager.rb +386 -0
  105. data/lib/ace/review/organisms/review_manager.rb +1059 -0
  106. data/lib/ace/review/version.rb +7 -0
  107. data/lib/ace/review.rb +135 -0
  108. 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