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,28 @@
1
+ ---
2
+ name: debug
3
+ description: Debug an issue or error in the code
4
+ aliases: [/dbg, /fix]
5
+ allowed_tools: [BashTool, FileReadTool, GrepTool, FileEditTool]
6
+ model: claude-sonnet-4-5-20250929
7
+ ---
8
+
9
+ # Debug Skill
10
+
11
+ When invoked, you will:
12
+ 1. Understand the error or issue
13
+ 2. Locate the source of the problem
14
+ 3. Analyze the code
15
+ 4. Propose and implement a fix
16
+ 5. Verify the fix works
17
+
18
+ ## User Request
19
+
20
+ {{args}}
21
+
22
+ ## Instructions
23
+
24
+ - Start by reproducing the error if possible
25
+ - Use logs and debugging output
26
+ - Check for common issues (null pointers, type errors, etc.)
27
+ - Test the fix thoroughly
28
+ - Explain what was wrong and how you fixed it
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: explain
3
+ description: Explain code, files, or concepts
4
+ aliases: [/exp, /describe]
5
+ allowed_tools: [FileReadTool, GrepTool]
6
+ model: claude-sonnet-4-5-20250929
7
+ ---
8
+
9
+ # Explain Skill
10
+
11
+ When invoked, you will:
12
+ 1. Read the specified code or files
13
+ 2. Understand the functionality
14
+ 3. Explain in clear terms
15
+ 4. Provide examples if helpful
16
+
17
+ ## User Request
18
+
19
+ {{args}}
20
+
21
+ ## Output Format
22
+
23
+ ### Overview
24
+ Brief description of what the code does
25
+
26
+ ### Detailed Explanation
27
+ Step-by-step explanation of how it works
28
+
29
+ ### Key Components
30
+ - **Component 1**: Description
31
+ - **Component 2**: Description
32
+
33
+ ### Examples
34
+ Code examples or usage if applicable
35
+
36
+ ## Instructions
37
+
38
+ - Use simple language
39
+ - Avoid jargon when possible
40
+ - Include code snippets for clarity
41
+ - Highlight important patterns or gotchas
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: plan
3
+ description: Create an implementation plan for a feature or task
4
+ aliases: [/planning]
5
+ allowed_tools: [FileReadTool, FileWriteTool, GrepTool]
6
+ model: claude-sonnet-4-5-20250929
7
+ ---
8
+
9
+ # Plan Skill
10
+
11
+ When invoked, you will:
12
+ 1. Understand the requirements
13
+ 2. Research the codebase
14
+ 3. Create a detailed implementation plan
15
+ 4. Break down into actionable tasks
16
+ 5. Identify risks and dependencies
17
+
18
+ ## User Request
19
+
20
+ {{args}}
21
+
22
+ ## Output Format
23
+
24
+ Create a plan with:
25
+
26
+ ### Overview
27
+ Brief description of what will be implemented
28
+
29
+ ### Tasks
30
+ - [ ] Task 1
31
+ - [ ] Task 2
32
+ - [ ] Task 3
33
+
34
+ ### Files to Change
35
+ - `path/to/file1.rb` - Description
36
+ - `path/to/file2.go` - Description
37
+
38
+ ### Risks
39
+ - Potential issues to watch for
40
+
41
+ ### Testing
42
+ - How to verify the implementation
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: verify
3
+ description: Verify a code change does what it should by running the app
4
+ aliases: [/v, /check]
5
+ allowed_tools: [BashTool, FileReadTool, GrepTool]
6
+ model: claude-sonnet-4-5-20250929
7
+ ---
8
+
9
+ # Verify Skill
10
+
11
+ When invoked, you will:
12
+ 1. Read the changed files
13
+ 2. Understand the intent
14
+ 3. Run tests/verification
15
+ 4. Report results
16
+
17
+ ## User Request
18
+
19
+ {{args}}
20
+
21
+ ## Instructions
22
+
23
+ - Be thorough in verification
24
+ - Run all relevant tests
25
+ - Check for edge cases
26
+ - Report any issues found
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Gsd
6
+ module Skills
7
+ # Skills Loader - Carrega skills bundled e do usuário
8
+ #
9
+ # Responsável por:
10
+ # - Carregar skills bundled
11
+ # - Carregar skills do usuário
12
+ # - Parsear formato de skill markdown
13
+ # - Executar skills
14
+ class Loader
15
+ attr_reader :bundled_skills, :user_skills
16
+
17
+ # Inicializa o loader
18
+ #
19
+ # @param cwd [String] Diretório de trabalho
20
+ def initialize(cwd: Dir.pwd)
21
+ @cwd = cwd
22
+ @bundled_skills = {}
23
+ @user_skills = {}
24
+
25
+ load_bundled_skills
26
+ load_user_skills
27
+ end
28
+
29
+ # Retorna todas as skills (bundled + user)
30
+ #
31
+ # @return [Hash] Todas as skills
32
+ def all
33
+ @bundled_skills.merge(@user_skills)
34
+ end
35
+
36
+ # Busca uma skill por nome
37
+ #
38
+ # @param name [String] Nome da skill
39
+ # @return [Hash,nil] Skill ou nil
40
+ def find(name)
41
+ @user_skills[name] || @bundled_skills[name]
42
+ end
43
+
44
+ # Lista todas as skills disponíveis
45
+ #
46
+ # @return [Array<String>] Lista de nomes
47
+ def list
48
+ all.keys.sort
49
+ end
50
+
51
+ # Executa uma skill
52
+ #
53
+ # @param name [String] Nome da skill
54
+ # @param args [String] Argumentos da skill
55
+ # @return [String] Resultado da execução
56
+ def execute(name, args = '')
57
+ skill = find(name)
58
+ raise "Skill not found: #{name}" unless skill
59
+
60
+ # Skill é um hash com :prompt e :metadata
61
+ prompt = skill[:prompt]
62
+ prompt = prompt.gsub('{{args}}', args) if args && !args.empty?
63
+
64
+ prompt
65
+ end
66
+
67
+ # Recarrega as skills
68
+ #
69
+ # @return [void]
70
+ def reload
71
+ @bundled_skills = {}
72
+ @user_skills = {}
73
+ load_bundled_skills
74
+ load_user_skills
75
+ end
76
+
77
+ private
78
+
79
+ # Carrega skills bundled
80
+ #
81
+ # @return [void]
82
+ def load_bundled_skills
83
+ bundled_dir = File.join(__dir__, 'bundled')
84
+ return unless File.directory?(bundled_dir)
85
+
86
+ Dir.glob(File.join(bundled_dir, '*.md')).each do |file|
87
+ skill = parse_skill_file(file)
88
+ @bundled_skills[skill[:name]] = skill if skill
89
+ end
90
+ end
91
+
92
+ # Carrega skills do usuário
93
+ #
94
+ # @return [void]
95
+ def load_user_skills
96
+ user_dir = File.join(@cwd, '.gsd', 'skills')
97
+ return unless File.directory?(user_dir)
98
+
99
+ Dir.glob(File.join(user_dir, '*.md')).each do |file|
100
+ skill = parse_skill_file(file)
101
+ @user_skills[skill[:name]] = skill if skill
102
+ end
103
+ end
104
+
105
+ # Parseia um arquivo de skill
106
+ #
107
+ # @param file [String] Caminho do arquivo
108
+ # @return [Hash,nil] Skill parseada ou nil
109
+ def parse_skill_file(file)
110
+ content = File.read(file)
111
+
112
+ # Extrai frontmatter YAML
113
+ frontmatter = extract_frontmatter(content)
114
+ body = extract_body(content)
115
+
116
+ return nil unless frontmatter
117
+
118
+ {
119
+ name: frontmatter['name'] || File.basename(file, '.md'),
120
+ description: frontmatter['description'] || '',
121
+ aliases: frontmatter['aliases'] || [],
122
+ allowed_tools: frontmatter['allowed_tools'] || [],
123
+ model: frontmatter['model'],
124
+ prompt: body,
125
+ metadata: frontmatter,
126
+ source: file
127
+ }
128
+ end
129
+
130
+ # Extrai frontmatter YAML do conteúdo
131
+ #
132
+ # @param content [String] Conteúdo do arquivo
133
+ # @return [Hash,nil] Frontmatter parseado ou nil
134
+ def extract_frontmatter(content)
135
+ # Frontmatter está entre --- no início do arquivo
136
+ return nil unless content.start_with?("---\n")
137
+
138
+ parts = content.split(/^---$/, 3)
139
+ return nil if parts.length < 2
140
+
141
+ yaml_content = parts[1]
142
+ parse_yaml_simple(yaml_content)
143
+ end
144
+
145
+ # Extrai corpo do conteúdo (após frontmatter)
146
+ #
147
+ # @param content [String] Conteúdo do arquivo
148
+ # @return [String] Corpo do arquivo
149
+ def extract_body(content)
150
+ parts = content.split(/^---$/, 3)
151
+ return content if parts.length < 3
152
+
153
+ parts[2].strip
154
+ end
155
+
156
+ # Parse YAML simples (sem dependências externas)
157
+ #
158
+ # @param yaml [String] YAML string
159
+ # @return [Hash] Hash parseado
160
+ def parse_yaml_simple(yaml)
161
+ result = {}
162
+
163
+ yaml.each_line do |line|
164
+ line = line.strip
165
+ next if line.empty? || line.start_with?('#')
166
+
167
+ if line.include?(':')
168
+ key, value = line.split(':', 2)
169
+ key = key.strip
170
+ value = value.strip
171
+
172
+ # Remove aspas
173
+ value = value[1..-2] if value.start_with?('"') && value.end_with?('"')
174
+ value = value[1..-2] if value.start_with?("'") && value.end_with?("'")
175
+
176
+ # Parseia array simples
177
+ if value.start_with?('[') && value.end_with?(']')
178
+ value = value[1..-2].split(',').map(&:strip)
179
+ end
180
+
181
+ result[key] = value
182
+ end
183
+ end
184
+
185
+ result
186
+ end
187
+ end
188
+ end
189
+ end
data/lib/gsd/state.rb ADDED
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'go/bridge'
4
+
5
+ module Gsd
6
+ # State - Wrapper Ruby para operações de STATE.md via Go
7
+ class State
8
+ class << self
9
+ # Carrega STATE.md e retorna dados completos
10
+ def load(cwd: nil)
11
+ cwd ||= Dir.pwd
12
+ result = Go::Bridge.state_load(cwd: cwd)
13
+
14
+ if result['success']
15
+ result['data']
16
+ else
17
+ raise StateError, result['error']
18
+ end
19
+ end
20
+
21
+ # Retorna frontmatter do STATE.md como JSON
22
+ def json(cwd: nil)
23
+ cwd ||= Dir.pwd
24
+ result = Go::Bridge.state_json(cwd: cwd)
25
+
26
+ if result['success']
27
+ result['data']
28
+ else
29
+ raise StateError, result['error']
30
+ end
31
+ end
32
+
33
+ # Atualiza um campo específico no STATE.md
34
+ def update(field:, value:, cwd: nil)
35
+ cwd ||= Dir.pwd
36
+ result = Go::Bridge.state_update(field: field, value: value, cwd: cwd)
37
+
38
+ if result['success']
39
+ result['data']
40
+ else
41
+ raise StateError, result['error']
42
+ end
43
+ end
44
+
45
+ # Atualiza múltiplos campos no STATE.md
46
+ def patch(fields:, cwd: nil)
47
+ cwd ||= Dir.pwd
48
+ result = Go::Bridge.state_patch(fields: fields, cwd: cwd)
49
+
50
+ if result['success']
51
+ result['data']
52
+ else
53
+ raise StateError, result['error']
54
+ end
55
+ end
56
+
57
+ # Obtém conteúdo do STATE.md ou uma seção específica
58
+ def get(section: nil, cwd: nil)
59
+ cwd ||= Dir.pwd
60
+ result = Go::Bridge.state_get(section: section, cwd: cwd)
61
+
62
+ if result['success']
63
+ result['data']
64
+ else
65
+ raise StateError, result['error']
66
+ end
67
+ end
68
+
69
+ # Helper: verifica se STATE.md existe
70
+ def exists?(cwd: nil)
71
+ cwd ||= Dir.pwd
72
+ state_path = File.join(cwd, '.planning', 'STATE.md')
73
+ File.exist?(state_path)
74
+ end
75
+
76
+ # Helper: obtém valor de um campo específico
77
+ def fetch(field, cwd: nil)
78
+ data = json(cwd: cwd)
79
+ data[field]
80
+ end
81
+
82
+ # Helper: atualiza progresso automaticamente
83
+ def update_progress(plans_of:, plans_total:, cwd: nil)
84
+ progress = calculate_progress(plans_of, plans_total)
85
+ update(field: 'plans_of', value: plans_of.to_s, cwd: cwd)
86
+ update(field: 'plans_total', value: plans_total.to_s, cwd: cwd)
87
+ update(field: 'progress', value: progress, cwd: cwd)
88
+ end
89
+
90
+ private
91
+
92
+ def calculate_progress(of, total)
93
+ return '0%' if total.nil? || total.to_i == 0
94
+
95
+ percentage = (of.to_f / total.to_i * 100).round
96
+ "#{percentage}%"
97
+ end
98
+ end
99
+
100
+ class StateError < StandardError; end
101
+ end
102
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'go/bridge'
4
+
5
+ module Gsd
6
+ # Template - Wrapper Ruby para operações de template filling via Go
7
+ module Template
8
+ class << self
9
+ # Preenche template de SUMMARY.md
10
+ #
11
+ # @param phase [String] Número da phase
12
+ # @param plan [String] Número do plan
13
+ # @param name [String] Nome do summary
14
+ # @param fields [Hash] Campos adicionais
15
+ # @param cwd [String] Diretório de trabalho
16
+ # @return [Hash] Resultado do preenchimento
17
+ def fill_summary(phase:, plan:, name:, fields: {}, cwd: nil)
18
+ cwd ||= Dir.pwd
19
+ args = build_template_args('summary', phase, plan, name, fields)
20
+ result = Go::Bridge.call('template', args, cwd: cwd)
21
+
22
+ if result['success']
23
+ result['data']
24
+ else
25
+ raise TemplateError, result['error']
26
+ end
27
+ end
28
+
29
+ # Preenche template de PLAN.md
30
+ #
31
+ # @param phase [String] Número da phase
32
+ # @param plan [String] Número do plan
33
+ # @param type [String] Tipo do plan (execute, tdd)
34
+ # @param wave [String] Número da wave
35
+ # @param fields [Hash] Campos adicionais
36
+ # @param cwd [String] Diretório de trabalho
37
+ # @return [Hash] Resultado do preenchimento
38
+ def fill_plan(phase:, plan:, type: 'execute', wave: '1', fields: {}, cwd: nil)
39
+ cwd ||= Dir.pwd
40
+ args = build_template_args('plan', phase, plan, nil, fields)
41
+ args['type'] = type
42
+ args['wave'] = wave
43
+ result = Go::Bridge.call('template', args, cwd: cwd)
44
+
45
+ if result['success']
46
+ result['data']
47
+ else
48
+ raise TemplateError, result['error']
49
+ end
50
+ end
51
+
52
+ # Preenche template de VERIFICATION.md
53
+ #
54
+ # @param phase [String] Número da phase
55
+ # @param fields [Hash] Campos adicionais
56
+ # @param cwd [String] Diretório de trabalho
57
+ # @return [Hash] Resultado do preenchimento
58
+ def fill_verification(phase:, fields: {}, cwd: nil)
59
+ cwd ||= Dir.pwd
60
+ args = { 'fill' => 'verification', 'phase' => phase }
61
+ fields.each { |k, v| args[k.to_s] = v.to_s }
62
+ result = Go::Bridge.call('template', args, cwd: cwd)
63
+
64
+ if result['success']
65
+ result['data']
66
+ else
67
+ raise TemplateError, result['error']
68
+ end
69
+ end
70
+
71
+ # Preenche template de CONTEXT.md
72
+ #
73
+ # @param phase [String] Número da phase
74
+ # @param fields [Hash] Campos adicionais
75
+ # @param cwd [String] Diretório de trabalho
76
+ # @return [Hash] Resultado do preenchimento
77
+ def fill_context(phase:, fields: {}, cwd: nil)
78
+ cwd ||= Dir.pwd
79
+ args = { 'fill' => 'context', 'phase' => phase }
80
+ fields.each { |k, v| args[k.to_s] = v.to_s }
81
+ result = Go::Bridge.call('template', args, cwd: cwd)
82
+
83
+ if result['success']
84
+ result['data']
85
+ else
86
+ raise TemplateError, result['error']
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ def build_template_args(template_type, phase, plan, name, fields)
93
+ args = {
94
+ 'fill' => template_type,
95
+ 'phase' => phase,
96
+ 'plan' => plan
97
+ }
98
+ args['name'] = name if name
99
+ fields.each { |k, v| args[k.to_s] = v.to_s }
100
+ args
101
+ end
102
+ end
103
+
104
+ class TemplateError < StandardError; end
105
+ end
106
+ end