ace-idea 0.18.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 (52) hide show
  1. checksums.yaml +7 -0
  2. data/.ace-defaults/idea/config.yml +21 -0
  3. data/.ace-defaults/nav/protocols/wfi-sources/ace-idea.yml +19 -0
  4. data/CHANGELOG.md +387 -0
  5. data/README.md +42 -0
  6. data/Rakefile +13 -0
  7. data/docs/demo/ace-idea-getting-started.gif +0 -0
  8. data/docs/demo/ace-idea-getting-started.tape.yml +44 -0
  9. data/docs/demo/fixtures/README.md +3 -0
  10. data/docs/demo/fixtures/sample.txt +1 -0
  11. data/docs/getting-started.md +102 -0
  12. data/docs/handbook.md +39 -0
  13. data/docs/usage.md +320 -0
  14. data/exe/ace-idea +22 -0
  15. data/handbook/skills/as-idea-capture/SKILL.md +25 -0
  16. data/handbook/skills/as-idea-capture-features/SKILL.md +26 -0
  17. data/handbook/skills/as-idea-review/SKILL.md +26 -0
  18. data/handbook/workflow-instructions/idea/capture-features.wf.md +243 -0
  19. data/handbook/workflow-instructions/idea/capture.wf.md +270 -0
  20. data/handbook/workflow-instructions/idea/prioritize.wf.md +223 -0
  21. data/handbook/workflow-instructions/idea/review.wf.md +93 -0
  22. data/lib/ace/idea/atoms/idea_file_pattern.rb +40 -0
  23. data/lib/ace/idea/atoms/idea_frontmatter_defaults.rb +39 -0
  24. data/lib/ace/idea/atoms/idea_id_formatter.rb +37 -0
  25. data/lib/ace/idea/atoms/idea_validation_rules.rb +89 -0
  26. data/lib/ace/idea/atoms/slug_sanitizer_adapter.rb +6 -0
  27. data/lib/ace/idea/cli/commands/create.rb +98 -0
  28. data/lib/ace/idea/cli/commands/doctor.rb +206 -0
  29. data/lib/ace/idea/cli/commands/list.rb +62 -0
  30. data/lib/ace/idea/cli/commands/show.rb +55 -0
  31. data/lib/ace/idea/cli/commands/status.rb +61 -0
  32. data/lib/ace/idea/cli/commands/update.rb +118 -0
  33. data/lib/ace/idea/cli.rb +75 -0
  34. data/lib/ace/idea/models/idea.rb +39 -0
  35. data/lib/ace/idea/molecules/idea_clipboard_reader.rb +117 -0
  36. data/lib/ace/idea/molecules/idea_config_loader.rb +93 -0
  37. data/lib/ace/idea/molecules/idea_creator.rb +248 -0
  38. data/lib/ace/idea/molecules/idea_display_formatter.rb +165 -0
  39. data/lib/ace/idea/molecules/idea_doctor_fixer.rb +504 -0
  40. data/lib/ace/idea/molecules/idea_doctor_reporter.rb +264 -0
  41. data/lib/ace/idea/molecules/idea_frontmatter_validator.rb +137 -0
  42. data/lib/ace/idea/molecules/idea_llm_enhancer.rb +177 -0
  43. data/lib/ace/idea/molecules/idea_loader.rb +124 -0
  44. data/lib/ace/idea/molecules/idea_mover.rb +78 -0
  45. data/lib/ace/idea/molecules/idea_resolver.rb +57 -0
  46. data/lib/ace/idea/molecules/idea_scanner.rb +56 -0
  47. data/lib/ace/idea/molecules/idea_structure_validator.rb +157 -0
  48. data/lib/ace/idea/organisms/idea_doctor.rb +207 -0
  49. data/lib/ace/idea/organisms/idea_manager.rb +251 -0
  50. data/lib/ace/idea/version.rb +7 -0
  51. data/lib/ace/idea.rb +37 -0
  52. metadata +166 -0
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "idea_id_formatter"
4
+
5
+ module Ace
6
+ module Idea
7
+ module Atoms
8
+ # Pure validation predicates and constants for idea health checking.
9
+ # Used by doctor validators to determine correctness of frontmatter,
10
+ # file structure, and scope/status consistency.
11
+ module IdeaValidationRules
12
+ VALID_STATUSES = %w[pending in-progress done obsolete].freeze
13
+ TERMINAL_STATUSES = %w[done obsolete].freeze
14
+ REQUIRED_FIELDS = %w[id status title].freeze
15
+ RECOMMENDED_FIELDS = %w[tags created_at].freeze
16
+
17
+ # Check if a status string is valid
18
+ # @param status [String] Status to validate
19
+ # @return [Boolean]
20
+ def self.valid_status?(status)
21
+ VALID_STATUSES.include?(status.to_s)
22
+ end
23
+
24
+ # Check if a status is terminal (belongs in _archive)
25
+ # @param status [String] Status to check
26
+ # @return [Boolean]
27
+ def self.terminal_status?(status)
28
+ TERMINAL_STATUSES.include?(status.to_s)
29
+ end
30
+
31
+ # Check if an ID string is a valid b36ts idea ID
32
+ # @param id [String] ID to validate
33
+ # @return [Boolean]
34
+ def self.valid_id?(id)
35
+ IdeaIdFormatter.valid?(id)
36
+ end
37
+
38
+ # Check if scope (special folder) is consistent with status
39
+ # @param status [String] Idea status
40
+ # @param special_folder [String, nil] Special folder name (e.g., "_archive", "_maybe")
41
+ # @return [Array<Hash>] List of inconsistency issues (empty if consistent)
42
+ def self.scope_consistent?(status, special_folder)
43
+ issues = []
44
+
45
+ if terminal_status?(status) && special_folder != "_archive"
46
+ issues << {
47
+ type: :warning,
48
+ message: "Idea with terminal status '#{status}' not in _archive/"
49
+ }
50
+ end
51
+
52
+ if special_folder == "_archive" && !terminal_status?(status) && status
53
+ issues << {
54
+ type: :warning,
55
+ message: "Idea in _archive/ but status is '#{status}' (expected terminal status)"
56
+ }
57
+ end
58
+
59
+ if special_folder == "_maybe" && terminal_status?(status)
60
+ issues << {
61
+ type: :warning,
62
+ message: "Idea in _maybe/ with terminal status '#{status}' (should be in _archive/)"
63
+ }
64
+ end
65
+
66
+ issues
67
+ end
68
+
69
+ # Return list of missing required fields from frontmatter
70
+ # @param frontmatter [Hash] Parsed frontmatter
71
+ # @return [Array<String>] Names of missing required fields
72
+ def self.missing_required_fields(frontmatter)
73
+ return REQUIRED_FIELDS.dup if frontmatter.nil? || !frontmatter.is_a?(Hash)
74
+
75
+ REQUIRED_FIELDS.select { |field| frontmatter[field].nil? || frontmatter[field].to_s.strip.empty? }
76
+ end
77
+
78
+ # Return list of missing recommended fields from frontmatter
79
+ # @param frontmatter [Hash] Parsed frontmatter
80
+ # @return [Array<String>] Names of missing recommended fields
81
+ def self.missing_recommended_fields(frontmatter)
82
+ return RECOMMENDED_FIELDS.dup if frontmatter.nil? || !frontmatter.is_a?(Hash)
83
+
84
+ RECOMMENDED_FIELDS.select { |field| frontmatter[field].nil? }
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file exists for explicit require - the actual implementation
4
+ # lives in ace-support-items. We use it via require "ace/support/items".
5
+ # This adapter ensures the dependency is clear.
6
+ require "ace/support/items"
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ace/support/cli"
4
+
5
+ module Ace
6
+ module Idea
7
+ module CLI
8
+ module Commands
9
+ # ace-support-cli Command class for ace-idea create
10
+ class Create < Ace::Support::Cli::Command
11
+ include Ace::Support::Cli::Base
12
+
13
+ desc <<~DESC.strip
14
+ Create a new idea
15
+
16
+ Captures a new idea with optional title, tags, and folder placement.
17
+ Content can be provided as a positional argument or via --clipboard.
18
+
19
+ DESC
20
+
21
+ example [
22
+ '"Dark mode for night coding" # Basic capture',
23
+ '"Dark mode" --title "Dark mode" --tags ux,design # With metadata',
24
+ '"raw thought" --move-to maybe # Place in _maybe/',
25
+ "--clipboard --llm-enhance --move-to maybe # From clipboard with LLM",
26
+ '"rough idea" --dry-run # Preview without writing'
27
+ ]
28
+
29
+ argument :content, required: false, desc: "Idea content (positional)"
30
+
31
+ option :title, type: :string, aliases: %w[-t], desc: "Explicit title"
32
+ option :tags, type: :string, aliases: %w[-T], desc: "Comma-separated tags"
33
+ option :"move-to", type: :string, aliases: %w[-m], desc: "Target folder (e.g. next, maybe, archive)"
34
+ option :clipboard, type: :boolean, aliases: %w[-c], desc: "Capture content from clipboard"
35
+ option :"llm-enhance", type: :boolean, aliases: %w[-l], desc: "Enhance content with LLM"
36
+ option :"dry-run", type: :boolean, aliases: %w[-n], desc: "Preview without writing"
37
+
38
+ option :git_commit, type: :boolean, aliases: %w[--gc], desc: "Auto-commit changes"
39
+
40
+ option :quiet, type: :boolean, aliases: %w[-q], desc: "Suppress non-essential output"
41
+ option :verbose, type: :boolean, aliases: %w[-v], desc: "Show verbose output"
42
+ option :debug, type: :boolean, aliases: %w[-d], desc: "Show debug output"
43
+
44
+ def call(content: nil, **options)
45
+ clipboard = options[:clipboard]
46
+ llm_enhance = options[:"llm-enhance"]
47
+ move_to = options[:"move-to"]
48
+ title = options[:title]
49
+ tags_str = options[:tags]
50
+ dry_run = options[:"dry-run"]
51
+
52
+ # Parse tags from comma-separated string
53
+ tags = tags_str ? tags_str.split(",").map(&:strip).reject(&:empty?) : []
54
+
55
+ # Require content or clipboard
56
+ if content.nil? && !clipboard
57
+ warn "Error: provide content or --clipboard"
58
+ warn ""
59
+ warn "Usage: ace-idea create [CONTENT] [--clipboard] [--title T] [--tags T1,T2] [--move-to FOLDER]"
60
+ raise Ace::Support::Cli::Error.new("Content or --clipboard required")
61
+ end
62
+
63
+ if dry_run
64
+ puts "Would create idea:"
65
+ puts " Content: #{content || "(from clipboard)"}"
66
+ puts " Title: #{title || "(auto-generated)"}"
67
+ puts " Tags: #{tags.any? ? tags.join(", ") : "(none)"}"
68
+ puts " Folder: #{move_to ? "_#{move_to.delete_prefix("_")}" : "(root)"}"
69
+ puts " LLM: #{llm_enhance ? "yes" : "no"}"
70
+ return
71
+ end
72
+
73
+ manager = Ace::Idea::Organisms::IdeaManager.new
74
+ idea = manager.create(
75
+ content,
76
+ title: title,
77
+ tags: tags,
78
+ move_to: move_to,
79
+ clipboard: clipboard || false,
80
+ llm_enhance: llm_enhance || false
81
+ )
82
+
83
+ folder_info = idea.special_folder ? " (#{idea.special_folder})" : ""
84
+ puts "Idea created: #{idea.id} #{idea.title}#{folder_info}"
85
+ puts " Path: #{idea.file_path}"
86
+
87
+ if options[:git_commit]
88
+ Ace::Support::Items::Molecules::GitCommitter.commit(
89
+ paths: [idea.path],
90
+ intention: "create idea #{idea.id}"
91
+ )
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ace/support/cli"
4
+ require "ace/core"
5
+ require "ace/llm"
6
+ require_relative "../../organisms/idea_doctor"
7
+ require_relative "../../molecules/idea_doctor_fixer"
8
+ require_relative "../../molecules/idea_doctor_reporter"
9
+ require_relative "../../molecules/idea_config_loader"
10
+
11
+ module Ace
12
+ module Idea
13
+ module CLI
14
+ module Commands
15
+ # ace-support-cli Command class for ace-idea doctor
16
+ #
17
+ # Runs health checks on ideas and optionally auto-fixes issues.
18
+ class Doctor < Ace::Support::Cli::Command
19
+ include Ace::Support::Cli::Base
20
+
21
+ desc <<~DESC.strip
22
+ Run health checks on ideas
23
+
24
+ Validates frontmatter, file structure, and scope/status consistency
25
+ across all ideas in the repository. Supports auto-fixing safe issues.
26
+
27
+ DESC
28
+
29
+ example [
30
+ " # Run all health checks",
31
+ "--auto-fix # Auto-fix safe issues",
32
+ "--auto-fix --dry-run # Preview fixes without applying",
33
+ "--auto-fix-with-agent # Auto-fix then launch agent for remaining",
34
+ "--check frontmatter # Run specific check (frontmatter|structure|scope)",
35
+ "--json # Output as JSON",
36
+ "--verbose # Show all warnings"
37
+ ]
38
+
39
+ option :quiet, type: :boolean, aliases: %w[-q], desc: "Suppress non-essential output"
40
+ option :verbose, type: :boolean, aliases: %w[-v], desc: "Show verbose output"
41
+ option :auto_fix, type: :boolean, aliases: %w[-f], desc: "Auto-fix safe issues"
42
+ option :auto_fix_with_agent, type: :boolean, desc: "Auto-fix then launch agent for remaining"
43
+ option :model, type: :string, desc: "Provider:model for agent session"
44
+ option :errors_only, type: :boolean, desc: "Show only errors, not warnings"
45
+ option :no_color, type: :boolean, desc: "Disable colored output"
46
+ option :json, type: :boolean, desc: "Output in JSON format"
47
+ option :dry_run, type: :boolean, aliases: %w[-n], desc: "Preview fixes without applying"
48
+ option :check, type: :string, desc: "Run specific check (frontmatter, structure, scope)"
49
+
50
+ def call(**options)
51
+ execute_doctor(options)
52
+ end
53
+
54
+ private
55
+
56
+ def execute_doctor(options)
57
+ config = Molecules::IdeaConfigLoader.load
58
+ root_dir = Molecules::IdeaConfigLoader.root_dir(config)
59
+
60
+ unless Dir.exist?(root_dir)
61
+ puts "Error: Ideas directory not found: #{root_dir}"
62
+ raise Ace::Support::Cli::Error.new("Ideas directory not found")
63
+ end
64
+
65
+ # Normalize options
66
+ format = options[:json] ? :json : :terminal
67
+ fix = options[:auto_fix] || options[:auto_fix_with_agent]
68
+ colors = !options[:no_color]
69
+ colors = false if format == :json
70
+
71
+ doctor_opts = {}
72
+ doctor_opts[:check] = options[:check] if options[:check]
73
+
74
+ if options[:quiet]
75
+ results = run_diagnosis(root_dir, doctor_opts)
76
+ raise Ace::Support::Cli::Error.new("Health check failed") unless results[:valid]
77
+ return
78
+ end
79
+
80
+ results = run_diagnosis(root_dir, doctor_opts)
81
+
82
+ # Filter errors-only
83
+ if options[:errors_only] && results[:issues]
84
+ results[:issues] = results[:issues].select { |i| i[:type] == :error }
85
+ end
86
+
87
+ output = Molecules::IdeaDoctorReporter.format_results(
88
+ results,
89
+ format: format,
90
+ verbose: options[:verbose],
91
+ colors: colors
92
+ )
93
+ puts output
94
+
95
+ if fix && results[:issues]&.any?
96
+ handle_auto_fix(results, root_dir, doctor_opts, options, colors)
97
+ end
98
+
99
+ if options[:auto_fix_with_agent]
100
+ handle_agent_fix(root_dir, doctor_opts, options, config)
101
+ end
102
+
103
+ raise Ace::Support::Cli::Error.new("Health check failed") unless results[:valid]
104
+ rescue Ace::Support::Cli::Error
105
+ raise
106
+ rescue => e
107
+ raise Ace::Support::Cli::Error.new(e.message)
108
+ end
109
+
110
+ def run_diagnosis(root_dir, doctor_opts)
111
+ doctor = Organisms::IdeaDoctor.new(root_dir, doctor_opts)
112
+ doctor.run_diagnosis
113
+ end
114
+
115
+ def handle_auto_fix(results, root_dir, doctor_opts, options, colors)
116
+ doctor = Organisms::IdeaDoctor.new(root_dir, doctor_opts)
117
+ fixable_issues = results[:issues].select { |issue| doctor.auto_fixable?(issue) }
118
+
119
+ if fixable_issues.empty?
120
+ puts "\nNo auto-fixable issues found"
121
+ return
122
+ end
123
+
124
+ unless options[:quiet] || options[:dry_run]
125
+ puts "\nFound #{fixable_issues.size} auto-fixable issues"
126
+ print "Apply fixes? (y/N): "
127
+ response = $stdin.gets.chomp.downcase
128
+ return unless response == "y" || response == "yes"
129
+ end
130
+
131
+ fixer = Molecules::IdeaDoctorFixer.new(dry_run: options[:dry_run], root_dir: root_dir)
132
+ fix_results = fixer.fix_issues(fixable_issues)
133
+
134
+ output = Molecules::IdeaDoctorReporter.format_fix_results(
135
+ fix_results,
136
+ colors: colors
137
+ )
138
+ puts output
139
+
140
+ unless options[:dry_run]
141
+ puts "\nRe-running health check after fixes..."
142
+ new_results = run_diagnosis(root_dir, doctor_opts)
143
+
144
+ output = Molecules::IdeaDoctorReporter.format_results(
145
+ new_results,
146
+ format: :summary,
147
+ verbose: false,
148
+ colors: colors
149
+ )
150
+ puts output
151
+ end
152
+ end
153
+
154
+ def handle_agent_fix(root_dir, doctor_opts, options, config)
155
+ results = run_diagnosis(root_dir, doctor_opts)
156
+ remaining = results[:issues]&.reject { |i| i[:type] == :info }
157
+
158
+ if remaining.nil? || remaining.empty?
159
+ puts "\nNo remaining issues for agent to fix."
160
+ return
161
+ end
162
+
163
+ issue_list = remaining.map { |i|
164
+ prefix = (i[:type] == :error) ? "ERROR" : "WARNING"
165
+ "- [#{prefix}] #{i[:message]}#{" (#{i[:location]})" if i[:location]}"
166
+ }.join("\n")
167
+
168
+ provider_model = options[:model] || config.dig("idea", "doctor_agent_model") || "gemini:flash-latest@yolo"
169
+
170
+ prompt = <<~PROMPT
171
+ The following #{remaining.size} idea issues could NOT be auto-fixed and need manual intervention:
172
+
173
+ #{issue_list}
174
+
175
+ ---
176
+
177
+ Fix each issue listed above in the .ace-ideas/ directory.
178
+
179
+ IMPORTANT RULES:
180
+ - For invalid ID format issues, inspect the folder name and fix the frontmatter ID to match
181
+ - For YAML syntax errors, read the file and fix the YAML
182
+ - For missing opening delimiter, add '---' at the start of the file
183
+ - Do NOT delete content files — prefer fixing in place
184
+ - For folder naming issues, rename the folder to match {id}-{slug} convention
185
+
186
+ ---
187
+
188
+ Run `ace-idea doctor --verbose` to verify all issues are fixed.
189
+ PROMPT
190
+
191
+ puts "\nLaunching agent to fix #{remaining.size} remaining issues..."
192
+ query_options = {
193
+ system: nil,
194
+ timeout: 600,
195
+ fallback: false
196
+ }
197
+
198
+ response = Ace::LLM::QueryInterface.query(provider_model, prompt, **query_options)
199
+
200
+ puts response[:text]
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ace/support/cli"
4
+
5
+ module Ace
6
+ module Idea
7
+ module CLI
8
+ module Commands
9
+ # ace-support-cli Command class for ace-idea list
10
+ class List < Ace::Support::Cli::Command
11
+ include Ace::Support::Cli::Base
12
+
13
+ C = Ace::Support::Items::Atoms::AnsiColors
14
+ desc "List ideas\n\n" \
15
+ "Lists all ideas with optional filtering by status, tags, or folder.\n\n" \
16
+ "Status legend:\n" \
17
+ " #{C::RESET}○ pending #{C::YELLOW}▶ in-progress#{C::RESET} #{C::GREEN}✓ done#{C::RESET} #{C::DIM}✗ obsolete#{C::RESET}"
18
+ remove_const(:C)
19
+
20
+ example [
21
+ " # Active ideas (root only, default)",
22
+ "--in all # All ideas including archived/maybe",
23
+ "--in maybe # Ideas in _maybe/",
24
+ "--status pending # Filter by status",
25
+ "--tags ux,design # Ideas matching any tag",
26
+ "--in next --status pending # Combined filters",
27
+ "--filter status:pending --filter tags:ux|design # Generic filters"
28
+ ]
29
+
30
+ option :status, type: :string, aliases: %w[-s], desc: "Filter by status (pending, in-progress, done, obsolete)"
31
+ option :tags, type: :string, aliases: %w[-T], desc: "Filter by tags (comma-separated, any match)"
32
+ option :in, type: :string, aliases: %w[-i], desc: "Filter by folder (next=root only [default], all=everything, maybe, archive)"
33
+ option :root, type: :string, aliases: %w[-r], desc: "Override root path (subpath within ideas root)"
34
+ option :filter, type: :array, aliases: %w[-f], desc: "Filter by key:value (repeatable, supports key:a|b and key:!value)"
35
+
36
+ option :quiet, type: :boolean, aliases: %w[-q], desc: "Suppress non-essential output"
37
+ option :verbose, type: :boolean, aliases: %w[-v], desc: "Show verbose output"
38
+ option :debug, type: :boolean, aliases: %w[-d], desc: "Show debug output"
39
+
40
+ def call(**options)
41
+ status = options[:status]
42
+ in_folder = options[:in]
43
+ root = options[:root]
44
+ tags_str = options[:tags]
45
+ tags = tags_str ? tags_str.split(",").map(&:strip).reject(&:empty?) : []
46
+ filters = options[:filter]
47
+
48
+ manager = Ace::Idea::Organisms::IdeaManager.new
49
+ list_opts = {status: status, tags: tags, root: root, filters: filters}
50
+ list_opts[:in_folder] = in_folder if in_folder
51
+ ideas = manager.list(**list_opts)
52
+
53
+ puts Ace::Idea::Molecules::IdeaDisplayFormatter.format_list(
54
+ ideas, total_count: manager.last_list_total,
55
+ global_folder_stats: manager.last_folder_counts
56
+ )
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ace/support/cli"
4
+
5
+ module Ace
6
+ module Idea
7
+ module CLI
8
+ module Commands
9
+ # ace-support-cli Command class for ace-idea show
10
+ class Show < Ace::Support::Cli::Command
11
+ include Ace::Support::Cli::Base
12
+
13
+ desc <<~DESC.strip
14
+ Show idea details
15
+
16
+ Displays an idea by reference (full 6-char ID or last 3-char shortcut).
17
+
18
+ DESC
19
+
20
+ example [
21
+ "q7w # Formatted display (default)",
22
+ "8ppq7w --path # Print file path only",
23
+ "q7w --content # Print raw markdown content"
24
+ ]
25
+
26
+ argument :ref, required: true, desc: "Idea reference (6-char ID or 3-char shortcut)"
27
+
28
+ option :path, type: :boolean, desc: "Print file path only"
29
+ option :content, type: :boolean, desc: "Print raw markdown content"
30
+
31
+ option :quiet, type: :boolean, aliases: %w[-q], desc: "Suppress non-essential output"
32
+ option :verbose, type: :boolean, aliases: %w[-v], desc: "Show verbose output"
33
+ option :debug, type: :boolean, aliases: %w[-d], desc: "Show debug output"
34
+
35
+ def call(ref:, **options)
36
+ manager = Ace::Idea::Organisms::IdeaManager.new
37
+ idea = manager.show(ref)
38
+
39
+ unless idea
40
+ raise Ace::Support::Cli::Error.new("Idea '#{ref}' not found")
41
+ end
42
+
43
+ if options[:path]
44
+ puts idea.file_path
45
+ elsif options[:content]
46
+ puts File.read(idea.file_path)
47
+ else
48
+ puts Ace::Idea::Molecules::IdeaDisplayFormatter.format(idea, show_content: true)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ace/support/cli"
4
+
5
+ module Ace
6
+ module Idea
7
+ module CLI
8
+ module Commands
9
+ # ace-support-cli Command class for ace-idea status
10
+ class Status < Ace::Support::Cli::Command
11
+ include Ace::Support::Cli::Base
12
+
13
+ desc <<~DESC.strip
14
+ Show idea status overview
15
+
16
+ Displays up-next ideas, summary stats, and recently completed ideas.
17
+ DESC
18
+
19
+ example [
20
+ " # Default status view",
21
+ "--up-next-limit 5 # Show 5 up-next ideas",
22
+ "--recently-done-limit 3 # Show 3 recently done ideas"
23
+ ]
24
+
25
+ option :up_next_limit, type: :integer, desc: "Max up-next ideas to show"
26
+ option :recently_done_limit, type: :integer, desc: "Max recently-done ideas to show"
27
+
28
+ def call(**options)
29
+ manager = Ace::Idea::Organisms::IdeaManager.new
30
+ all_ideas = manager.list(in_folder: "all")
31
+
32
+ config = Ace::Idea::Molecules::IdeaConfigLoader.load
33
+ limits = resolve_limits(config, options)
34
+
35
+ categorized = Ace::Support::Items::Molecules::StatusCategorizer.categorize(
36
+ all_ideas,
37
+ up_next_limit: limits[:up_next],
38
+ recently_done_limit: limits[:recently_done],
39
+ pending_statuses: %w[pending],
40
+ done_statuses: %w[done]
41
+ )
42
+
43
+ puts Ace::Idea::Molecules::IdeaDisplayFormatter.format_status(
44
+ categorized, all_ideas: all_ideas
45
+ )
46
+ end
47
+
48
+ private
49
+
50
+ def resolve_limits(config, options)
51
+ status_config = config.dig("idea", "status") || {}
52
+ {
53
+ up_next: (options[:up_next_limit] || status_config["up_next_limit"] || 7).to_i,
54
+ recently_done: (options[:recently_done_limit] || status_config["recently_done_limit"] || 7).to_i
55
+ }
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end