fantasy-cli 1.2.6

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 (112) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +456 -0
  4. data/bin/gsd +8 -0
  5. data/bin/gsd-core-darwin-amd64 +0 -0
  6. data/bin/gsd-core-darwin-arm64 +0 -0
  7. data/bin/gsd-core-linux-amd64 +0 -0
  8. data/bin/gsd-core-linux-arm64 +0 -0
  9. data/bin/gsd-core-windows-amd64.exe +0 -0
  10. data/bin/gsd-core-windows-arm64.exe +0 -0
  11. data/bin/gsd-core.exe +0 -0
  12. data/lib/gsd/agents/coordinator.rb +195 -0
  13. data/lib/gsd/agents/task_manager.rb +158 -0
  14. data/lib/gsd/agents/worker.rb +162 -0
  15. data/lib/gsd/agents.rb +30 -0
  16. data/lib/gsd/ai/chat.rb +486 -0
  17. data/lib/gsd/ai/cli.rb +248 -0
  18. data/lib/gsd/ai/command_parser.rb +97 -0
  19. data/lib/gsd/ai/commands/base.rb +42 -0
  20. data/lib/gsd/ai/commands/clear.rb +20 -0
  21. data/lib/gsd/ai/commands/context.rb +30 -0
  22. data/lib/gsd/ai/commands/cost.rb +30 -0
  23. data/lib/gsd/ai/commands/export.rb +42 -0
  24. data/lib/gsd/ai/commands/help.rb +61 -0
  25. data/lib/gsd/ai/commands/model.rb +67 -0
  26. data/lib/gsd/ai/commands/reset.rb +22 -0
  27. data/lib/gsd/ai/config.rb +256 -0
  28. data/lib/gsd/ai/context.rb +324 -0
  29. data/lib/gsd/ai/cost_tracker.rb +361 -0
  30. data/lib/gsd/ai/git_context.rb +169 -0
  31. data/lib/gsd/ai/history.rb +384 -0
  32. data/lib/gsd/ai/providers/anthropic.rb +429 -0
  33. data/lib/gsd/ai/providers/base.rb +282 -0
  34. data/lib/gsd/ai/providers/lmstudio.rb +279 -0
  35. data/lib/gsd/ai/providers/ollama.rb +336 -0
  36. data/lib/gsd/ai/providers/openai.rb +396 -0
  37. data/lib/gsd/ai/providers/openrouter.rb +429 -0
  38. data/lib/gsd/ai/reference_resolver.rb +225 -0
  39. data/lib/gsd/ai/repl.rb +349 -0
  40. data/lib/gsd/ai/streaming.rb +438 -0
  41. data/lib/gsd/ai/ui.rb +429 -0
  42. data/lib/gsd/buddy/cli.rb +284 -0
  43. data/lib/gsd/buddy/gacha.rb +148 -0
  44. data/lib/gsd/buddy/renderer.rb +108 -0
  45. data/lib/gsd/buddy/species.rb +190 -0
  46. data/lib/gsd/buddy/stats.rb +156 -0
  47. data/lib/gsd/buddy.rb +28 -0
  48. data/lib/gsd/cli.rb +455 -0
  49. data/lib/gsd/commands.rb +198 -0
  50. data/lib/gsd/config.rb +183 -0
  51. data/lib/gsd/error.rb +188 -0
  52. data/lib/gsd/frontmatter.rb +123 -0
  53. data/lib/gsd/go/bridge.rb +173 -0
  54. data/lib/gsd/history.rb +76 -0
  55. data/lib/gsd/milestone.rb +75 -0
  56. data/lib/gsd/output.rb +184 -0
  57. data/lib/gsd/phase.rb +102 -0
  58. data/lib/gsd/plugins/base.rb +92 -0
  59. data/lib/gsd/plugins/cli.rb +330 -0
  60. data/lib/gsd/plugins/config.rb +164 -0
  61. data/lib/gsd/plugins/hooks.rb +132 -0
  62. data/lib/gsd/plugins/installer.rb +158 -0
  63. data/lib/gsd/plugins/loader.rb +122 -0
  64. data/lib/gsd/plugins/manager.rb +187 -0
  65. data/lib/gsd/plugins/marketplace.rb +142 -0
  66. data/lib/gsd/plugins/sandbox.rb +114 -0
  67. data/lib/gsd/plugins/search.rb +131 -0
  68. data/lib/gsd/plugins/validator.rb +157 -0
  69. data/lib/gsd/plugins.rb +48 -0
  70. data/lib/gsd/profile.rb +127 -0
  71. data/lib/gsd/research.rb +85 -0
  72. data/lib/gsd/roadmap.rb +90 -0
  73. data/lib/gsd/skills/bundled/commit.md +58 -0
  74. data/lib/gsd/skills/bundled/debug.md +28 -0
  75. data/lib/gsd/skills/bundled/explain.md +41 -0
  76. data/lib/gsd/skills/bundled/plan.md +42 -0
  77. data/lib/gsd/skills/bundled/verify.md +26 -0
  78. data/lib/gsd/skills/loader.rb +189 -0
  79. data/lib/gsd/state.rb +102 -0
  80. data/lib/gsd/template.rb +106 -0
  81. data/lib/gsd/tools/ask_user_question.rb +179 -0
  82. data/lib/gsd/tools/base.rb +204 -0
  83. data/lib/gsd/tools/bash.rb +246 -0
  84. data/lib/gsd/tools/file_edit.rb +297 -0
  85. data/lib/gsd/tools/file_read.rb +199 -0
  86. data/lib/gsd/tools/file_write.rb +153 -0
  87. data/lib/gsd/tools/glob.rb +202 -0
  88. data/lib/gsd/tools/grep.rb +227 -0
  89. data/lib/gsd/tools/gsd_frontmatter.rb +165 -0
  90. data/lib/gsd/tools/gsd_phase.rb +140 -0
  91. data/lib/gsd/tools/gsd_roadmap.rb +108 -0
  92. data/lib/gsd/tools/gsd_state.rb +143 -0
  93. data/lib/gsd/tools/gsd_template.rb +157 -0
  94. data/lib/gsd/tools/gsd_verify.rb +159 -0
  95. data/lib/gsd/tools/registry.rb +103 -0
  96. data/lib/gsd/tools/task.rb +235 -0
  97. data/lib/gsd/tools/todo_write.rb +290 -0
  98. data/lib/gsd/tools/web.rb +260 -0
  99. data/lib/gsd/tui/app.rb +366 -0
  100. data/lib/gsd/tui/auto_complete.rb +79 -0
  101. data/lib/gsd/tui/colors.rb +111 -0
  102. data/lib/gsd/tui/command_palette.rb +126 -0
  103. data/lib/gsd/tui/header.rb +38 -0
  104. data/lib/gsd/tui/input_box.rb +199 -0
  105. data/lib/gsd/tui/spinner.rb +40 -0
  106. data/lib/gsd/tui/status_bar.rb +51 -0
  107. data/lib/gsd/tui.rb +17 -0
  108. data/lib/gsd/validator.rb +216 -0
  109. data/lib/gsd/verify.rb +175 -0
  110. data/lib/gsd/version.rb +5 -0
  111. data/lib/gsd/workstream.rb +91 -0
  112. metadata +231 -0
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'error'
4
+
5
+ module Gsd
6
+ # Input validation utilities
7
+ class Validator
8
+ class << self
9
+ # Phase number validation
10
+ #
11
+ # @param phase [String] Phase number to validate
12
+ # @return [String] Validated phase number
13
+ # @raise [ValidationError] If phase format is invalid
14
+ def phase_number(phase)
15
+ return phase if phase =~ /^\d+(\.\d+)?$/
16
+
17
+ raise ValidationError.new(
18
+ 'phase',
19
+ phase,
20
+ 'Invalid phase number format. Use: 1, 1.1, 2.3, etc.'
21
+ )
22
+ end
23
+
24
+ # Field name validation for state
25
+ #
26
+ # @param field [String] Field name to validate
27
+ # @return [String] Validated field name
28
+ # @raise [ValidationError] If field is invalid
29
+ def state_field(field)
30
+ valid_fields = %w[
31
+ model_profile current_phase plans_of plans_total
32
+ progress milestone milestone_branch active_workspace
33
+ ]
34
+
35
+ return field if valid_fields.include?(field)
36
+
37
+ raise ValidationError.new(
38
+ 'field',
39
+ field,
40
+ "Invalid field. Must be one of: #{valid_fields.join(', ')}"
41
+ )
42
+ end
43
+
44
+ # Path exists validation
45
+ #
46
+ # @param path [String] Path to validate
47
+ # @param name [String] Name for error message
48
+ # @return [String] Validated path
49
+ # @raise [NotFoundError] If path doesn't exist
50
+ def path_exists(path, name = 'Path')
51
+ return path if File.exist?(path)
52
+
53
+ raise NotFoundError.new(name, path)
54
+ end
55
+
56
+ # Planning directory validation
57
+ #
58
+ # @param cwd [String] Working directory
59
+ # @return [String] Planning directory path
60
+ # @raise [NotFoundError] If planning directory doesn't exist
61
+ def planning_dir_exists(cwd)
62
+ planning_path = File.join(cwd, '.planning')
63
+ return planning_path if File.directory?(planning_path)
64
+
65
+ raise NotFoundError.new('planning directory', planning_path)
66
+ end
67
+
68
+ # Phase directory validation
69
+ #
70
+ # @param phase_num [String] Phase number
71
+ # @param cwd [String] Working directory
72
+ # @return [String] Phase directory path
73
+ # @raise [NotFoundError] If phase directory doesn't exist
74
+ def phase_dir_exists(phase_num, cwd)
75
+ phases_dir = File.join(cwd, '.planning', 'phases')
76
+
77
+ unless File.directory?(phases_dir)
78
+ raise NotFoundError.new('phases directory', phases_dir)
79
+ end
80
+
81
+ # Find phase directory
82
+ entries = Dir.entries(phases_dir).select { |e| File.directory?(File.join(phases_dir, e)) }
83
+ phase_entry = entries.find { |e| e.start_with?(phase_num) || e.include?("-#{phase_num}-") }
84
+
85
+ return File.join(phases_dir, phase_entry) if phase_entry
86
+
87
+ raise NotFoundError.new('phase', phase_num)
88
+ end
89
+
90
+ # File extension validation
91
+ #
92
+ # @param path [String] File path
93
+ # @param expected_ext [String] Expected extension (without dot)
94
+ # @return [String] Validated path
95
+ # @raise [ValidationError] If extension doesn't match
96
+ def file_extension(path, expected_ext)
97
+ ext = File.extname(path)
98
+ return path if ext == ".#{expected_ext}"
99
+
100
+ raise ValidationError.new(
101
+ 'file extension',
102
+ ext,
103
+ "Expected .#{expected_ext}, got #{ext}"
104
+ )
105
+ end
106
+
107
+ # Non-empty string validation
108
+ #
109
+ # @param value [String] Value to validate
110
+ # @param name [String] Field name for error message
111
+ # @return [String] Validated string
112
+ # @raise [ValidationError] If value is empty
113
+ def non_empty_string(value, name)
114
+ return value if value.is_a?(String) && !value.strip.empty?
115
+
116
+ raise ValidationError.new(
117
+ name,
118
+ value.inspect,
119
+ "#{name} must be a non-empty string"
120
+ )
121
+ end
122
+
123
+ # Positive integer validation
124
+ #
125
+ # @param value [Integer] Value to validate
126
+ # @param name [String] Field name for error message
127
+ # @return [Integer] Validated integer
128
+ # @raise [ValidationError] If value is not positive
129
+ def positive_integer(value, name)
130
+ return value if value.is_a?(Integer) && value > 0
131
+
132
+ raise ValidationError.new(
133
+ name,
134
+ value.inspect,
135
+ "#{name} must be a positive integer"
136
+ )
137
+ end
138
+
139
+ # Percentage validation (0-100)
140
+ #
141
+ # @param value [Numeric] Value to validate
142
+ # @return [Numeric] Validated percentage
143
+ # @raise [ValidationError] If value is out of range
144
+ def percentage(value)
145
+ num = value.to_f
146
+ return num if num >= 0 && num <= 100
147
+
148
+ raise ValidationError.new(
149
+ 'percentage',
150
+ value,
151
+ 'Percentage must be between 0 and 100'
152
+ )
153
+ end
154
+
155
+ # Model profile validation
156
+ #
157
+ # @param profile [String] Model profile name
158
+ # @return [String] Validated profile
159
+ # @raise [ValidationError] If profile is invalid
160
+ def model_profile(profile)
161
+ return profile if profile =~ /^[a-z0-9_-]+$/
162
+
163
+ raise ValidationError.new(
164
+ 'model_profile',
165
+ profile,
166
+ 'Model profile must contain only lowercase letters, numbers, hyphens, and underscores'
167
+ )
168
+ end
169
+
170
+ # Git commit message validation
171
+ #
172
+ # @param message [String] Commit message
173
+ # @return [String] Validated message
174
+ # @raise [ValidationError] If message is invalid
175
+ def commit_message(message)
176
+ return message if message.is_a?(String) && message.length >= 5
177
+
178
+ raise ValidationError.new(
179
+ 'message',
180
+ message.inspect,
181
+ 'Commit message must be at least 5 characters'
182
+ )
183
+ end
184
+
185
+ # Slug validation
186
+ #
187
+ # @param slug [String] Slug to validate
188
+ # @return [String] Validated slug
189
+ # @raise [ValidationError] If slug is invalid
190
+ def slug(slug)
191
+ return slug if slug =~ /^[a-z0-9]+(-[a-z0-9]+)*$/
192
+
193
+ raise ValidationError.new(
194
+ 'slug',
195
+ slug,
196
+ 'Slug must be lowercase alphanumeric with hyphens'
197
+ )
198
+ end
199
+
200
+ # Workspace name validation
201
+ #
202
+ # @param name [String] Workspace name
203
+ # @return [String] Validated name
204
+ # @raise [ValidationError] If name is invalid
205
+ def workspace_name(name)
206
+ return name if name =~ /^[a-zA-Z0-9_-]+$/
207
+
208
+ raise ValidationError.new(
209
+ 'workspace',
210
+ name,
211
+ 'Workspace name must be alphanumeric, hyphens, and underscores only'
212
+ )
213
+ end
214
+ end
215
+ end
216
+ end
data/lib/gsd/verify.rb ADDED
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'go/bridge'
4
+
5
+ module Gsd
6
+ # Verify - Wrapper Ruby para operações de verificação via Go
7
+ module Verify
8
+ class << self
9
+ # Verifica estrutura de um PLAN.md
10
+ #
11
+ # @param file [String] Caminho para o arquivo PLAN.md
12
+ # @param cwd [String] Diretório de trabalho
13
+ # @return [Hash] Resultado da verificação
14
+ def plan_structure(file:, cwd: nil)
15
+ cwd ||= Dir.pwd
16
+ result = Go::Bridge.call('verify', { 'plan-structure' => file }, cwd: cwd)
17
+
18
+ if result['success']
19
+ result['data']
20
+ else
21
+ raise VerifyError, result['error']
22
+ end
23
+ end
24
+
25
+ # Verifica se todos os plans têm summaries correspondentes
26
+ #
27
+ # @param phase [String] Número da phase
28
+ # @param cwd [String] Diretório de trabalho
29
+ # @return [Hash] Resultado da verificação
30
+ def phase_completeness(phase:, cwd: nil)
31
+ cwd ||= Dir.pwd
32
+ result = Go::Bridge.call('verify', { 'phase-completeness' => phase }, cwd: cwd)
33
+
34
+ if result['success']
35
+ result['data']
36
+ else
37
+ raise VerifyError, result['error']
38
+ end
39
+ end
40
+
41
+ # Verifica referências (@-refs e paths)
42
+ #
43
+ # @param file [String] Caminho para o arquivo
44
+ # @param cwd [String] Diretório de trabalho
45
+ # @return [Hash] Resultado da verificação
46
+ def references(file:, cwd: nil)
47
+ cwd ||= Dir.pwd
48
+ result = Go::Bridge.call('verify', { 'references' => file }, cwd: cwd)
49
+
50
+ if result['success']
51
+ result['data']
52
+ else
53
+ raise VerifyError, result['error']
54
+ end
55
+ end
56
+
57
+ # Verifica commits hashes
58
+ #
59
+ # @param hashes [Array<String>] Lista de commit hashes
60
+ # @param cwd [String] Diretório de trabalho
61
+ # @return [Hash] Resultado da verificação
62
+ def commits(hashes:, cwd: nil)
63
+ cwd ||= Dir.pwd
64
+ result = Go::Bridge.call('verify', { 'commits' => hashes.join(' ') }, cwd: cwd)
65
+
66
+ if result['success']
67
+ result['data']
68
+ else
69
+ raise VerifyError, result['error']
70
+ end
71
+ end
72
+
73
+ # Verifica artefatos obrigatórios
74
+ #
75
+ # @param file [String] Caminho para o arquivo PLAN.md
76
+ # @param cwd [String] Diretório de trabalho
77
+ # @return [Hash] Resultado da verificação
78
+ def artifacts(file:, cwd: nil)
79
+ cwd ||= Dir.pwd
80
+ result = Go::Bridge.call('verify', { 'artifacts' => file }, cwd: cwd)
81
+
82
+ if result['success']
83
+ result['data']
84
+ else
85
+ raise VerifyError, result['error']
86
+ end
87
+ end
88
+
89
+ # Verifica links obrigatórios
90
+ #
91
+ # @param file [String] Caminho para o arquivo PLAN.md
92
+ # @param cwd [String] Diretório de trabalho
93
+ # @return [Hash] Resultado da verificação
94
+ def key_links(file:, cwd: nil)
95
+ cwd ||= Dir.pwd
96
+ result = Go::Bridge.call('verify', { 'key-links' => file }, cwd: cwd)
97
+
98
+ if result['success']
99
+ result['data']
100
+ else
101
+ raise VerifyError, result['error']
102
+ end
103
+ end
104
+
105
+ # Verifica formatação de tasks
106
+ #
107
+ # @param file [String] Caminho para o arquivo
108
+ # @param cwd [String] Diretório de trabalho
109
+ # @return [Hash] Resultado da verificação
110
+ def tasks(file:, cwd: nil)
111
+ cwd ||= Dir.pwd
112
+ result = Go::Bridge.call('verify', { 'tasks' => file }, cwd: cwd)
113
+
114
+ if result['success']
115
+ result['data']
116
+ else
117
+ raise VerifyError, result['error']
118
+ end
119
+ end
120
+
121
+ # Verifica estrutura de SUMMARY.md
122
+ #
123
+ # @param file [String] Caminho para o arquivo SUMMARY.md
124
+ # @param check_count [Integer] Número mínimo de tasks completas esperadas
125
+ # @param cwd [String] Diretório de trabalho
126
+ # @return [Hash] Resultado da verificação
127
+ def summary(file:, check_count: 2, cwd: nil)
128
+ cwd ||= Dir.pwd
129
+ result = Go::Bridge.call('verify', { 'summary' => file, 'check-count' => check_count.to_s }, cwd: cwd)
130
+
131
+ if result['success']
132
+ result['data']
133
+ else
134
+ raise VerifyError, result['error']
135
+ end
136
+ end
137
+
138
+ # Helper: Verifica se uma verificação passou
139
+ #
140
+ # @param result [Hash] Resultado da verificação
141
+ # @return [Boolean] true se passou
142
+ def passed?(result)
143
+ result.is_a?(Hash) && result['success'] == true
144
+ end
145
+
146
+ # Helper: Verifica se uma verificação falhou
147
+ #
148
+ # @param result [Hash] Resultado da verificação
149
+ # @return [Boolean] true se falhou
150
+ def failed?(result)
151
+ result.is_a?(Hash) && result['success'] != true
152
+ end
153
+
154
+ # Helper: Obtém erros de uma verificação
155
+ #
156
+ # @param result [Hash] Resultado da verificação
157
+ # @return [Array<String>] Lista de erros
158
+ def errors(result)
159
+ return [] unless result.is_a?(Hash)
160
+ result['errors'] || result['data']&.dig('errors') || []
161
+ end
162
+
163
+ # Helper: Obtém warnings de uma verificação
164
+ #
165
+ # @param result [Hash] Resultado da verificação
166
+ # @return [Array<String>] Lista de warnings
167
+ def warnings(result)
168
+ return [] unless result.is_a?(Hash)
169
+ result['warnings'] || result['data']&.dig('warnings') || []
170
+ end
171
+ end
172
+
173
+ class VerifyError < StandardError; end
174
+ end
175
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gsd
4
+ VERSION = '1.2.6'
5
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'go/bridge'
4
+
5
+ module Gsd
6
+ # Workstream - Wrapper Ruby para operações de workstream via Go
7
+ module Workstream
8
+ class << self
9
+ # Obtém o workstream atual
10
+ #
11
+ # @param cwd [String] Diretório de trabalho
12
+ # @return [Hash] Workstream info
13
+ def get(cwd: nil)
14
+ cwd ||= Dir.pwd
15
+ result = Go::Bridge.call('workstream', { 'get' => true }, cwd: cwd)
16
+
17
+ if result['success']
18
+ result['data']
19
+ else
20
+ raise WorkstreamError, result['error']
21
+ end
22
+ end
23
+
24
+ # Define o workstream ativo
25
+ #
26
+ # @param name [String] Nome do workstream
27
+ # @param cwd [String] Diretório de trabalho
28
+ # @return [Hash] Resultado da operação
29
+ def set(name, cwd: nil)
30
+ cwd ||= Dir.pwd
31
+ args = { 'set' => name }
32
+ result = Go::Bridge.call('workstream', args, cwd: cwd)
33
+
34
+ if result['success']
35
+ result['data']
36
+ else
37
+ raise WorkstreamError, result['error']
38
+ end
39
+ end
40
+
41
+ # Lista todos os workstreams
42
+ #
43
+ # @param cwd [String] Diretório de trabalho
44
+ # @return [Hash] Lista de workstreams
45
+ def list(cwd: nil)
46
+ cwd ||= Dir.pwd
47
+ result = Go::Bridge.call('workstream', { 'list' => true }, cwd: cwd)
48
+
49
+ if result['success']
50
+ result['data']
51
+ else
52
+ raise WorkstreamError, result['error']
53
+ end
54
+ end
55
+
56
+ # Limpa o workstream ativo
57
+ #
58
+ # @param cwd [String] Diretório de trabalho
59
+ # @return [Hash] Resultado da operação
60
+ def clear(cwd: nil)
61
+ cwd ||= Dir.pwd
62
+ result = Go::Bridge.call('workstream', { 'clear' => true }, cwd: cwd)
63
+
64
+ if result['success']
65
+ result['data']
66
+ else
67
+ raise WorkstreamError, result['error']
68
+ end
69
+ end
70
+
71
+ # Helper: Verifica se há um workstream ativo
72
+ #
73
+ # @param result [Hash] Resultado do get
74
+ # @return [Boolean] true se ativo
75
+ def active?(result)
76
+ result.is_a?(Hash) && result['active'] == true
77
+ end
78
+
79
+ # Helper: Obtém nome do workstream ativo
80
+ #
81
+ # @param result [Hash] Resultado do get
82
+ # @return [String, nil] Nome do workstream ou nil
83
+ def name(result)
84
+ return nil unless result.is_a?(Hash)
85
+ result['workstream']
86
+ end
87
+ end
88
+
89
+ class WorkstreamError < StandardError; end
90
+ end
91
+ end