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,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
|
data/lib/gsd/plugins.rb
ADDED
|
@@ -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'
|
data/lib/gsd/profile.rb
ADDED
|
@@ -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
|
data/lib/gsd/research.rb
ADDED
|
@@ -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
|
data/lib/gsd/roadmap.rb
ADDED
|
@@ -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
|