faure 0.1.1
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/README.md +48 -0
- data/bin/faure-agent +3 -0
- data/bin/faure-codeur +3 -0
- data/lib/faure/agent.rb +89 -0
- data/lib/faure/codeur.rb +211 -0
- data/lib/faure/config.rb +23 -0
- data/lib/faure/version.rb +3 -0
- metadata +53 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b6caa6c0d2ed4a8ad383112454acb77a37df2af5db57cb1dc4f4cc6a23fc8191
|
|
4
|
+
data.tar.gz: 0de6f2b9d21361bcb0b8c3566187ed2ea5f08b35723a2a5da8a6c7f1c0471f1f
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3c7e2438a5e882f4fde1d94692452914b86786acaaeaca4882efff96f16853839a4d43d1556aa60968cb3520c8cc8b5b94dd21198803332faf6868f85cd5c857
|
|
7
|
+
data.tar.gz: 6c705864faf21d94c6213757d5e722eb747fd2d1a34a96d338ed980afae21c734ab80656e930be550486e1df8f7ab2409829c2cadb3c0b5c6c644684cad9149a
|
data/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# ShamoX Agent
|
|
2
|
+
|
|
3
|
+
Agent codeur autonome GitLab CI/CD + MLX local.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Issue GitLab + commentaire @shamox-codeur
|
|
9
|
+
↓
|
|
10
|
+
Webhook GitLab → trigger pipeline CI
|
|
11
|
+
↓
|
|
12
|
+
Job `codeur` (Qwen3-Coder-30B via mlx_lm)
|
|
13
|
+
- lit l'issue et l'historique des commentaires
|
|
14
|
+
- produit les modifications de fichiers en XML
|
|
15
|
+
- commit + push sur feature/issue-NNN
|
|
16
|
+
↓
|
|
17
|
+
Job `agent-git` (Qwen3.5-4B via mlx_lm)
|
|
18
|
+
- analyse le diff
|
|
19
|
+
- génère la description de MR
|
|
20
|
+
- crée la MR et assigne le reviewer
|
|
21
|
+
↓
|
|
22
|
+
Review manuelle + merge
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Variables CI/CD requises
|
|
26
|
+
|
|
27
|
+
| Variable | Description |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `SHAMOX_BOT_ID` | ID GitLab du compte de service shamox-bot |
|
|
30
|
+
| `SHAMOX_BOT_API_TOKEN` | Project Access Token avec scope `api` |
|
|
31
|
+
| `SHAMOX_REVIEWER_ID` | ID GitLab numérique du reviewer |
|
|
32
|
+
|
|
33
|
+
## Intégration dans un projet
|
|
34
|
+
|
|
35
|
+
Dans le `.gitlab-ci.yml` du projet cible, inclure :
|
|
36
|
+
|
|
37
|
+
```yaml
|
|
38
|
+
include:
|
|
39
|
+
- project: 'rlaures.pro/shamox-agent'
|
|
40
|
+
ref: main
|
|
41
|
+
file: '.gitlab-ci.yml'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Stack
|
|
45
|
+
|
|
46
|
+
- Ruby stdlib uniquement (pas de gems externes)
|
|
47
|
+
- mlx_lm sur Apple Silicon (M1/M4)
|
|
48
|
+
- GitLab CI/CD Runners headless (chacana, hecaton-macmini-001)
|
data/bin/faure-agent
ADDED
data/bin/faure-codeur
ADDED
data/lib/faure/agent.rb
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# lib/faure/agent.rb
|
|
3
|
+
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'uri'
|
|
7
|
+
require_relative 'version'
|
|
8
|
+
require_relative 'config'
|
|
9
|
+
|
|
10
|
+
module Faure
|
|
11
|
+
class Agent
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
payload = JSON.parse(ENV.fetch('TRIGGER_PAYLOAD', '{}'))
|
|
15
|
+
@issue_iid = payload.dig('issue', 'iid').to_s
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def run
|
|
19
|
+
branch_name = "feature/issue-#{@issue_iid}"
|
|
20
|
+
diff = `git diff origin/#{Config::TARGET_BRANCH}...#{branch_name} --stat`
|
|
21
|
+
diff_full = `git diff origin/#{Config::TARGET_BRANCH}...#{branch_name}`
|
|
22
|
+
|
|
23
|
+
if diff.strip.empty?
|
|
24
|
+
puts '[faure:agent] Aucun diff détecté, abandon.'
|
|
25
|
+
exit 0
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
puts "[faure:agent] Diff détecté :\n#{diff}"
|
|
29
|
+
|
|
30
|
+
description = call_model([
|
|
31
|
+
{ role: 'system', content: mr_system_prompt },
|
|
32
|
+
{ role: 'user', content: <<~PROMPT }
|
|
33
|
+
Voici le diff de la branche #{branch_name} :
|
|
34
|
+
#{diff_full[0..3000]}
|
|
35
|
+
Rédige une description de MR en 3-5 phrases : ce qui a été fait, pourquoi, et ce qu'il faut vérifier.
|
|
36
|
+
PROMPT
|
|
37
|
+
])
|
|
38
|
+
|
|
39
|
+
puts '[faure:agent] Description MR générée'
|
|
40
|
+
|
|
41
|
+
res = gitlab_post("/projects/#{Config::PROJECT_ID}/merge_requests", {
|
|
42
|
+
source_branch: branch_name,
|
|
43
|
+
target_branch: Config::TARGET_BRANCH,
|
|
44
|
+
title: "faure: résolution issue ##{@issue_iid}",
|
|
45
|
+
description: "Closes ##{@issue_iid}\n\n#{description}",
|
|
46
|
+
reviewer_ids: [Config::REVIEWER_ID],
|
|
47
|
+
remove_source_branch: true,
|
|
48
|
+
squash: false
|
|
49
|
+
})
|
|
50
|
+
body = JSON.parse(res.body)
|
|
51
|
+
|
|
52
|
+
if body['web_url']
|
|
53
|
+
puts "[faure:agent] MR créée : #{body['web_url']}"
|
|
54
|
+
else
|
|
55
|
+
puts "[faure:agent] Erreur création MR : #{res.body}"
|
|
56
|
+
exit 1
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def mr_system_prompt
|
|
63
|
+
if Config::LANG == 'fr'
|
|
64
|
+
'Tu es un assistant qui rédige des descriptions de Merge Request claires et concises en français. Réponds uniquement avec le texte de la description, sans balises.'
|
|
65
|
+
else
|
|
66
|
+
'You are an assistant that writes clear and concise Merge Request descriptions in English. Reply only with the description text, no markup.'
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def call_model(messages, max_tokens: 512)
|
|
71
|
+
uri = URI("#{Config::OPENAI_URL}/v1/chat/completions")
|
|
72
|
+
req = Net::HTTP::Post.new(uri)
|
|
73
|
+
req['Content-Type'] = 'application/json'
|
|
74
|
+
req.body = { model: Config::AGENT_MODEL, messages: messages, max_tokens: max_tokens }.to_json
|
|
75
|
+
res = Net::HTTP.start(uri.host, uri.port) { |h| h.request(req) }
|
|
76
|
+
JSON.parse(res.body).dig('choices', 0, 'message', 'content')
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def gitlab_post(path, payload)
|
|
80
|
+
uri = URI("#{Config::GITLAB_URL}/api/v4#{path}")
|
|
81
|
+
req = Net::HTTP::Post.new(uri)
|
|
82
|
+
req['PRIVATE-TOKEN'] = Config::API_TOKEN
|
|
83
|
+
req['Content-Type'] = 'application/json'
|
|
84
|
+
req.body = payload.to_json
|
|
85
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') { |h| h.request(req) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
end
|
|
89
|
+
end
|
data/lib/faure/codeur.rb
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# lib/faure/codeur.rb
|
|
3
|
+
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'uri'
|
|
7
|
+
require 'fileutils'
|
|
8
|
+
require_relative 'version'
|
|
9
|
+
require_relative 'config'
|
|
10
|
+
|
|
11
|
+
module Faure
|
|
12
|
+
class Codeur
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
payload = JSON.parse(ENV.fetch('TRIGGER_PAYLOAD', '{}'))
|
|
16
|
+
@note = payload.dig('object_attributes', 'note') || ''
|
|
17
|
+
@noteable_type = payload.dig('object_attributes', 'noteable_type')
|
|
18
|
+
@author_id = payload.dig('user', 'id').to_i
|
|
19
|
+
@issue_iid = payload.dig('issue', 'iid').to_s
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def run
|
|
23
|
+
guard!
|
|
24
|
+
puts "[faure] Issue ##{@issue_iid} — auteur_id=#{@author_id}"
|
|
25
|
+
|
|
26
|
+
issue = gitlab_get("/projects/#{Config::PROJECT_ID}/issues/#{@issue_iid}")
|
|
27
|
+
comments = gitlab_get("/projects/#{Config::PROJECT_ID}/issues/#{@issue_iid}/notes?sort=asc&per_page=100")
|
|
28
|
+
|
|
29
|
+
previous_summary = comments
|
|
30
|
+
.select { |n| n['body'].include?('<!-- faure:context -->') }
|
|
31
|
+
.last&.dig('body')
|
|
32
|
+
|
|
33
|
+
pending_question = comments
|
|
34
|
+
.select { |n| n['body'].include?('🤔 **faure demande :**') }
|
|
35
|
+
.last
|
|
36
|
+
|
|
37
|
+
mode = if pending_question && !@note.include?('@faure-codeur')
|
|
38
|
+
:answering_question
|
|
39
|
+
else
|
|
40
|
+
:new_instruction
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
puts "[faure] Mode : #{mode}"
|
|
44
|
+
|
|
45
|
+
branch_name = "feature/issue-#{@issue_iid}"
|
|
46
|
+
user_content = build_prompt(mode, issue, previous_summary, pending_question)
|
|
47
|
+
|
|
48
|
+
puts "[faure] Appel #{Config::CODER_MODEL}..."
|
|
49
|
+
response = call_model(Config::CODER_MODEL, [
|
|
50
|
+
{ role: 'system', content: system_prompt },
|
|
51
|
+
{ role: 'user', content: user_content }
|
|
52
|
+
])
|
|
53
|
+
puts "[faure] Réponse reçue (#{response.length} chars)"
|
|
54
|
+
|
|
55
|
+
files = parse_files(response)
|
|
56
|
+
question = parse_question(response)
|
|
57
|
+
|
|
58
|
+
if files.empty? && question.nil?
|
|
59
|
+
post_comment("⚠️ faure: réponse non structurée du modèle. Relancer ou reformuler l'issue.")
|
|
60
|
+
exit 1
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
commit_files(files, branch_name) unless files.empty?
|
|
64
|
+
post_comment("🤔 **faure demande :** #{question}") if question
|
|
65
|
+
|
|
66
|
+
summary = summarize_context(issue['title'], previous_summary, response, branch_name)
|
|
67
|
+
post_comment("<!-- faure:context -->\n#{summary}")
|
|
68
|
+
|
|
69
|
+
puts '[faure] Codeur terminé.'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def guard!
|
|
75
|
+
abort '[faure] Pas un commentaire sur issue, abandon.' unless @noteable_type == 'Issue'
|
|
76
|
+
abort '[faure] Commentaire du bot, abandon.' if @author_id == Config::BOT_ID
|
|
77
|
+
abort '[faure] Commentaire faure:context, abandon.' if @note.include?('<!-- faure:context -->')
|
|
78
|
+
abort '[faure] Pas de mention @faure-codeur ni réponse attendue.' \
|
|
79
|
+
unless @note.include?('@faure-codeur') || @note.match?(/^[^@]/)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def system_prompt
|
|
83
|
+
lang_instruction = Config::LANG == 'fr' ? 'Réponds en français.' : 'Reply in English.'
|
|
84
|
+
<<~SYSTEM
|
|
85
|
+
Tu es un agent codeur expert. Tu reçois une tâche de développement et tu dois produire les modifications de fichiers nécessaires.
|
|
86
|
+
#{lang_instruction}
|
|
87
|
+
Réponds UNIQUEMENT avec des balises XML :
|
|
88
|
+
- <file path="chemin/fichier">contenu complet du fichier</file> pour chaque fichier modifié
|
|
89
|
+
- <question>ta question</question> si tu as besoin de clarifications (peut coexister avec des <file>)
|
|
90
|
+
Ne produis rien en dehors de ces balises XML.
|
|
91
|
+
SYSTEM
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def build_prompt(mode, issue, previous_summary, pending_question)
|
|
95
|
+
case mode
|
|
96
|
+
when :answering_question
|
|
97
|
+
<<~PROMPT
|
|
98
|
+
Contexte de la tâche :
|
|
99
|
+
#{previous_summary || issue['description']}
|
|
100
|
+
|
|
101
|
+
Question que tu avais posée :
|
|
102
|
+
#{pending_question['body']}
|
|
103
|
+
|
|
104
|
+
Réponse :
|
|
105
|
+
#{@note}
|
|
106
|
+
PROMPT
|
|
107
|
+
when :new_instruction
|
|
108
|
+
if previous_summary
|
|
109
|
+
<<~PROMPT
|
|
110
|
+
Contexte de la tâche :
|
|
111
|
+
#{previous_summary}
|
|
112
|
+
|
|
113
|
+
Nouvelle instruction : #{@note}
|
|
114
|
+
PROMPT
|
|
115
|
+
else
|
|
116
|
+
<<~PROMPT
|
|
117
|
+
Issue ##{@issue_iid} : #{issue['title']}
|
|
118
|
+
|
|
119
|
+
Description :
|
|
120
|
+
#{issue['description']}
|
|
121
|
+
|
|
122
|
+
#{@note.include?('@faure-codeur') ? "Instruction : #{@note}" : ''}
|
|
123
|
+
PROMPT
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def commit_files(files, branch_name)
|
|
129
|
+
puts "[faure] #{files.size} fichier(s) à écrire..."
|
|
130
|
+
files.each do |f|
|
|
131
|
+
full_path = File.join(Config::PROJECT_DIR, f[:path])
|
|
132
|
+
FileUtils.mkdir_p(File.dirname(full_path))
|
|
133
|
+
File.write(full_path, f[:content])
|
|
134
|
+
puts " → #{f[:path]}"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
system("git config user.email 'faure@liant.dev'")
|
|
138
|
+
system("git config user.name 'Faure Codeur'")
|
|
139
|
+
system("git checkout -B #{branch_name}")
|
|
140
|
+
system('git add -A')
|
|
141
|
+
system("git commit -m 'faure(issue-#{@issue_iid}): modifications automatiques'")
|
|
142
|
+
system("git push origin #{branch_name} --force-with-lease 2>/dev/null || git push origin #{branch_name} --force")
|
|
143
|
+
puts "[faure] Push sur #{branch_name} effectué"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def summarize_context(issue_title, previous_summary, response, branch_name)
|
|
147
|
+
messages = [
|
|
148
|
+
{ role: 'system', content: "Tu es un assistant qui résume de façon concise l'état d'avancement d'une tâche de développement. Réponds uniquement en XML valide." },
|
|
149
|
+
{ role: 'user', content: <<~PROMPT }
|
|
150
|
+
Issue : #{issue_title}
|
|
151
|
+
Résumé précédent : #{previous_summary || 'aucun'}
|
|
152
|
+
Dernière réponse du codeur :
|
|
153
|
+
#{response[0..2000]}
|
|
154
|
+
|
|
155
|
+
Produis un résumé structuré :
|
|
156
|
+
<context>
|
|
157
|
+
<task>description courte de la tâche</task>
|
|
158
|
+
<done>ce qui a été fait</done>
|
|
159
|
+
<pending>ce qui reste ou les questions en suspens</pending>
|
|
160
|
+
<branch>#{branch_name}</branch>
|
|
161
|
+
</context>
|
|
162
|
+
PROMPT
|
|
163
|
+
]
|
|
164
|
+
call_model(Config::SUMMARY_MODEL, messages, max_tokens: 512)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def parse_files(content)
|
|
168
|
+
content.scan(/<file path="([^"]+)">(.*?)<\/file>/m).map do |path, body|
|
|
169
|
+
{ path: path.strip, content: body.strip }
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def parse_question(content)
|
|
174
|
+
m = content.match(/<question>(.*?)<\/question>/m)
|
|
175
|
+
m ? m[1].strip : nil
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def call_model(model, messages, max_tokens: 4096)
|
|
179
|
+
uri = URI("#{Config::OPENAI_URL}/v1/chat/completions")
|
|
180
|
+
req = Net::HTTP::Post.new(uri)
|
|
181
|
+
req['Content-Type'] = 'application/json'
|
|
182
|
+
req.body = { model: model, messages: messages, max_tokens: max_tokens }.to_json
|
|
183
|
+
res = Net::HTTP.start(uri.host, uri.port) { |h| h.request(req) }
|
|
184
|
+
data = JSON.parse(res.body)
|
|
185
|
+
data.dig('choices', 0, 'message', 'content') or raise "Réponse inattendue : #{res.body}"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def gitlab_get(path)
|
|
189
|
+
uri = URI("#{Config::GITLAB_URL}/api/v4#{path}")
|
|
190
|
+
req = Net::HTTP::Get.new(uri)
|
|
191
|
+
req['PRIVATE-TOKEN'] = Config::API_TOKEN
|
|
192
|
+
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') { |h| h.request(req) }
|
|
193
|
+
JSON.parse(res.body)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def gitlab_post(path, payload)
|
|
197
|
+
uri = URI("#{Config::GITLAB_URL}/api/v4#{path}")
|
|
198
|
+
req = Net::HTTP::Post.new(uri)
|
|
199
|
+
req['PRIVATE-TOKEN'] = Config::API_TOKEN
|
|
200
|
+
req['Content-Type'] = 'application/json'
|
|
201
|
+
req.body = payload.to_json
|
|
202
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') { |h| h.request(req) }
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def post_comment(body)
|
|
206
|
+
gitlab_post("/projects/#{Config::PROJECT_ID}/issues/#{@issue_iid}/notes", { body: body })
|
|
207
|
+
puts "[faure] Commentaire posté sur l'issue ##{@issue_iid}"
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
end
|
|
211
|
+
end
|
data/lib/faure/config.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Faure
|
|
2
|
+
module Config
|
|
3
|
+
# Obligatoires
|
|
4
|
+
API_TOKEN = ENV.fetch('FAURE_API_TOKEN')
|
|
5
|
+
REVIEWER_ID = ENV.fetch('FAURE_REVIEWER_ID').to_i
|
|
6
|
+
BOT_ID = ENV.fetch('FAURE_BOT_ID', '0').to_i
|
|
7
|
+
OPENAI_URL = ENV.fetch('OPENAI_API_URL', 'http://host.containers.internal:8080')
|
|
8
|
+
|
|
9
|
+
# Modèles — configurables par projet
|
|
10
|
+
CODER_MODEL = ENV.fetch('FAURE_CODER_MODEL', 'mlx-community/Qwen3-Coder-30B-A3B-Instruct-4bit')
|
|
11
|
+
AGENT_MODEL = ENV.fetch('FAURE_AGENT_MODEL', 'mlx-community/Qwen3.5-4B-4bit')
|
|
12
|
+
SUMMARY_MODEL = ENV.fetch('FAURE_SUMMARY_MODEL', 'mlx-community/Qwen3.5-4B-4bit')
|
|
13
|
+
|
|
14
|
+
# Options
|
|
15
|
+
TARGET_BRANCH = ENV.fetch('FAURE_TARGET_BRANCH', 'main')
|
|
16
|
+
LANG = ENV.fetch('FAURE_LANG', 'fr')
|
|
17
|
+
|
|
18
|
+
# GitLab CI — injectées automatiquement
|
|
19
|
+
GITLAB_URL = ENV.fetch('CI_SERVER_URL', 'https://gitlab.com')
|
|
20
|
+
PROJECT_ID = ENV.fetch('CI_PROJECT_ID')
|
|
21
|
+
PROJECT_DIR = ENV.fetch('CI_PROJECT_DIR', Dir.pwd)
|
|
22
|
+
end
|
|
23
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: faure
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Roland Laurès
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-03-16 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Faure orchestre un agent codeur et un agent git via GitLab CI/CD et un
|
|
14
|
+
backend OpenAI-compatible (mlx_lm, Ollama, OpenAI...).
|
|
15
|
+
email:
|
|
16
|
+
- roland@liant.dev
|
|
17
|
+
executables:
|
|
18
|
+
- faure-codeur
|
|
19
|
+
- faure-agent
|
|
20
|
+
extensions: []
|
|
21
|
+
extra_rdoc_files: []
|
|
22
|
+
files:
|
|
23
|
+
- README.md
|
|
24
|
+
- bin/faure-agent
|
|
25
|
+
- bin/faure-codeur
|
|
26
|
+
- lib/faure/agent.rb
|
|
27
|
+
- lib/faure/codeur.rb
|
|
28
|
+
- lib/faure/config.rb
|
|
29
|
+
- lib/faure/version.rb
|
|
30
|
+
homepage: https://gitlab.com/rol-public/shamox-agent
|
|
31
|
+
licenses:
|
|
32
|
+
- MIT
|
|
33
|
+
metadata: {}
|
|
34
|
+
post_install_message:
|
|
35
|
+
rdoc_options: []
|
|
36
|
+
require_paths:
|
|
37
|
+
- lib
|
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '3.0'
|
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
requirements: []
|
|
49
|
+
rubygems_version: 3.5.22
|
|
50
|
+
signing_key:
|
|
51
|
+
specification_version: 4
|
|
52
|
+
summary: Agent codeur autonome GitLab CI/CD + backend OpenAI-compatible
|
|
53
|
+
test_files: []
|