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.
- checksums.yaml +7 -0
- data/.ace-defaults/idea/config.yml +21 -0
- data/.ace-defaults/nav/protocols/wfi-sources/ace-idea.yml +19 -0
- data/CHANGELOG.md +387 -0
- data/README.md +42 -0
- data/Rakefile +13 -0
- data/docs/demo/ace-idea-getting-started.gif +0 -0
- data/docs/demo/ace-idea-getting-started.tape.yml +44 -0
- data/docs/demo/fixtures/README.md +3 -0
- data/docs/demo/fixtures/sample.txt +1 -0
- data/docs/getting-started.md +102 -0
- data/docs/handbook.md +39 -0
- data/docs/usage.md +320 -0
- data/exe/ace-idea +22 -0
- data/handbook/skills/as-idea-capture/SKILL.md +25 -0
- data/handbook/skills/as-idea-capture-features/SKILL.md +26 -0
- data/handbook/skills/as-idea-review/SKILL.md +26 -0
- data/handbook/workflow-instructions/idea/capture-features.wf.md +243 -0
- data/handbook/workflow-instructions/idea/capture.wf.md +270 -0
- data/handbook/workflow-instructions/idea/prioritize.wf.md +223 -0
- data/handbook/workflow-instructions/idea/review.wf.md +93 -0
- data/lib/ace/idea/atoms/idea_file_pattern.rb +40 -0
- data/lib/ace/idea/atoms/idea_frontmatter_defaults.rb +39 -0
- data/lib/ace/idea/atoms/idea_id_formatter.rb +37 -0
- data/lib/ace/idea/atoms/idea_validation_rules.rb +89 -0
- data/lib/ace/idea/atoms/slug_sanitizer_adapter.rb +6 -0
- data/lib/ace/idea/cli/commands/create.rb +98 -0
- data/lib/ace/idea/cli/commands/doctor.rb +206 -0
- data/lib/ace/idea/cli/commands/list.rb +62 -0
- data/lib/ace/idea/cli/commands/show.rb +55 -0
- data/lib/ace/idea/cli/commands/status.rb +61 -0
- data/lib/ace/idea/cli/commands/update.rb +118 -0
- data/lib/ace/idea/cli.rb +75 -0
- data/lib/ace/idea/models/idea.rb +39 -0
- data/lib/ace/idea/molecules/idea_clipboard_reader.rb +117 -0
- data/lib/ace/idea/molecules/idea_config_loader.rb +93 -0
- data/lib/ace/idea/molecules/idea_creator.rb +248 -0
- data/lib/ace/idea/molecules/idea_display_formatter.rb +165 -0
- data/lib/ace/idea/molecules/idea_doctor_fixer.rb +504 -0
- data/lib/ace/idea/molecules/idea_doctor_reporter.rb +264 -0
- data/lib/ace/idea/molecules/idea_frontmatter_validator.rb +137 -0
- data/lib/ace/idea/molecules/idea_llm_enhancer.rb +177 -0
- data/lib/ace/idea/molecules/idea_loader.rb +124 -0
- data/lib/ace/idea/molecules/idea_mover.rb +78 -0
- data/lib/ace/idea/molecules/idea_resolver.rb +57 -0
- data/lib/ace/idea/molecules/idea_scanner.rb +56 -0
- data/lib/ace/idea/molecules/idea_structure_validator.rb +157 -0
- data/lib/ace/idea/organisms/idea_doctor.rb +207 -0
- data/lib/ace/idea/organisms/idea_manager.rb +251 -0
- data/lib/ace/idea/version.rb +7 -0
- data/lib/ace/idea.rb +37 -0
- 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,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
|