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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +456 -0
- data/bin/gsd +8 -0
- data/bin/gsd-core-darwin-amd64 +0 -0
- data/bin/gsd-core-darwin-arm64 +0 -0
- data/bin/gsd-core-linux-amd64 +0 -0
- data/bin/gsd-core-linux-arm64 +0 -0
- data/bin/gsd-core-windows-amd64.exe +0 -0
- data/bin/gsd-core-windows-arm64.exe +0 -0
- data/bin/gsd-core.exe +0 -0
- data/lib/gsd/agents/coordinator.rb +195 -0
- data/lib/gsd/agents/task_manager.rb +158 -0
- data/lib/gsd/agents/worker.rb +162 -0
- data/lib/gsd/agents.rb +30 -0
- data/lib/gsd/ai/chat.rb +486 -0
- data/lib/gsd/ai/cli.rb +248 -0
- data/lib/gsd/ai/command_parser.rb +97 -0
- data/lib/gsd/ai/commands/base.rb +42 -0
- data/lib/gsd/ai/commands/clear.rb +20 -0
- data/lib/gsd/ai/commands/context.rb +30 -0
- data/lib/gsd/ai/commands/cost.rb +30 -0
- data/lib/gsd/ai/commands/export.rb +42 -0
- data/lib/gsd/ai/commands/help.rb +61 -0
- data/lib/gsd/ai/commands/model.rb +67 -0
- data/lib/gsd/ai/commands/reset.rb +22 -0
- data/lib/gsd/ai/config.rb +256 -0
- data/lib/gsd/ai/context.rb +324 -0
- data/lib/gsd/ai/cost_tracker.rb +361 -0
- data/lib/gsd/ai/git_context.rb +169 -0
- data/lib/gsd/ai/history.rb +384 -0
- data/lib/gsd/ai/providers/anthropic.rb +429 -0
- data/lib/gsd/ai/providers/base.rb +282 -0
- data/lib/gsd/ai/providers/lmstudio.rb +279 -0
- data/lib/gsd/ai/providers/ollama.rb +336 -0
- data/lib/gsd/ai/providers/openai.rb +396 -0
- data/lib/gsd/ai/providers/openrouter.rb +429 -0
- data/lib/gsd/ai/reference_resolver.rb +225 -0
- data/lib/gsd/ai/repl.rb +349 -0
- data/lib/gsd/ai/streaming.rb +438 -0
- data/lib/gsd/ai/ui.rb +429 -0
- data/lib/gsd/buddy/cli.rb +284 -0
- data/lib/gsd/buddy/gacha.rb +148 -0
- data/lib/gsd/buddy/renderer.rb +108 -0
- data/lib/gsd/buddy/species.rb +190 -0
- data/lib/gsd/buddy/stats.rb +156 -0
- data/lib/gsd/buddy.rb +28 -0
- data/lib/gsd/cli.rb +455 -0
- data/lib/gsd/commands.rb +198 -0
- data/lib/gsd/config.rb +183 -0
- data/lib/gsd/error.rb +188 -0
- data/lib/gsd/frontmatter.rb +123 -0
- data/lib/gsd/go/bridge.rb +173 -0
- data/lib/gsd/history.rb +76 -0
- data/lib/gsd/milestone.rb +75 -0
- data/lib/gsd/output.rb +184 -0
- data/lib/gsd/phase.rb +102 -0
- data/lib/gsd/plugins/base.rb +92 -0
- data/lib/gsd/plugins/cli.rb +330 -0
- data/lib/gsd/plugins/config.rb +164 -0
- data/lib/gsd/plugins/hooks.rb +132 -0
- data/lib/gsd/plugins/installer.rb +158 -0
- data/lib/gsd/plugins/loader.rb +122 -0
- data/lib/gsd/plugins/manager.rb +187 -0
- data/lib/gsd/plugins/marketplace.rb +142 -0
- data/lib/gsd/plugins/sandbox.rb +114 -0
- data/lib/gsd/plugins/search.rb +131 -0
- data/lib/gsd/plugins/validator.rb +157 -0
- data/lib/gsd/plugins.rb +48 -0
- data/lib/gsd/profile.rb +127 -0
- data/lib/gsd/research.rb +85 -0
- data/lib/gsd/roadmap.rb +90 -0
- data/lib/gsd/skills/bundled/commit.md +58 -0
- data/lib/gsd/skills/bundled/debug.md +28 -0
- data/lib/gsd/skills/bundled/explain.md +41 -0
- data/lib/gsd/skills/bundled/plan.md +42 -0
- data/lib/gsd/skills/bundled/verify.md +26 -0
- data/lib/gsd/skills/loader.rb +189 -0
- data/lib/gsd/state.rb +102 -0
- data/lib/gsd/template.rb +106 -0
- data/lib/gsd/tools/ask_user_question.rb +179 -0
- data/lib/gsd/tools/base.rb +204 -0
- data/lib/gsd/tools/bash.rb +246 -0
- data/lib/gsd/tools/file_edit.rb +297 -0
- data/lib/gsd/tools/file_read.rb +199 -0
- data/lib/gsd/tools/file_write.rb +153 -0
- data/lib/gsd/tools/glob.rb +202 -0
- data/lib/gsd/tools/grep.rb +227 -0
- data/lib/gsd/tools/gsd_frontmatter.rb +165 -0
- data/lib/gsd/tools/gsd_phase.rb +140 -0
- data/lib/gsd/tools/gsd_roadmap.rb +108 -0
- data/lib/gsd/tools/gsd_state.rb +143 -0
- data/lib/gsd/tools/gsd_template.rb +157 -0
- data/lib/gsd/tools/gsd_verify.rb +159 -0
- data/lib/gsd/tools/registry.rb +103 -0
- data/lib/gsd/tools/task.rb +235 -0
- data/lib/gsd/tools/todo_write.rb +290 -0
- data/lib/gsd/tools/web.rb +260 -0
- data/lib/gsd/tui/app.rb +366 -0
- data/lib/gsd/tui/auto_complete.rb +79 -0
- data/lib/gsd/tui/colors.rb +111 -0
- data/lib/gsd/tui/command_palette.rb +126 -0
- data/lib/gsd/tui/header.rb +38 -0
- data/lib/gsd/tui/input_box.rb +199 -0
- data/lib/gsd/tui/spinner.rb +40 -0
- data/lib/gsd/tui/status_bar.rb +51 -0
- data/lib/gsd/tui.rb +17 -0
- data/lib/gsd/validator.rb +216 -0
- data/lib/gsd/verify.rb +175 -0
- data/lib/gsd/version.rb +5 -0
- data/lib/gsd/workstream.rb +91 -0
- 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
|
data/lib/gsd/version.rb
ADDED
|
@@ -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
|