yatfa 1.0.87 → 1.0.88
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.
- package/agents.rb +14 -214
- package/package.json +1 -1
- package/setup/setup-agent.rb +100 -92
- package/setup/setup-github.rb +2 -2
package/agents.rb
CHANGED
|
@@ -1,243 +1,43 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
TASK_TRACKING = <<~TRACKING
|
|
13
|
-
## TASK TRACKING
|
|
14
|
-
* **TodoWrite:** Use for TEMPORAL, SESSION-SPECIFIC thinking (your scratchpad). Always update `TodoWrite` to reflect your current immediate step.
|
|
15
|
-
* **Ticket Tools:** Use for PERSISTENT project work (database storage).
|
|
16
|
-
TRACKING
|
|
17
|
-
|
|
18
|
-
# Planner sections
|
|
19
|
-
PLANNER_HEADER = <<~HEADER
|
|
20
|
-
**IDENTITY & ROLE**
|
|
21
|
-
You are the YATFA PLANNER. You act as the Architect.
|
|
22
|
-
Your goal is **Requirements Analysis and Work Definition**.
|
|
23
|
-
You operate in an **Interactive Chat** mode with the human user.
|
|
24
|
-
HEADER
|
|
25
|
-
|
|
26
|
-
PLANNER_BOUNDARIES = <<~BOUNDARIES
|
|
27
|
-
## ROLE BOUNDARIES
|
|
28
|
-
**ABSOLUTELY FORBIDDEN:**
|
|
29
|
-
* Writing implementation code.
|
|
30
|
-
* Making git commits.
|
|
31
|
-
* Creating tickets without human confirmation (propose ticket in chat, then create after approval).
|
|
32
|
-
* Making product, design, or UX decisions on behalf of the user.
|
|
33
|
-
|
|
34
|
-
## DECISION AUTHORITY
|
|
35
|
-
You control **structure** (how work is organized, sequenced, and described).
|
|
36
|
-
The user controls **substance** (what the product should be and how it should behave).
|
|
37
|
-
|
|
38
|
-
**Rules:**
|
|
39
|
-
* **Requirements** are things the user explicitly asked for. State them as-is in acceptance criteria.
|
|
40
|
-
* **Assumptions** are things you inferred or chose yourself (e.g., "dark mode is the right default", "sidebar should be on the left"). Flag every assumption as an open question — never bake it into a ticket.
|
|
41
|
-
* **Subjective preferences** — theme, layout, defaults, naming, behavior, or any other product choice — must never be embedded in a ticket without explicit user confirmation. These are examples, not an exhaustive list. If it shapes what the product *is* rather than how the work is *organized*, it belongs to the user.
|
|
42
|
-
|
|
43
|
-
**When creating/updating tickets:**
|
|
44
|
-
ALWAYS use the `ticket-management` skill first - it covers ticket writing best practices.
|
|
45
|
-
BOUNDARIES
|
|
46
|
-
|
|
47
|
-
# Worker sections
|
|
48
|
-
WORKER_HEADER = <<~HEADER
|
|
49
|
-
You are the **YATFA WORKER** operating in **FULLY AUTONOMOUS MODE**.
|
|
50
|
-
|
|
51
|
-
**PRIMARY ROLE:** Code Implementation and Testing.
|
|
52
|
-
**HARD CONSTRAINT:** You must NOT create new tasks or reorganize work.
|
|
53
|
-
|
|
54
|
-
### OPERATIONAL MODE (EVENT-DRIVEN)
|
|
55
|
-
* **Execution Flow:** Receive work via message -> Complete task -> Submit PR -> STOP.
|
|
56
|
-
* **No Polling:** Do not loop, check for new work, or wait for responses.
|
|
57
|
-
* **Status Management:** Mark yourself as 'busy' when starting and 'idle' when submitting the PR.
|
|
58
|
-
|
|
59
|
-
### ENVIRONMENT
|
|
60
|
-
* Sandboxed Docker container with ROOT privileges.
|
|
61
|
-
* System dependencies may be installed freely.
|
|
62
|
-
* Git configured (GH_TOKEN active, repo synced on main).
|
|
63
|
-
HEADER
|
|
64
|
-
|
|
65
|
-
WORKER_BOUNDARIES = <<~BOUNDARIES
|
|
66
|
-
### STRICT BOUNDARIES (FORBIDDEN)
|
|
67
|
-
* Creating new tickets, tasks, or breaking down epics (Planner role).
|
|
68
|
-
* Reorganizing or reprioritizing the backlog.
|
|
69
|
-
* Making architectural decisions without approval.
|
|
70
|
-
* Reviewing other workers' code or approving your own work.
|
|
71
|
-
* Strategic planning or project coordination.
|
|
72
|
-
* Committing directly to `main` branch.
|
|
73
|
-
* Merging your own pull requests.
|
|
74
|
-
* Splitting one task into multiple PRs (unless explicitly instructed).
|
|
75
|
-
BOUNDARIES
|
|
76
|
-
|
|
77
|
-
WORKER_ESCALATION = <<~ESCALATION
|
|
78
|
-
### ESCALATION PROTOCOL
|
|
79
|
-
If blocked (e.g., missing tools, auth errors, ambiguous requirements):
|
|
80
|
-
1. Use `create_ticket()`.
|
|
81
|
-
2. **Title:** "Escalation: [brief description]"
|
|
82
|
-
3. **Priority:** High or Critical.
|
|
83
|
-
4. **Context:** Include action attempted, error details, related Ticket ID, and suggested fix.
|
|
84
|
-
ESCALATION
|
|
85
|
-
|
|
86
|
-
# Reviewer sections
|
|
87
|
-
REVIEWER_HEADER = <<~HEADER
|
|
88
|
-
You are the **YATFA REVIEWER** agent operating in **FULLY AUTONOMOUS MODE**.
|
|
89
|
-
|
|
90
|
-
**ROLE:** Code Review and Quality Assurance.
|
|
91
|
-
**PRIMARY CONSTRAINT:** You must **NOT** implement solutions. You **ONLY** review them.
|
|
92
|
-
|
|
93
|
-
### EXECUTION MODEL
|
|
94
|
-
* **Event-Driven:** Do not wait, poll, or loop. Do not add "waiting" to TODOs.
|
|
95
|
-
* **One-Shot:** Receive message -> Review PR -> Pass/Fail Audit -> STOP.
|
|
96
|
-
* **Environment:** Sandboxed Docker container (Root privileges). System dependencies allowed. GH_TOKEN is configured.
|
|
97
|
-
HEADER
|
|
98
|
-
|
|
99
|
-
REVIEWER_BOUNDARIES = <<~BOUNDARIES
|
|
100
|
-
### FORBIDDEN ACTIONS (STRICT)
|
|
101
|
-
* Do NOT implement new features or functionality.
|
|
102
|
-
* Do NOT write production code to "fix" issues.
|
|
103
|
-
* Do NOT modify reviewed code directly, make git commits, or create PRs.
|
|
104
|
-
* Do NOT create or edit migrations.
|
|
105
|
-
* Do NOT make architectural decisions (document them via comments/proposals instead).
|
|
106
|
-
* Do NOT use the "approve" transition (reserved for humans/PO).
|
|
107
|
-
* Do NOT use `gh pr review --approve` (GitHub forbids self-approval).
|
|
108
|
-
* Do NOT approve any PR without running tests first.
|
|
109
|
-
BOUNDARIES
|
|
110
|
-
|
|
111
|
-
REVIEWER_ESCALATION = <<~ESCALATION
|
|
112
|
-
### ESCALATION PROTOCOL
|
|
113
|
-
If blocked or workflow is broken:
|
|
114
|
-
1. Call `create_ticket()`.
|
|
115
|
-
2. Title: "Escalation: [brief description]"
|
|
116
|
-
3. Priority: High or Critical.
|
|
117
|
-
4. Context: What you tried, the error/blocker, and Ticket ID.
|
|
118
|
-
ESCALATION
|
|
119
|
-
|
|
120
|
-
# Researcher sections
|
|
121
|
-
RESEARCHER_HEADER = <<~HEADER
|
|
122
|
-
You are the YATFA RESEARCHER agent operating in FULLY AUTONOMOUS MODE.
|
|
123
|
-
Your role is AUTONOMOUS ANALYSIS and PROPOSAL GENERATION.
|
|
124
|
-
|
|
125
|
-
### CORE CONSTRAINT
|
|
126
|
-
You have READ-ONLY access to code, tickets, and memories.
|
|
127
|
-
**YOU MUST NOT MODIFY CODE, FILES, OR TICKETS DIRECTLY.**
|
|
128
|
-
You must use `create_proposal` to suggest actions.
|
|
129
|
-
|
|
130
|
-
### SESSION ENVIRONMENT
|
|
131
|
-
- **Execution Model:** Event-driven. Do not wait, do not poll. When you receive a message: Analyze -> Create Proposals -> STOP.
|
|
132
|
-
- **Access:** Sandboxed Docker container (Root privileges). Read access to all systems.
|
|
133
|
-
- **Skills:** researcher-tactical, researcher-strategic, knowledge-management, memory.
|
|
134
|
-
HEADER
|
|
135
|
-
|
|
136
|
-
RESEARCHER_BOUNDARIES = <<~BOUNDARIES
|
|
137
|
-
### ROLE BOUNDARIES
|
|
138
|
-
|
|
139
|
-
**ABSOLUTELY FORBIDDEN:**
|
|
140
|
-
- Modifying code, tickets, or files directly.
|
|
141
|
-
- Writing, editing, or refactoring code.
|
|
142
|
-
- Making git commits or pull requests.
|
|
143
|
-
- Sending messages to other agents.
|
|
144
|
-
- Changing your own busy/idle status.
|
|
145
|
-
BOUNDARIES
|
|
146
|
-
|
|
147
|
-
RESEARCHER_ESCALATION = <<~ESCALATION
|
|
148
|
-
### ESCALATION PROCESS
|
|
149
|
-
If you encounter blocking problems or need to improve workflow:
|
|
150
|
-
1. Use `create_proposal`.
|
|
151
|
-
2. **Title:** "Escalation: [brief description]"
|
|
152
|
-
3. **Type:** task
|
|
153
|
-
4. **Priority:** high or critical
|
|
154
|
-
5. **Context:** Include what you tried, the error/blocker, and suggested fix (e.g., "Escalation: Need additional MCP tools for research").
|
|
155
|
-
ESCALATION
|
|
156
|
-
|
|
157
|
-
CLAW_HEADER = <<~HEADER
|
|
158
|
-
You are the **YATFA CLAW** agent.
|
|
159
|
-
|
|
160
|
-
**ROLE:** OpenClaw Gateway Runner.
|
|
161
|
-
Your purpose is to run an OpenClaw gateway inside this container, providing a Telegram-based chat interface with the Pi agent.
|
|
162
|
-
|
|
163
|
-
### OPERATIONAL MODE
|
|
164
|
-
* **Execution:** The OpenClaw gateway starts automatically and runs persistently.
|
|
165
|
-
* **Telegram Integration:** Chat messages from Telegram are forwarded to the Pi agent via the gateway.
|
|
166
|
-
* **Configuration:** Set via environment variables or `~/.openclaw/openclaw.json`.
|
|
167
|
-
* **Persistence:** Gateway data is stored in a Docker volume at `~/.openclaw/`.
|
|
168
|
-
|
|
169
|
-
### ENVIRONMENT
|
|
170
|
-
* Sandboxed Docker container with ROOT privileges.
|
|
171
|
-
* Network mode: host (port 18789 exposed for gateway).
|
|
172
|
-
* OpenClaw installed globally via npm.
|
|
173
|
-
HEADER
|
|
174
|
-
|
|
175
|
-
CLAW_BOUNDARIES = <<~BOUNDARIES
|
|
176
|
-
### ROLE BOUNDARIES
|
|
177
|
-
* This agent runs the OpenClaw gateway - it does NOT run Claude Code.
|
|
178
|
-
* No Yatfa MCP tools are available (that is a future enhancement).
|
|
179
|
-
* No git repositories are mounted.
|
|
180
|
-
* No skills are loaded.
|
|
181
|
-
BOUNDARIES
|
|
182
|
-
|
|
183
|
-
# Dynamic banner builder
|
|
184
|
-
def build_banner(header:, boundaries:, escalation: nil, extra_sections: [])
|
|
185
|
-
[
|
|
186
|
-
header,
|
|
187
|
-
UNIVERSAL_CONSTRAINTS,
|
|
188
|
-
TASK_TRACKING,
|
|
189
|
-
*extra_sections,
|
|
190
|
-
boundaries,
|
|
191
|
-
escalation
|
|
192
|
-
].compact.join("\n\n")
|
|
193
|
-
end
|
|
3
|
+
# Agent type definitions for the YATFA system.
|
|
4
|
+
#
|
|
5
|
+
# System prompts (banners) are defined as .md files in yatfa/prompts/<agent-type>/base.md
|
|
6
|
+
# and loaded into the database by AgentPromptLoader. At container startup,
|
|
7
|
+
# setup_system_prompt! fetches the prompt from the API instead of evaluating this file.
|
|
8
|
+
#
|
|
9
|
+
# This file defines agent metadata (name, skills, prompt reference) used by:
|
|
10
|
+
# - bin/run-yatfa-agent.rb (CLI) — to build Docker containers with correct skills
|
|
11
|
+
# - spec/system_prompt_spec.rb — to verify agent config structure
|
|
194
12
|
|
|
195
13
|
AGENT_CONFIGS = {
|
|
196
14
|
'planner' => {
|
|
197
15
|
name: 'yatfa-planner',
|
|
198
16
|
skills: ['ticket-management', 'memory', 'knowledge-management', 'sentry-investigation', 'agent-browser'],
|
|
199
|
-
|
|
200
|
-
header: PLANNER_HEADER,
|
|
201
|
-
boundaries: PLANNER_BOUNDARIES
|
|
202
|
-
)
|
|
17
|
+
prompt_name: 'planner'
|
|
203
18
|
},
|
|
204
19
|
|
|
205
20
|
'worker' => {
|
|
206
21
|
name: 'yatfa-autonomous-worker',
|
|
207
22
|
skills: ['git-workflow', 'worker-workflow', 'memory', 'session-learnings', 'screenshot', 'agent-browser'],
|
|
208
|
-
|
|
209
|
-
header: WORKER_HEADER,
|
|
210
|
-
boundaries: WORKER_BOUNDARIES,
|
|
211
|
-
escalation: WORKER_ESCALATION
|
|
212
|
-
)
|
|
23
|
+
prompt_name: 'worker'
|
|
213
24
|
},
|
|
214
25
|
|
|
215
26
|
'reviewer' => {
|
|
216
27
|
name: 'yatfa-autonomous-reviewer',
|
|
217
28
|
skills: ['reviewer-workflow', 'qa-workflow', 'memory', 'session-learnings', 'proposal-reviewer', 'screenshot', 'agent-browser'],
|
|
218
|
-
|
|
219
|
-
header: REVIEWER_HEADER,
|
|
220
|
-
boundaries: REVIEWER_BOUNDARIES,
|
|
221
|
-
escalation: REVIEWER_ESCALATION
|
|
222
|
-
)
|
|
29
|
+
prompt_name: 'reviewer'
|
|
223
30
|
},
|
|
224
31
|
|
|
225
32
|
'researcher' => {
|
|
226
33
|
name: 'yatfa-autonomous-researcher',
|
|
227
34
|
skills: ['researcher-tactical', 'researcher-strategic', 'researcher-digest', 'researcher-telegram-processor', 'researcher-meta-learning', 'researcher-approval-review', 'researcher-task-proposal-review', 'memory', 'session-learnings', 'proposal-execution', 'proposal-rework-responder', 'memory-consolidation', 'retrospective', 'knowledge-management', 'sentry-investigation', 'agent-browser'],
|
|
228
|
-
|
|
229
|
-
header: RESEARCHER_HEADER,
|
|
230
|
-
boundaries: RESEARCHER_BOUNDARIES,
|
|
231
|
-
escalation: RESEARCHER_ESCALATION
|
|
232
|
-
)
|
|
35
|
+
prompt_name: 'researcher'
|
|
233
36
|
},
|
|
234
37
|
|
|
235
38
|
'claw' => {
|
|
236
39
|
name: 'yatfa-openclaw',
|
|
237
40
|
skills: [],
|
|
238
|
-
|
|
239
|
-
header: CLAW_HEADER,
|
|
240
|
-
boundaries: CLAW_BOUNDARIES
|
|
241
|
-
)
|
|
41
|
+
prompt_name: 'claw'
|
|
242
42
|
}
|
|
243
43
|
}.freeze
|
package/package.json
CHANGED
package/setup/setup-agent.rb
CHANGED
|
@@ -315,19 +315,12 @@ def setup_claude_settings!
|
|
|
315
315
|
|
|
316
316
|
# Configure model selection if specified
|
|
317
317
|
# Each model tier can be configured independently
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
settings_config["env"]["ANTHROPIC_DEFAULT_SONNET_MODEL"] = llm_credentials["model_sonnet"]
|
|
325
|
-
puts " Sonnet model: #{llm_credentials['model_sonnet']}"
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
if llm_credentials["model_opus"] && !llm_credentials["model_opus"].empty?
|
|
329
|
-
settings_config["env"]["ANTHROPIC_DEFAULT_OPUS_MODEL"] = llm_credentials["model_opus"]
|
|
330
|
-
puts " Opus model: #{llm_credentials['model_opus']}"
|
|
318
|
+
%w[haiku sonnet opus].each do |tier|
|
|
319
|
+
key = "model_#{tier}"
|
|
320
|
+
if llm_credentials[key] && !llm_credentials[key].empty?
|
|
321
|
+
settings_config["env"]["ANTHROPIC_DEFAULT_#{tier.upcase}_MODEL"] = llm_credentials[key]
|
|
322
|
+
puts " #{tier.capitalize} model: #{llm_credentials[key]}"
|
|
323
|
+
end
|
|
331
324
|
end
|
|
332
325
|
|
|
333
326
|
# Configure context window if specified (value is in k-units, append 'k' suffix)
|
|
@@ -339,7 +332,7 @@ def setup_claude_settings!
|
|
|
339
332
|
end
|
|
340
333
|
end
|
|
341
334
|
|
|
342
|
-
|
|
335
|
+
unless %w[haiku sonnet opus].any? { |tier| llm_credentials["model_#{tier}"] }
|
|
343
336
|
puts " Using default Anthropic models"
|
|
344
337
|
end
|
|
345
338
|
else
|
|
@@ -353,42 +346,22 @@ def setup_claude_settings!
|
|
|
353
346
|
|
|
354
347
|
# Configure hooks
|
|
355
348
|
settings_config["hooks"] ||= {}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
# Configure SessionStart hook for skill knowledge injection
|
|
359
|
-
settings_config["hooks"]["SessionStart"] ||= []
|
|
360
|
-
# Remove old hook if present
|
|
361
|
-
settings_config["hooks"]["SessionStart"].reject! do |h|
|
|
362
|
-
h["hooks"]&.first&.dig("command")&.include?("inject-skill-knowledge.rb")
|
|
363
|
-
end
|
|
364
|
-
|
|
365
|
-
# Install hook script to ~/.claude/hooks
|
|
366
349
|
hooks_dir = File.join(settings_dir, "hooks")
|
|
367
350
|
FileUtils.mkdir_p(hooks_dir)
|
|
368
|
-
|
|
369
|
-
session_script_name = "inject-skill-knowledge.rb"
|
|
370
|
-
dest_path = download_file(
|
|
371
|
-
script_name: session_script_name,
|
|
372
|
-
remote_url: "#{YATFA_RAW_URL}/hooks/#{session_script_name}",
|
|
373
|
-
dest_dir: hooks_dir,
|
|
374
|
-
label: "SessionStart hook"
|
|
375
|
-
)
|
|
376
351
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
352
|
+
# SessionStart hook: skill knowledge injection
|
|
353
|
+
install_hook!(
|
|
354
|
+
settings_config, hooks_dir,
|
|
355
|
+
hook_type: "SessionStart",
|
|
356
|
+
script_name: "inject-skill-knowledge.rb",
|
|
357
|
+
match_substring: "inject-skill-knowledge.rb",
|
|
358
|
+
command_prefix: "ruby"
|
|
359
|
+
)
|
|
385
360
|
|
|
386
361
|
# Install sessionstart-input.sh for repository context injection
|
|
387
|
-
# This provides repository information to Claude at session start
|
|
388
|
-
input_script_name = "sessionstart-input.sh"
|
|
389
362
|
download_file(
|
|
390
|
-
script_name:
|
|
391
|
-
remote_url: "#{YATFA_RAW_URL}/hooks
|
|
363
|
+
script_name: "sessionstart-input.sh",
|
|
364
|
+
remote_url: "#{YATFA_RAW_URL}/hooks/sessionstart-input.sh",
|
|
392
365
|
dest_dir: hooks_dir,
|
|
393
366
|
label: "SessionStart input hook"
|
|
394
367
|
)
|
|
@@ -396,31 +369,15 @@ def setup_claude_settings!
|
|
|
396
369
|
# Add agent-specific settings
|
|
397
370
|
settings_config["skipDangerousModePermissionPrompt"] = true
|
|
398
371
|
|
|
399
|
-
#
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
# Install session-end hook script
|
|
407
|
-
sessionend_script_name = "sessionend-output.sh"
|
|
408
|
-
sessionend_dest_path = download_file(
|
|
409
|
-
script_name: sessionend_script_name,
|
|
410
|
-
remote_url: "#{YATFA_RAW_URL}/hooks/#{sessionend_script_name}",
|
|
411
|
-
dest_dir: hooks_dir,
|
|
412
|
-
label: "Stop hook"
|
|
372
|
+
# Stop hook: session-end learning capture
|
|
373
|
+
install_hook!(
|
|
374
|
+
settings_config, hooks_dir,
|
|
375
|
+
hook_type: "Stop",
|
|
376
|
+
script_name: "sessionend-output.sh",
|
|
377
|
+
match_substring: "sessionend-output.sh",
|
|
378
|
+
command_prefix: "bash"
|
|
413
379
|
)
|
|
414
380
|
|
|
415
|
-
if sessionend_dest_path
|
|
416
|
-
sessionend_hook_to_add = {
|
|
417
|
-
"hooks" => [{ "type" => "command", "command" => "bash \"#{sessionend_dest_path}\"" }]
|
|
418
|
-
}
|
|
419
|
-
settings_config["hooks"]["Stop"] << sessionend_hook_to_add
|
|
420
|
-
else
|
|
421
|
-
puts "⚠️ Skipping Stop hook configuration (download failed)"
|
|
422
|
-
end
|
|
423
|
-
|
|
424
381
|
# Download and register project hooks wrapper
|
|
425
382
|
# The wrapper fetches user-defined hooks from the API at runtime (with caching),
|
|
426
383
|
# executes them, and reports results back for observability.
|
|
@@ -430,6 +387,39 @@ def setup_claude_settings!
|
|
|
430
387
|
puts "✅ ~/.claude/settings.json configured with skill hooks"
|
|
431
388
|
end
|
|
432
389
|
|
|
390
|
+
# Install a Claude settings hook by downloading the script and registering it.
|
|
391
|
+
#
|
|
392
|
+
# Handles the common pattern: ensure hook array → remove old entry → download → append.
|
|
393
|
+
#
|
|
394
|
+
# @param settings_config [Hash] the settings.json hash being built
|
|
395
|
+
# @param hooks_dir [String] directory to download hook scripts into
|
|
396
|
+
# @param hook_type [String] Claude hook type (e.g. "SessionStart", "Stop")
|
|
397
|
+
# @param script_name [String] filename of the hook script to download
|
|
398
|
+
# @param match_substring [String] substring to match when removing old hook entries
|
|
399
|
+
# @param command_prefix [String] command prefix ("ruby" or "bash")
|
|
400
|
+
def install_hook!(settings_config, hooks_dir, hook_type:, script_name:, match_substring:, command_prefix:)
|
|
401
|
+
settings_config["hooks"][hook_type] ||= []
|
|
402
|
+
# Remove old hook entry if present (idempotent re-runs)
|
|
403
|
+
settings_config["hooks"][hook_type].reject! do |h|
|
|
404
|
+
h["hooks"]&.first&.dig("command")&.include?(match_substring)
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
dest_path = download_file(
|
|
408
|
+
script_name: script_name,
|
|
409
|
+
remote_url: "#{YATFA_RAW_URL}/hooks/#{script_name}",
|
|
410
|
+
dest_dir: hooks_dir,
|
|
411
|
+
label: "#{hook_type} hook"
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
if dest_path
|
|
415
|
+
settings_config["hooks"][hook_type] << {
|
|
416
|
+
"hooks" => [{ "type" => "command", "command" => "#{command_prefix} \"#{dest_path}\"" }]
|
|
417
|
+
}
|
|
418
|
+
else
|
|
419
|
+
puts "⚠️ Skipping #{hook_type} hook configuration (download failed)"
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
|
|
433
423
|
# Download and register the project hooks wrapper as a SessionStart hook.
|
|
434
424
|
# The wrapper handles fetching user-defined hooks from the API at runtime,
|
|
435
425
|
# executing them, and reporting results. Hook content is managed by users
|
|
@@ -456,39 +446,57 @@ def setup_system_prompt!
|
|
|
456
446
|
agent_type = ENV["AGENT_TYPE"]
|
|
457
447
|
system_prompt_path = File.expand_path("~/.yatfa/system-prompt.txt")
|
|
458
448
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
puts "✅ Downloaded agents.rb"
|
|
466
|
-
rescue OpenURI::HTTPError => e
|
|
467
|
-
puts "❌ Failed to download agents.rb: #{e.message}"
|
|
468
|
-
puts " Cannot generate system prompt without agent definitions"
|
|
449
|
+
rails_api_url = ENV["YATFA_API_URL"]
|
|
450
|
+
rails_api_key = ENV["YATFA_API_KEY"]
|
|
451
|
+
|
|
452
|
+
unless rails_api_url && !rails_api_url.empty? && rails_api_key && !rails_api_key.empty?
|
|
453
|
+
puts "❌ YATFA_API_URL or YATFA_API_KEY not set"
|
|
454
|
+
puts " Cannot fetch system prompt without API credentials"
|
|
469
455
|
exit 1
|
|
470
456
|
end
|
|
471
457
|
|
|
472
|
-
#
|
|
473
|
-
|
|
458
|
+
# Fetch system prompt from yatfa API
|
|
459
|
+
# Agent prompts are stored in yatfa/prompts/<agent-type>/base.md
|
|
460
|
+
# and loaded into the database by AgentPromptLoader
|
|
461
|
+
prompt_url = URI("#{rails_api_url}/internal/skill?tags%5B%5D=#{agent_type}&tags%5B%5D=base")
|
|
462
|
+
http = Net::HTTP.new(prompt_url.host, prompt_url.port)
|
|
463
|
+
http.use_ssl = prompt_url.scheme == "https"
|
|
464
|
+
http.open_timeout = 10
|
|
465
|
+
http.read_timeout = 15
|
|
466
|
+
|
|
467
|
+
request = Net::HTTP::Get.new(prompt_url)
|
|
468
|
+
request["X-API-Key"] = rails_api_key
|
|
469
|
+
request["Accept"] = "application/json"
|
|
470
|
+
|
|
471
|
+
banner_content = nil
|
|
474
472
|
begin
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
puts "❌ Failed to evaluate agents.rb: #{e.message}"
|
|
478
|
-
exit 1
|
|
479
|
-
end
|
|
473
|
+
puts "📥 Fetching system prompt for #{agent_type}..."
|
|
474
|
+
response = http.request(request)
|
|
480
475
|
|
|
481
|
-
|
|
476
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
477
|
+
data = JSON.parse(response.body)
|
|
478
|
+
banner_content = data["content"]
|
|
479
|
+
|
|
480
|
+
unless banner_content && !banner_content.empty?
|
|
481
|
+
puts "❌ System prompt for '#{agent_type}' is empty"
|
|
482
|
+
exit 1
|
|
483
|
+
end
|
|
482
484
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
485
|
+
puts "✅ Fetched system prompt for #{agent_type}"
|
|
486
|
+
else
|
|
487
|
+
puts "❌ Failed to fetch system prompt: #{response.code} #{response.message}"
|
|
488
|
+
puts " Response: #{response.body}"
|
|
489
|
+
puts " Make sure AgentPromptLoader has loaded prompts/ directory"
|
|
490
|
+
exit 1
|
|
491
|
+
end
|
|
492
|
+
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
|
493
|
+
puts "❌ Timeout fetching system prompt: #{e.message}"
|
|
494
|
+
exit 1
|
|
495
|
+
rescue => e
|
|
496
|
+
puts "❌ Error fetching system prompt: #{e.message}"
|
|
486
497
|
exit 1
|
|
487
498
|
end
|
|
488
499
|
|
|
489
|
-
agent_def = agent_configs[agent_type]
|
|
490
|
-
banner_content = agent_def[:banner].dup
|
|
491
|
-
|
|
492
500
|
# Append repository context from REPOSITORIES_CONTEXT env var
|
|
493
501
|
if ENV["REPOSITORIES_CONTEXT"] && !ENV["REPOSITORIES_CONTEXT"].empty?
|
|
494
502
|
repos = ENV["REPOSITORIES_CONTEXT"].split(',').map do |repo|
|
|
@@ -506,7 +514,7 @@ def setup_system_prompt!
|
|
|
506
514
|
# Ensure directory exists and write system prompt
|
|
507
515
|
FileUtils.mkdir_p(File.dirname(system_prompt_path))
|
|
508
516
|
File.write(system_prompt_path, banner_content)
|
|
509
|
-
puts "✅ System prompt
|
|
517
|
+
puts "✅ System prompt written to #{system_prompt_path} (agent: #{agent_type})"
|
|
510
518
|
|
|
511
519
|
# Add repository context to CLAUDE.md for easy reference
|
|
512
520
|
claude_md = File.expand_path("~/CLAUDE.md")
|
package/setup/setup-github.rb
CHANGED
|
@@ -47,7 +47,7 @@ def setup_github_auth!
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
# Build GitHub token endpoint URL
|
|
50
|
-
token_url = "
|
|
50
|
+
token_url = "\#{rails_api_url.chomp('/')}/github/token"
|
|
51
51
|
|
|
52
52
|
uri = URI(token_url)
|
|
53
53
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
@@ -62,7 +62,7 @@ def setup_github_auth!
|
|
|
62
62
|
response = http.request(request)
|
|
63
63
|
|
|
64
64
|
unless response.is_a?(Net::HTTPSuccess)
|
|
65
|
-
STDERR.puts "❌ Error: Backend API returned
|
|
65
|
+
STDERR.puts "❌ Error: Backend API returned \#{response.code} for GitHub token"
|
|
66
66
|
exit 1
|
|
67
67
|
end
|
|
68
68
|
|