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,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gsd
4
+ module Plugins
5
+ # Plugin Search - Busca avançada de plugins
6
+ #
7
+ # Responsável por:
8
+ # - Buscar plugins por nome, descrição, tags
9
+ # - Filtrar por categoria, versão, rating
10
+ # - Ordenar resultados
11
+ class Search
12
+ attr_reader :client
13
+
14
+ # Inicializa o Search
15
+ #
16
+ # @param client [MarketplaceClient] Cliente do marketplace
17
+ def initialize(client: nil)
18
+ @client = client || MarketplaceClient.new
19
+ end
20
+
21
+ # Busca plugins
22
+ #
23
+ # @param query [String] Query de busca
24
+ # @param options [Hash] Opções de busca
25
+ # @return [Array<Hash>] Resultados
26
+ def search(query, options = {})
27
+ filters = build_filters(options)
28
+ @client.search(query, filters)
29
+ end
30
+
31
+ # Busca por nome
32
+ #
33
+ # @param name [String] Nome ou parte do nome
34
+ # @return [Array<Hash>] Resultados
35
+ def by_name(name)
36
+ search(name, search_in: ['name'])
37
+ end
38
+
39
+ # Busca por descrição
40
+ #
41
+ # @param text [String] Texto da descrição
42
+ # @return [Array<Hash>] Resultados
43
+ def by_description(text)
44
+ search(text, search_in: ['description'])
45
+ end
46
+
47
+ # Busca por tags
48
+ #
49
+ # @param tags [Array<String>] Tags
50
+ # @return [Array<Hash>] Resultados
51
+ def by_tags(tags)
52
+ search('', tags: tags)
53
+ end
54
+
55
+ # Busca por autor
56
+ #
57
+ # @param author [String] Nome do autor
58
+ # @return [Array<Hash>] Resultados
59
+ def by_author(author)
60
+ search(author, author: author)
61
+ end
62
+
63
+ # Filtra por categoria
64
+ #
65
+ # @param category [String] Categoria
66
+ # @return [Array<Hash>] Resultados
67
+ def in_category(category)
68
+ @client.by_category(category)
69
+ end
70
+
71
+ # Plugins populares
72
+ #
73
+ # @param limit [Integer] Limite
74
+ # @return [Array<Hash>] Resultados
75
+ def popular(limit: 10)
76
+ @client.popular(limit: limit)
77
+ end
78
+
79
+ # Plugins recentes
80
+ #
81
+ # @param limit [Integer] Limite
82
+ # @return [Array<Hash>] Resultados
83
+ def recent(limit: 10)
84
+ @client.recent(limit: limit)
85
+ end
86
+
87
+ # Ordena resultados por rating
88
+ #
89
+ # @param results [Array<Hash>] Resultados
90
+ # @return [Array<Hash>] Resultados ordenados
91
+ def sort_by_rating(results)
92
+ results.sort_by { |r| -r['rating'].to_f }
93
+ end
94
+
95
+ # Ordena resultados por downloads
96
+ #
97
+ # @param results [Array<Hash>] Resultados
98
+ # @return [Array<Hash>] Resultados ordenados
99
+ def sort_by_downloads(results)
100
+ results.sort_by { |r| -r['downloads'].to_i }
101
+ end
102
+
103
+ # Ordena resultados por data
104
+ #
105
+ # @param results [Array<Hash>] Resultados
106
+ # @return [Array<Hash>] Resultados ordenados
107
+ def sort_by_date(results)
108
+ results.sort_by { |r| r['updated_at'] || r['created_at'] }.reverse
109
+ end
110
+
111
+ private
112
+
113
+ # Constrói filtros
114
+ #
115
+ # @param options [Hash] Opções
116
+ # @return [Hash] Filtros
117
+ def build_filters(options)
118
+ filters = {}
119
+
120
+ filters['search_in'] = options[:search_in].join(',') if options[:search_in]
121
+ filters['tags'] = options[:tags].join(',') if options[:tags]
122
+ filters['category'] = options[:category] if options[:category]
123
+ filters['min_rating'] = options[:min_rating] if options[:min_rating]
124
+ filters['min_downloads'] = options[:min_downloads] if options[:min_downloads]
125
+ filters['gsd_version'] = options[:gsd_version] if options[:gsd_version]
126
+
127
+ filters
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Gsd
6
+ module Plugins
7
+ # Plugin Validator - Valida plugins
8
+ #
9
+ # Responsável por:
10
+ # - Validar estrutura do plugin
11
+ # - Validar metadata
12
+ # - Validar segurança
13
+ class Validator
14
+ # Erros de validação
15
+ attr_reader :errors
16
+
17
+ # Inicializa o Validator
18
+ def initialize
19
+ @errors = []
20
+ end
21
+
22
+ # Valida plugin
23
+ #
24
+ # @param path [String] Caminho do plugin
25
+ # @return [Boolean] true se válido
26
+ def validate(path)
27
+ @errors.clear
28
+
29
+ validate_structure(path)
30
+ validate_metadata(path)
31
+ validate_security(path)
32
+
33
+ @errors.empty?
34
+ end
35
+
36
+ # Valida e retorna erros
37
+ #
38
+ # @param path [String] Caminho do plugin
39
+ # @return [Array<String>] Lista de erros
40
+ def validate_with_errors(path)
41
+ validate(path)
42
+ @errors
43
+ end
44
+
45
+ private
46
+
47
+ # Valida estrutura de diretórios
48
+ #
49
+ # @param path [String] Caminho do plugin
50
+ # @return [void]
51
+ def validate_structure(path)
52
+ # Verifica se é diretório
53
+ unless File.directory?(path)
54
+ @errors << "Not a directory: #{path}"
55
+ return
56
+ end
57
+
58
+ # Verifica plugin.rb
59
+ unless File.exist?(File.join(path, 'plugin.rb'))
60
+ @errors << "Missing plugin.rb"
61
+ end
62
+
63
+ # Verifica plugin.json
64
+ unless File.exist?(File.join(path, 'plugin.json'))
65
+ @errors << "Missing plugin.json"
66
+ end
67
+ end
68
+
69
+ # Valida metadata
70
+ #
71
+ # @param path [String] Caminho do plugin
72
+ # @return [void]
73
+ def validate_metadata(path)
74
+ plugin_file = File.join(path, 'plugin.json')
75
+ return unless File.exist?(plugin_file)
76
+
77
+ begin
78
+ metadata = JSON.parse(File.read(plugin_file))
79
+
80
+ # Campos obrigatórios
81
+ %w[name version description].each do |field|
82
+ unless metadata[field]
83
+ @errors << "Missing required field: #{field}"
84
+ end
85
+ end
86
+
87
+ # Valida versão (semver)
88
+ if metadata['version'] && !valid_semver?(metadata['version'])
89
+ @errors << "Invalid version format: #{metadata['version']}"
90
+ end
91
+
92
+ # Valida nome
93
+ if metadata['name'] && !valid_name?(metadata['name'])
94
+ @errors << "Invalid plugin name: #{metadata['name']}"
95
+ end
96
+
97
+ # Valida gsd_version requirement
98
+ if metadata['gsd_version'] && !valid_version_requirement?(metadata['gsd_version'])
99
+ @errors << "Invalid gsd_version requirement: #{metadata['gsd_version']}"
100
+ end
101
+ rescue JSON::ParserError => e
102
+ @errors << "Invalid JSON in plugin.json: #{e.message}"
103
+ end
104
+ end
105
+
106
+ # Valida segurança
107
+ #
108
+ # @param path [String] Caminho do plugin
109
+ # @return [void]
110
+ def validate_security(path)
111
+ # Verifica arquivos suspeitos
112
+ suspicious_files = ['../../etc/passwd', '../..']
113
+
114
+ Dir.glob(File.join(path, '**', '*')).each do |file|
115
+ suspicious_files.each do |suspicious|
116
+ if file.include?(suspicious)
117
+ @errors << "Suspicious path detected: #{file}"
118
+ end
119
+ end
120
+ end
121
+
122
+ # Verifica se plugin.rb tenta acessar fora do diretório
123
+ plugin_rb = File.join(path, 'plugin.rb')
124
+ if File.exist?(plugin_rb)
125
+ content = File.read(plugin_rb)
126
+ if content.include?('`') || content.include?('system(')
127
+ @errors << "Plugin executes shell commands (potential security risk)"
128
+ end
129
+ end
130
+ end
131
+
132
+ # Valida versão semver
133
+ #
134
+ # @param version [String] Versão
135
+ # @return [Boolean] true se válido
136
+ def valid_semver?(version)
137
+ version.match?(/^\d+\.\d+\.\d+(-[a-zA-Z0-9]+)?$/)
138
+ end
139
+
140
+ # Valida nome do plugin
141
+ #
142
+ # @param name [String] Nome
143
+ # @return [Boolean] true se válido
144
+ def valid_name?(name)
145
+ name.match?(/^[a-z][a-z0-9-]*$/)
146
+ end
147
+
148
+ # Valida version requirement
149
+ #
150
+ # @param requirement [String] Requirement
151
+ # @return [Boolean] true se válido
152
+ def valid_version_requirement?(requirement)
153
+ requirement.match?(/^[>=<\s\d.]+$/)
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # GSD Plugins Module
4
+ #
5
+ # Sistema de plugins extensível para GSD Tools
6
+ #
7
+ # Uso:
8
+ # require 'gsd/plugins'
9
+ #
10
+ # # Carregar manager
11
+ # manager = Gsd::Plugins::Manager.new
12
+ #
13
+ # # Listar plugins
14
+ # plugins = manager.list
15
+ #
16
+ # # Ativar plugin
17
+ # manager.enable('my-plugin')
18
+ #
19
+ # # Marketplace
20
+ # marketplace = Gsd::Plugins::MarketplaceClient.new
21
+ # plugins = marketplace.search('github')
22
+ #
23
+ # # Search
24
+ # search = Gsd::Plugins::Search.new
25
+ # results = search.by_name('git')
26
+ #
27
+ # # Hooks
28
+ # hooks = Gsd::Plugins::Hooks.new
29
+ # hooks.register(:pre_command, 'my-plugin', ->(ctx) { puts "Before command" })
30
+ # hooks.run(:pre_command, { command: 'test' })
31
+
32
+ module Gsd
33
+ module Plugins
34
+ end
35
+ end
36
+
37
+ # Load core plugin files
38
+ require 'gsd/plugins/base'
39
+ require 'gsd/plugins/manager'
40
+ require 'gsd/plugins/loader'
41
+ require 'gsd/plugins/installer'
42
+ require 'gsd/plugins/validator'
43
+ require 'gsd/plugins/sandbox'
44
+ require 'gsd/plugins/marketplace'
45
+ require 'gsd/plugins/search'
46
+ require 'gsd/plugins/config'
47
+ require 'gsd/plugins/hooks'
48
+ require 'gsd/plugins/cli'
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'go/bridge'
4
+
5
+ module Gsd
6
+ # Profile - Wrapper Ruby para operações de profile via Go
7
+ module Profile
8
+ class << self
9
+ # Obtém um profile específico
10
+ #
11
+ # @param name [String] Nome do profile
12
+ # @param cwd [String] Diretório de trabalho
13
+ # @return [Hash] Profile data
14
+ def get(name, cwd: nil)
15
+ cwd ||= Dir.pwd
16
+ result = Go::Bridge.call('profile', { 'get' => name }, cwd: cwd)
17
+
18
+ if result['success']
19
+ result['data']
20
+ else
21
+ raise ProfileError, result['error']
22
+ end
23
+ end
24
+
25
+ # Define um profile
26
+ #
27
+ # @param name [String] Nome do profile
28
+ # @param model [String] Modelo do profile
29
+ # @param description [String] Descrição do profile
30
+ # @param cwd [String] Diretório de trabalho
31
+ # @return [Hash] Resultado da operação
32
+ def set(name, model, description: nil, cwd: nil)
33
+ cwd ||= Dir.pwd
34
+ args = { 'set' => name, 'model' => model }
35
+ args['description'] = description if description
36
+ result = Go::Bridge.call('profile', args, cwd: cwd)
37
+
38
+ if result['success']
39
+ result['data']
40
+ else
41
+ raise ProfileError, result['error']
42
+ end
43
+ end
44
+
45
+ # Lista todos os profiles
46
+ #
47
+ # @param cwd [String] Diretório de trabalho
48
+ # @return [Hash] Lista de profiles
49
+ def list(cwd: nil)
50
+ cwd ||= Dir.pwd
51
+ result = Go::Bridge.call('profile', { 'list' => true }, cwd: cwd)
52
+
53
+ if result['success']
54
+ result['data']
55
+ else
56
+ raise ProfileError, result['error']
57
+ end
58
+ end
59
+
60
+ # Define o profile padrão
61
+ #
62
+ # @param name [String] Nome do profile
63
+ # @param cwd [String] Diretório de trabalho
64
+ # @return [Hash] Resultado da operação
65
+ def set_default(name, cwd: nil)
66
+ cwd ||= Dir.pwd
67
+ result = Go::Bridge.call('profile', { 'set-default' => name }, cwd: cwd)
68
+
69
+ if result['success']
70
+ result['data']
71
+ else
72
+ raise ProfileError, result['error']
73
+ end
74
+ end
75
+
76
+ # Obtém o profile para um agent type
77
+ #
78
+ # @param agent_type [String] Tipo do agent
79
+ # @param cwd [String] Diretório de trabalho
80
+ # @return [Hash] Profile data
81
+ def get_agent(agent_type, cwd: nil)
82
+ cwd ||= Dir.pwd
83
+ result = Go::Bridge.call('profile', { 'get-agent' => agent_type }, cwd: cwd)
84
+
85
+ if result['success']
86
+ result['data']
87
+ else
88
+ raise ProfileError, result['error']
89
+ end
90
+ end
91
+
92
+ # Define o profile para um agent type
93
+ #
94
+ # @param agent_type [String] Tipo do agent
95
+ # @param profile_name [String] Nome do profile
96
+ # @param cwd [String] Diretório de trabalho
97
+ # @return [Hash] Resultado da operação
98
+ def set_agent(agent_type, profile_name, cwd: nil)
99
+ cwd ||= Dir.pwd
100
+ result = Go::Bridge.call('profile', { 'set-agent' => [agent_type, profile_name].join(' ') }, cwd: cwd)
101
+
102
+ if result['success']
103
+ result['data']
104
+ else
105
+ raise ProfileError, result['error']
106
+ end
107
+ end
108
+
109
+ # Obtém a configuração do pipeline
110
+ #
111
+ # @param cwd [String] Diretório de trabalho
112
+ # @return [Hash] Pipeline config
113
+ def pipeline(cwd: nil)
114
+ cwd ||= Dir.pwd
115
+ result = Go::Bridge.call('profile', { 'pipeline' => true }, cwd: cwd)
116
+
117
+ if result['success']
118
+ result['data']
119
+ else
120
+ raise ProfileError, result['error']
121
+ end
122
+ end
123
+ end
124
+
125
+ class ProfileError < StandardError; end
126
+ end
127
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'go/bridge'
4
+
5
+ module Gsd
6
+ # Research - Wrapper Ruby para operações de research via Go
7
+ module Research
8
+ class << self
9
+ # Realiza uma pesquisa web usando Brave Search API
10
+ #
11
+ # @param query [String] Query de pesquisa
12
+ # @param limit [Integer] Número máximo de resultados
13
+ # @param freshness [String] Freshness filter (day, week, month)
14
+ # @param cwd [String] Diretório de trabalho
15
+ # @return [Hash] Resultados da pesquisa
16
+ def websearch(query, limit: 10, freshness: nil, cwd: nil)
17
+ cwd ||= Dir.pwd
18
+ args = { 'websearch' => query }
19
+ args['limit'] = limit.to_s if limit
20
+ args['freshness'] = freshness if freshness
21
+ result = Go::Bridge.call('research', args, cwd: cwd)
22
+
23
+ if result['success']
24
+ result['data']
25
+ else
26
+ raise ResearchError, result['error']
27
+ end
28
+ end
29
+
30
+ # Inicia uma sessão de research para uma phase
31
+ #
32
+ # @param phase [String] Número da phase
33
+ # @param cwd [String] Diretório de trabalho
34
+ # @return [Hash] Resultado da operação
35
+ def start(phase, cwd: nil)
36
+ cwd ||= Dir.pwd
37
+ result = Go::Bridge.call('research', { 'start' => phase }, cwd: cwd)
38
+
39
+ if result['success']
40
+ result['data']
41
+ else
42
+ raise ResearchError, result['error']
43
+ end
44
+ end
45
+
46
+ # Completa uma sessão de research
47
+ #
48
+ # @param phase [String] Número da phase
49
+ # @param cwd [String] Diretório de trabalho
50
+ # @return [Hash] Resultado da operação
51
+ def complete(phase, cwd: nil)
52
+ cwd ||= Dir.pwd
53
+ result = Go::Bridge.call('research', { 'complete' => phase }, cwd: cwd)
54
+
55
+ if result['success']
56
+ result['data']
57
+ else
58
+ raise ResearchError, result['error']
59
+ end
60
+ end
61
+
62
+ # Helper: Verifica se a pesquisa retornou resultados
63
+ #
64
+ # @param result [Hash] Resultado da pesquisa
65
+ # @return [Boolean] true se há resultados
66
+ def has_results?(result)
67
+ result.is_a?(Hash) && result['success'] == true &&
68
+ result['data'] && result['data']['count'] && result['data']['count'] > 0
69
+ end
70
+
71
+ # Helper: Obtém os primeiros N resultados
72
+ #
73
+ # @param result [Hash] Resultado da pesquisa
74
+ # @param n [Integer] Número de resultados
75
+ # @return [Array] Primeiros N resultados
76
+ def top_results(result, n: 5)
77
+ return [] unless has_results?(result)
78
+ results = result['data']['results'] || []
79
+ results.first(n)
80
+ end
81
+ end
82
+
83
+ class ResearchError < StandardError; end
84
+ end
85
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'go/bridge'
4
+
5
+ module Gsd
6
+ # Roadmap - Wrapper Ruby para operações de ROADMAP.md via Go
7
+ class Roadmap
8
+ class << self
9
+ # Encontra uma phase no ROADMAP.md
10
+ def get_phase(phase_num, cwd: nil)
11
+ cwd ||= Dir.pwd
12
+ result = Go::Bridge.roadmap_get_phase(phase_num, cwd: cwd)
13
+
14
+ if result['success']
15
+ result['data']
16
+ else
17
+ raise RoadmapError, result['error']
18
+ end
19
+ end
20
+
21
+ # Analisa ROADMAP.md completo com status de disk
22
+ def analyze(cwd: nil)
23
+ cwd ||= Dir.pwd
24
+ result = Go::Bridge.roadmap_analyze(cwd: cwd)
25
+
26
+ if result['success']
27
+ result['data']
28
+ else
29
+ raise RoadmapError, result['error']
30
+ end
31
+ end
32
+
33
+ # Helper: verifica se ROADMAP.md existe
34
+ def exists?(cwd: nil)
35
+ cwd ||= Dir.pwd
36
+ roadmap_path = File.join(cwd, '.planning', 'ROADMAP.md')
37
+ File.exist?(roadmap_path)
38
+ end
39
+
40
+ # Helper: obtém lista de fases do roadmap
41
+ def phases(cwd: nil)
42
+ analysis = analyze(cwd: cwd)
43
+ analysis['phases']
44
+ end
45
+
46
+ # Helper: conta total de fases
47
+ def phase_count(cwd: nil)
48
+ analysis = analyze(cwd: cwd)
49
+ analysis['total_phases']
50
+ end
51
+
52
+ # Helper: verifica se phase está no disk
53
+ def phase_on_disk?(phase_num, cwd: nil)
54
+ analysis = analyze(cwd: cwd)
55
+ phase_data = analysis['phases'].find { |p| p['number'] == phase_num }
56
+ phase_data && phase_data['on_disk']
57
+ end
58
+
59
+ # Helper: obtém milestone atual
60
+ def milestone(cwd: nil)
61
+ analysis = analyze(cwd: cwd)
62
+ analysis['milestone']
63
+ end
64
+
65
+ # Helper: obtém milestone branch
66
+ def milestone_branch(cwd: nil)
67
+ analysis = analyze(cwd: cwd)
68
+ analysis['milestone_branch']
69
+ end
70
+
71
+ # Helper: verifica se todas as phases estão no disk
72
+ def all_phases_on_disk?(cwd: nil)
73
+ phases = phases(cwd: cwd)
74
+ return false if phases.nil? || phases.empty?
75
+
76
+ phases.all? { |p| p['on_disk'] }
77
+ end
78
+
79
+ # Helper: obtém phases sem diretório no disk
80
+ def missing_phase_dirs(cwd: nil)
81
+ phases = phases(cwd: cwd)
82
+ return [] if phases.nil?
83
+
84
+ phases.select { |p| !p['on_disk'] }
85
+ end
86
+ end
87
+
88
+ class RoadmapError < StandardError; end
89
+ end
90
+ end
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: commit
3
+ description: Create a commit with a well-formatted message
4
+ aliases: [/git-commit]
5
+ allowed_tools: [BashTool, FileReadTool]
6
+ model: claude-sonnet-4-5-20250929
7
+ ---
8
+
9
+ # Commit Skill
10
+
11
+ When invoked, you will:
12
+ 1. Review all changed files
13
+ 2. Understand what was changed
14
+ 3. Write a clear commit message
15
+ 4. Create the commit
16
+ 5. Report the commit hash
17
+
18
+ ## User Request
19
+
20
+ {{args}}
21
+
22
+ ## Commit Message Format
23
+
24
+ ```
25
+ <type>(<scope>): <subject>
26
+
27
+ <body>
28
+
29
+ <footer>
30
+ ```
31
+
32
+ ### Types
33
+ - `feat`: New feature
34
+ - `fix`: Bug fix
35
+ - `docs`: Documentation
36
+ - `style`: Formatting
37
+ - `refactor`: Code refactoring
38
+ - `test`: Tests
39
+ - `chore`: Maintenance
40
+
41
+ ### Example
42
+
43
+ ```
44
+ feat(auth): add JWT token validation
45
+
46
+ - Implement JWT token parsing
47
+ - Add token expiration check
48
+ - Update auth middleware
49
+
50
+ Closes #123
51
+ ```
52
+
53
+ ## Instructions
54
+
55
+ - Keep subject line under 50 characters
56
+ - Wrap body at 72 characters
57
+ - Explain WHAT and WHY, not HOW
58
+ - Reference issues when applicable