brainiac 0.0.7 → 0.0.9
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 +4 -4
- data/Gemfile.lock +2 -2
- data/bin/brainiac +132 -75
- data/lib/brainiac/agents.rb +12 -36
- data/lib/brainiac/brain.rb +6 -6
- data/lib/brainiac/config.rb +2 -67
- data/lib/brainiac/handlers/discord/config.rb +1 -1
- data/lib/brainiac/handlers/discord/delivery.rb +1 -1
- data/lib/brainiac/handlers/discord/gateway.rb +1 -1
- data/lib/brainiac/handlers/discord/message.rb +13 -25
- data/lib/brainiac/handlers/discord/reactions.rb +1 -1
- data/lib/brainiac/handlers/github.rb +49 -169
- data/lib/brainiac/handlers/shared/git.rb +4 -17
- data/lib/brainiac/handlers/shared/inline_tags.rb +1 -1
- data/lib/brainiac/handlers/zoho.rb +29 -82
- data/lib/brainiac/helpers.rb +43 -336
- data/lib/brainiac/hooks.rb +86 -0
- data/lib/brainiac/plugins.rb +27 -2
- data/lib/brainiac/prompts.rb +34 -172
- data/lib/brainiac/restart.rb +30 -12
- data/lib/brainiac/routes/api.rb +2 -18
- data/lib/brainiac/users.rb +1 -7
- data/lib/brainiac/version.rb +1 -1
- data/lib/brainiac.rb +1 -1
- data/monitor/waybar/deploy_env.rb +3 -3
- data/monitor/xbar/plugin.rb +11 -17
- data/receiver.rb +9 -157
- data/templates/agents.json.example +1 -2
- data/templates/brainiac.json.example +0 -1
- data/templates/users.json.example +0 -3
- metadata +2 -10
- data/lib/brainiac/handlers/fizzy/assignment.rb +0 -125
- data/lib/brainiac/handlers/fizzy/card_index.rb +0 -389
- data/lib/brainiac/handlers/fizzy/comments.rb +0 -730
- data/lib/brainiac/handlers/fizzy/dedup.rb +0 -74
- data/lib/brainiac/handlers/fizzy/deploy.rb +0 -152
- data/lib/brainiac/handlers/fizzy/deployments.rb +0 -260
- data/lib/brainiac/handlers/fizzy.rb +0 -14
- data/lib/brainiac/planning.rb +0 -237
- data/templates/fizzy.json.example +0 -24
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Brainiac hook system.
|
|
4
|
+
#
|
|
5
|
+
# Provides a lightweight pub/sub mechanism for plugins to extend core behavior
|
|
6
|
+
# without core knowing about specific plugins.
|
|
7
|
+
#
|
|
8
|
+
# Core emits events at lifecycle points. Plugins register handlers via Brainiac.on.
|
|
9
|
+
#
|
|
10
|
+
# Events:
|
|
11
|
+
# :agent_completed — After an agent session finishes (success or failure)
|
|
12
|
+
# :pr_merged — After a GitHub PR is merged
|
|
13
|
+
# :pr_opened — After a GitHub PR is opened
|
|
14
|
+
# :pr_reviewed — After a PR review is submitted
|
|
15
|
+
# :build_brain_context — When building brain context (plugins add source-specific queries)
|
|
16
|
+
# :pre_dispatch — Before dispatching an agent (plugins can inject config)
|
|
17
|
+
# :post_comment — After an agent posts a comment/response
|
|
18
|
+
#
|
|
19
|
+
# Usage (in plugin .register):
|
|
20
|
+
# Brainiac.on(:agent_completed) do |ctx|
|
|
21
|
+
# move_card_to_column(ctx[:card_number], "needs_review", ...)
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# Usage (in core):
|
|
25
|
+
# Brainiac.emit(:agent_completed, card_number: 42, agent_name: "Galen", ...)
|
|
26
|
+
|
|
27
|
+
module Brainiac
|
|
28
|
+
@hooks = Hash.new { |h, k| h[k] = [] }
|
|
29
|
+
@channel_prompts = {}
|
|
30
|
+
@channel_pre_post_checks = {}
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
# Register a hook for an event.
|
|
34
|
+
#
|
|
35
|
+
# @param event [Symbol] Event name
|
|
36
|
+
# @param block [Proc] Handler block, receives a context hash
|
|
37
|
+
def on(event, &block)
|
|
38
|
+
@hooks[event] << block
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Emit an event, calling all registered handlers.
|
|
42
|
+
# Returns an array of results from each handler (nil results filtered out).
|
|
43
|
+
#
|
|
44
|
+
# @param event [Symbol] Event name
|
|
45
|
+
# @param context [Hash] Context passed to each handler
|
|
46
|
+
# @return [Array] Results from handlers
|
|
47
|
+
def emit(event, **context)
|
|
48
|
+
results = @hooks[event].map do |handler|
|
|
49
|
+
handler.call(context)
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
LOG.error "[Hooks] Error in #{event} handler: #{e.message}" if defined?(LOG)
|
|
52
|
+
LOG.error "[Hooks] #{e.backtrace.first(3).join("\n ")}" if defined?(LOG)
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
results.compact
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Register a channel prompt template for use in render_prompt.
|
|
59
|
+
# Plugins call this to add their channel-specific prompt block.
|
|
60
|
+
#
|
|
61
|
+
# @param channel [Symbol] Channel name (e.g., :fizzy, :discord)
|
|
62
|
+
# @param prompt [String] Channel prompt text
|
|
63
|
+
# @param pre_post_check [String, nil] Optional pre-post comment check instructions
|
|
64
|
+
def register_channel_prompt(channel, prompt, pre_post_check: nil)
|
|
65
|
+
@channel_prompts[channel] = prompt
|
|
66
|
+
@channel_pre_post_checks[channel] = pre_post_check if pre_post_check
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Get registered channel prompts (used by render_prompt).
|
|
70
|
+
#
|
|
71
|
+
# @return [Hash<Symbol, String>]
|
|
72
|
+
attr_reader :channel_prompts
|
|
73
|
+
|
|
74
|
+
# Get registered pre-post checks (used by render_prompt).
|
|
75
|
+
#
|
|
76
|
+
# @return [Hash<Symbol, String>]
|
|
77
|
+
attr_reader :channel_pre_post_checks
|
|
78
|
+
|
|
79
|
+
# Clear all hooks (useful for testing).
|
|
80
|
+
def reset_hooks!
|
|
81
|
+
@hooks = Hash.new { |h, k| h[k] = [] }
|
|
82
|
+
@channel_prompts = {}
|
|
83
|
+
@channel_pre_post_checks = {}
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
data/lib/brainiac/plugins.rb
CHANGED
|
@@ -32,13 +32,33 @@ def installed_plugins
|
|
|
32
32
|
(PLUGINS_CONFIG["plugins"] || []).map { |p| p.is_a?(Hash) ? p["name"] : p.to_s }
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
# Returns the full plugin entry (Hash) for a given name, or nil.
|
|
36
|
+
def plugin_entry(name)
|
|
37
|
+
(PLUGINS_CONFIG["plugins"] || []).find { |p| (p.is_a?(Hash) ? p["name"] : p.to_s) == name }
|
|
38
|
+
end
|
|
39
|
+
|
|
35
40
|
# Load all installed plugin gems and call their register hooks.
|
|
36
41
|
# Called once during server startup, after core handlers are loaded.
|
|
37
42
|
def load_plugins!(app)
|
|
43
|
+
# rubocop:disable Metrics/BlockLength
|
|
38
44
|
installed_plugins.each do |name|
|
|
39
45
|
gem_name = "brainiac-#{name}"
|
|
46
|
+
entry = plugin_entry(name)
|
|
40
47
|
begin
|
|
41
|
-
|
|
48
|
+
# If plugin has a local path, add its lib/ to load path before requiring
|
|
49
|
+
if entry.is_a?(Hash) && entry["path"]
|
|
50
|
+
lib_path = File.join(entry["path"], "lib")
|
|
51
|
+
$LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
|
|
52
|
+
LOG.info "[Plugins] Loading #{gem_name} from local path: #{entry["path"]}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Try both naming conventions: brainiac-fizzy and brainiac_fizzy
|
|
56
|
+
begin
|
|
57
|
+
require gem_name
|
|
58
|
+
rescue LoadError
|
|
59
|
+
require gem_name.tr("-", "_")
|
|
60
|
+
end
|
|
61
|
+
|
|
42
62
|
plugin_module = resolve_plugin_module(name)
|
|
43
63
|
if plugin_module.respond_to?(:register)
|
|
44
64
|
plugin_module.register(app)
|
|
@@ -48,12 +68,17 @@ def load_plugins!(app)
|
|
|
48
68
|
end
|
|
49
69
|
rescue LoadError => e
|
|
50
70
|
LOG.error "[Plugins] Could not load #{gem_name}: #{e.message}"
|
|
51
|
-
|
|
71
|
+
if entry.is_a?(Hash) && entry["path"]
|
|
72
|
+
LOG.error "[Plugins] Local path: #{entry["path"]}"
|
|
73
|
+
else
|
|
74
|
+
LOG.error "[Plugins] Is the gem installed? Run: gem install #{gem_name}"
|
|
75
|
+
end
|
|
52
76
|
rescue StandardError => e
|
|
53
77
|
LOG.error "[Plugins] Error registering #{gem_name}: #{e.message}"
|
|
54
78
|
LOG.error "[Plugins] #{e.backtrace.first(3).join("\n ")}"
|
|
55
79
|
end
|
|
56
80
|
end
|
|
81
|
+
# rubocop:enable Metrics/BlockLength
|
|
57
82
|
end
|
|
58
83
|
|
|
59
84
|
# Resolve the plugin module for a given name.
|
data/lib/brainiac/prompts.rb
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
#
|
|
5
5
|
# Prompts are layered:
|
|
6
6
|
# PROMPT_CORE — universal (identity, memory, brain, reflection)
|
|
7
|
-
# PROMPT_FIZZY_CHANNEL — Fizzy-specific rules (HTML formatting, reactions, screenshots)
|
|
8
7
|
# PROMPT_DISCORD_CHANNEL — Discord-specific rules (markdown, response file, char limits)
|
|
9
8
|
# PROMPT_GITHUB_CHANNEL — GitHub-specific rules (GFM, PR conventions)
|
|
10
9
|
#
|
|
@@ -26,7 +25,7 @@ PROMPT_CORE = <<~PROMPT
|
|
|
26
25
|
**At the very start of every session:**
|
|
27
26
|
1. Read `{{MEMORY_DIR}}/card-{{CARD_ID}}.md`. If it contains content, it has context from your previous sessions. If the file is empty (first session on this card), just proceed without prior context.
|
|
28
27
|
|
|
29
|
-
**Note:** Only the last 15 comments are included in card context (truncated to 500 chars each). Your memory file is the authoritative record of prior discussions.
|
|
28
|
+
**Note:** Only the last 15 comments are included in card context (truncated to 500 chars each). Your memory file is the authoritative record of prior discussions.
|
|
30
29
|
|
|
31
30
|
**Before you finish every session (even if you didn't complete the task):**
|
|
32
31
|
2. Update your memory file at `{{MEMORY_DIR}}/card-{{CARD_ID}}.md`.
|
|
@@ -43,11 +42,11 @@ PROMPT_CORE = <<~PROMPT
|
|
|
43
42
|
Relevant knowledge is automatically retrieved and included above in this prompt when available.
|
|
44
43
|
You can also search manually: `qmd search "<query>" -c brainiac-knowledge`
|
|
45
44
|
|
|
46
|
-
**MANDATORY: Before running any non-standard CLI tool (
|
|
45
|
+
**MANDATORY: Before running any non-standard CLI tool (qmd, gh, project scripts) you haven't used in this session, search the brain first:**
|
|
47
46
|
```
|
|
48
47
|
qmd search "<tool-name>" -c brainiac-knowledge
|
|
49
48
|
```
|
|
50
|
-
Examples: `qmd search "
|
|
49
|
+
Examples: `qmd search "qmd" -c brainiac-knowledge`, `qmd search "gh" -c brainiac-knowledge`
|
|
51
50
|
|
|
52
51
|
Standard unix commands (cd, ls, grep, cat, git, curl, etc.) don't need a brain search.
|
|
53
52
|
But for project-specific tools, do NOT guess at flags or syntax — wrong commands waste time and tokens. Look it up first.
|
|
@@ -63,7 +62,7 @@ PROMPT_CORE = <<~PROMPT
|
|
|
63
62
|
|
|
64
63
|
### Persona (`{{PERSONA_DIR}}/`) — unique to you
|
|
65
64
|
Communication style, tone, personality, how to interact with specific people.
|
|
66
|
-
**This is for all external communication, such as writing comments on
|
|
65
|
+
**This is for all external communication, such as writing comments on cards, Discord chat, and GitHub PRs.**
|
|
67
66
|
|
|
68
67
|
Do NOT manually read persona files during coding/debugging — the auto-retrieved persona
|
|
69
68
|
above already shapes your communication style. Focus on implementation during work phases,
|
|
@@ -119,7 +118,7 @@ PROMPT_CORE = <<~PROMPT
|
|
|
119
118
|
- To discover project locations for cross-repo work, run: `brainiac list`
|
|
120
119
|
|
|
121
120
|
**Limitations:** Subagents don't get your brain context, persona, or memory.
|
|
122
|
-
They can read files and run commands, but cannot post to
|
|
121
|
+
They can read files and run commands, but cannot post to Discord, GitHub, or other channels.
|
|
123
122
|
They're excellent researchers — use them as such.
|
|
124
123
|
|
|
125
124
|
## Image Reading Limits
|
|
@@ -131,36 +130,8 @@ PROMPT
|
|
|
131
130
|
# ---------------------------------------------------------------------------
|
|
132
131
|
# PROMPT_PRE_POST_CHECK — inserted before PROMPT_REFLECTION so the agent
|
|
133
132
|
# re-checks for new comments/messages before posting its response.
|
|
134
|
-
# Channel-specific:
|
|
133
|
+
# Channel-specific: plugins register pre-post checks, Discord skips.
|
|
135
134
|
# ---------------------------------------------------------------------------
|
|
136
|
-
PROMPT_PRE_POST_CHECK_FIZZY = <<~PROMPT
|
|
137
|
-
## Pre-Post Comment Check (MANDATORY — do this BEFORE posting your comment)
|
|
138
|
-
|
|
139
|
-
Your session may have been running for a while. Before you post your final comment,
|
|
140
|
-
re-fetch the card to see if anything changed while you were working:
|
|
141
|
-
|
|
142
|
-
```bash
|
|
143
|
-
fizzy card show {{CARD_NUMBER}}
|
|
144
|
-
fizzy comment list --card {{CARD_NUMBER}}
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
Compare what you see now against the card context that was provided at the start
|
|
148
|
-
of your session. Check for:
|
|
149
|
-
|
|
150
|
-
**Card body changes:** If the card description was edited (new acceptance criteria,
|
|
151
|
-
clarified scope, updated requirements), adjust your work to match before posting.
|
|
152
|
-
|
|
153
|
-
**New comments:** If there are new comments that weren't in your original context:
|
|
154
|
-
1. **Read them carefully** — a human may have added context, changed requirements, or asked you to adjust something
|
|
155
|
-
2. **Decide how to respond:**
|
|
156
|
-
- If the new comment changes what you should build or how → adjust your work before posting
|
|
157
|
-
- If the new comment adds context that affects your response → incorporate it into your comment
|
|
158
|
-
- If the new comment is unrelated or just acknowledgment → proceed as planned, but mention you saw it
|
|
159
|
-
3. **Do NOT ignore new comments** — the whole point is to avoid posting a response that's already outdated
|
|
160
|
-
|
|
161
|
-
If nothing changed, proceed normally.
|
|
162
|
-
|
|
163
|
-
PROMPT
|
|
164
135
|
|
|
165
136
|
PROMPT_PRE_POST_CHECK_GITHUB = <<~PROMPT
|
|
166
137
|
## Pre-Post Comment Check (MANDATORY — do this BEFORE posting your comment)
|
|
@@ -215,46 +186,6 @@ PROMPT_REFLECTION = <<~PROMPT
|
|
|
215
186
|
|
|
216
187
|
PROMPT
|
|
217
188
|
|
|
218
|
-
# ---------------------------------------------------------------------------
|
|
219
|
-
# PROMPT_FIZZY_CHANNEL — Fizzy-specific rules, prepended to Fizzy templates
|
|
220
|
-
# ---------------------------------------------------------------------------
|
|
221
|
-
PROMPT_FIZZY_CHANNEL = <<~PROMPT
|
|
222
|
-
## Fizzy Channel Rules
|
|
223
|
-
|
|
224
|
-
### Standard Procedure
|
|
225
|
-
- If you have questions, ask them in the card's comments.
|
|
226
|
-
- Only assign a fizzy card if it is currently unassigned and you are requested to work on it. Otherwise leave it, it will be managed by the users.
|
|
227
|
-
|
|
228
|
-
### Column Transitions
|
|
229
|
-
Brainiac handles column moves automatically — do NOT move cards between columns yourself.
|
|
230
|
-
Cards move to "Right Now" when you're dispatched and to "Needs Review" when your session ends.
|
|
231
|
-
|
|
232
|
-
### Formatting
|
|
233
|
-
**Fizzy comments use HTML, NOT Markdown.** Use `<h2>`/`<h3>` for sections, `<p>` for paragraphs, `<ul><li>` for lists, `<pre data-language="ruby">` for code blocks, `<strong>` for emphasis. Never use markdown syntax (`**bold**`, `- list`, `## heading`) in Fizzy comments — it renders as raw text.
|
|
234
|
-
|
|
235
|
-
### Screenshots (MANDATORY for UI changes)
|
|
236
|
-
If you touched any `.js`, `.jsx`, `.css`, or `.html` in a web app directory and `./scripts/screenshot-page.sh` exists in the project, screenshot every affected page. Search the brain for "screenshot" if you need the full workflow.
|
|
237
|
-
|
|
238
|
-
**Before uploading, review your own screenshot:**
|
|
239
|
-
1. Read the screenshot image file
|
|
240
|
-
2. Check for: blank/white pages, obvious rendering errors, missing content, broken layouts, error messages, or anything that doesn't match what you expected
|
|
241
|
-
3. If the screenshot looks wrong, fix the underlying issue and retake (max 2 retries)
|
|
242
|
-
4. After 2 retries, upload whatever you have and note the display issue in your comment so the human knows it needs attention
|
|
243
|
-
|
|
244
|
-
Upload screenshots and embed them in your comment using `<action-text-attachment>`.
|
|
245
|
-
|
|
246
|
-
### Card Memory Discipline (CRITICAL for long-running cards)
|
|
247
|
-
Cards evolve — scope expands, requirements shift, new acceptance criteria appear mid-work.
|
|
248
|
-
When writing your memory file for a Fizzy card session, you MUST include:
|
|
249
|
-
- The original card scope/requirements (from the card body at time of assignment)
|
|
250
|
-
- Any scope changes from comments (e.g. "also handle X while you're in there")
|
|
251
|
-
- Any card body edits you detected during pre-post check
|
|
252
|
-
- The current scope/focus as of this session
|
|
253
|
-
This is the ONLY way future sessions will know the full picture when the card body has changed
|
|
254
|
-
or key decisions were made in comments that fell outside the pre-fetched window.
|
|
255
|
-
|
|
256
|
-
PROMPT
|
|
257
|
-
|
|
258
189
|
# ---------------------------------------------------------------------------
|
|
259
190
|
# PROMPT_DISCORD_CHANNEL — Discord-specific rules, prepended to Discord templates
|
|
260
191
|
# ---------------------------------------------------------------------------
|
|
@@ -284,7 +215,7 @@ PROMPT_DISCORD_CHANNEL = <<~PROMPT
|
|
|
284
215
|
per message, though long responses will be split automatically.
|
|
285
216
|
|
|
286
217
|
### Scope
|
|
287
|
-
This is a conversational interaction — no
|
|
218
|
+
This is a conversational interaction — no card, no PR. You're here to answer questions,
|
|
288
219
|
discuss code, share knowledge, or help with whatever the user needs.
|
|
289
220
|
|
|
290
221
|
**Detect user intent:**
|
|
@@ -351,81 +282,8 @@ PROMPT_GITHUB_CHANNEL = <<~PROMPT
|
|
|
351
282
|
|
|
352
283
|
### Scope
|
|
353
284
|
You are responding to activity on a GitHub PR. Focus on the code changes and review feedback.
|
|
354
|
-
When posting comments, post on the PR unless specifically asked to update the
|
|
355
|
-
|
|
356
|
-
PROMPT
|
|
285
|
+
When posting comments, post on the PR unless specifically asked to update the card.
|
|
357
286
|
|
|
358
|
-
# ---------------------------------------------------------------------------
|
|
359
|
-
# Situation templates — the specific "what happened" for each trigger type
|
|
360
|
-
# ---------------------------------------------------------------------------
|
|
361
|
-
|
|
362
|
-
PROMPT_CARD_ASSIGNED = <<~'PROMPT'
|
|
363
|
-
You have been assigned Fizzy card #{{CARD_NUMBER}}: "{{CARD_TITLE}}".
|
|
364
|
-
You are on branch "{{BRANCH}}" in a fresh worktree.
|
|
365
|
-
Implement the task, commit, push, and open a PR (link back to Fizzy).
|
|
366
|
-
When you're done, post a comment on the card with a concise summary, PR link, and branch name.
|
|
367
|
-
|
|
368
|
-
**MANDATORY: Always include the branch name in your comment.** Use this format:
|
|
369
|
-
`<p><strong>Branch:</strong> <code>{{BRANCH}}</code></p>`
|
|
370
|
-
PROMPT
|
|
371
|
-
|
|
372
|
-
PROMPT_FOLLOWUP_WORKTREE = <<~'PROMPT'
|
|
373
|
-
There's a new comment on Fizzy card #{{CARD_NUMBER}} that you've already started working on.
|
|
374
|
-
You are in the existing worktree for this card.
|
|
375
|
-
|
|
376
|
-
The comment that triggered this session is from {{COMMENT_CREATOR}} (comment ID: {{COMMENT_ID}}):
|
|
377
|
-
"""
|
|
378
|
-
{{COMMENT_BODY}}
|
|
379
|
-
"""
|
|
380
|
-
|
|
381
|
-
The card and its full comment history are provided above. Focus your response on the comment above.
|
|
382
|
-
If you've already addressed this exact request in a previous session (check your memory file), reply confirming it's done — do NOT redo it.
|
|
383
|
-
Otherwise, make the requested changes, commit, push, and update the PR.
|
|
384
|
-
PROMPT
|
|
385
|
-
|
|
386
|
-
PROMPT_FOLLOWUP_NO_WORKTREE = <<~PROMPT
|
|
387
|
-
There's a new comment on a Fizzy card (internal_id: "{{CARD_INTERNAL_ID}}") that you've been involved with.
|
|
388
|
-
|
|
389
|
-
The comment that triggered this session is from {{COMMENT_CREATOR}} (comment ID: {{COMMENT_ID}}):
|
|
390
|
-
"""
|
|
391
|
-
{{COMMENT_BODY}}
|
|
392
|
-
"""
|
|
393
|
-
|
|
394
|
-
The card and its full comment history are provided above. Focus your response on the comment above.
|
|
395
|
-
If you've already addressed this exact request in a previous session (check your memory file), reply confirming it's done — do NOT redo it.
|
|
396
|
-
Otherwise, respond accordingly — that could include doing work on a new or existing branch.
|
|
397
|
-
PROMPT
|
|
398
|
-
|
|
399
|
-
PROMPT_MENTION = <<~PROMPT
|
|
400
|
-
You were mentioned in a comment on a Fizzy card with internal_id "{{CARD_INTERNAL_ID}}"{{CARD_NUMBER_TEXT}}.
|
|
401
|
-
You are on branch "{{BRANCH}}" in a dedicated worktree for exploration and investigation.
|
|
402
|
-
|
|
403
|
-
Find the card and respond accordingly. You can:
|
|
404
|
-
- Investigate the codebase and provide your thoughts
|
|
405
|
-
- Make exploratory changes or create test files (they won't pollute the main branch)
|
|
406
|
-
- Create a PR if your exploration leads to a concrete solution
|
|
407
|
-
PROMPT
|
|
408
|
-
|
|
409
|
-
PROMPT_CROSS_AGENT_REVIEW = <<~'PROMPT'
|
|
410
|
-
You were tagged in a comment on Fizzy card #{{CARD_NUMBER}} (internal_id: "{{CARD_INTERNAL_ID}}").
|
|
411
|
-
This card is being worked on by {{CARD_AGENT}} — you're being brought in for your perspective.
|
|
412
|
-
|
|
413
|
-
The comment that tagged you is from {{COMMENT_CREATOR}} (comment ID: {{COMMENT_ID}}):
|
|
414
|
-
"""
|
|
415
|
-
{{COMMENT_BODY}}
|
|
416
|
-
"""
|
|
417
|
-
|
|
418
|
-
The card and its full comment history are provided above. Also check any linked PR to understand the current state.
|
|
419
|
-
Then respond to what's being asked of you — that might be a code review, an opinion on
|
|
420
|
-
an approach, debugging help, or just a sanity check.
|
|
421
|
-
|
|
422
|
-
You are in your own worktree at `{{WORKTREE_PATH}}` on branch `{{BRANCH}}`.
|
|
423
|
-
This is separate from {{CARD_AGENT}}'s worktree — you can read code, make changes, and
|
|
424
|
-
commit without affecting their work or the main repo.
|
|
425
|
-
|
|
426
|
-
**IMPORTANT: Do NOT @mention any other agents in your response.** You were brought in for
|
|
427
|
-
a one-shot review. If you think another agent should be involved, say so in plain text
|
|
428
|
-
but do NOT use @Agent syntax — tagging agents creates automated dispatches.
|
|
429
287
|
PROMPT
|
|
430
288
|
|
|
431
289
|
PROMPT_DISCORD = <<~'PROMPT'
|
|
@@ -447,7 +305,7 @@ PROMPT_DISCORD = <<~'PROMPT'
|
|
|
447
305
|
PROMPT
|
|
448
306
|
|
|
449
307
|
PROMPT_GITHUB_PR_COMMENT = <<~'PROMPT'
|
|
450
|
-
There's a new comment from @{{COMMENT_CREATOR}} on your PR #{{PR_NUMBER}} for
|
|
308
|
+
There's a new comment from @{{COMMENT_CREATOR}} on your PR #{{PR_NUMBER}} for card #{{CARD_NUMBER}}.
|
|
451
309
|
|
|
452
310
|
Comment:
|
|
453
311
|
{{COMMENT_BODY}}
|
|
@@ -462,7 +320,7 @@ PROMPT_GITHUB_PR_COMMENT = <<~'PROMPT'
|
|
|
462
320
|
PROMPT
|
|
463
321
|
|
|
464
322
|
PROMPT_GITHUB_PR_REVIEW = <<~'PROMPT'
|
|
465
|
-
A code review has been submitted on your PR #{{PR_NUMBER}} for
|
|
323
|
+
A code review has been submitted on your PR #{{PR_NUMBER}} for card #{{CARD_NUMBER}}.
|
|
466
324
|
|
|
467
325
|
{{REVIEW_CONTEXT}}
|
|
468
326
|
|
|
@@ -480,11 +338,11 @@ PROMPT
|
|
|
480
338
|
# Channel constant mapping for render_prompt
|
|
481
339
|
# ---------------------------------------------------------------------------
|
|
482
340
|
PROMPT_GITHUB_UAT = <<~'PROMPT'
|
|
483
|
-
PR #{{PR_NUMBER}} has been merged into main for
|
|
341
|
+
PR #{{PR_NUMBER}} has been merged into main for card #{{CARD_NUMBER}}: "{{CARD_TITLE}}"
|
|
484
342
|
|
|
485
343
|
The card has been moved to the UAT column. The changes are now deployed to the UAT environment.
|
|
486
344
|
|
|
487
|
-
Your job: post a comment on
|
|
345
|
+
Your job: post a comment on card #{{CARD_NUMBER}} with clear, specific steps for how to manually test this feature in UAT. Include:
|
|
488
346
|
1. What URL(s) or screen(s) to visit
|
|
489
347
|
2. Step-by-step actions to verify the feature works
|
|
490
348
|
3. What the expected behavior should be
|
|
@@ -497,7 +355,6 @@ PROMPT_GITHUB_UAT = <<~'PROMPT'
|
|
|
497
355
|
PROMPT
|
|
498
356
|
|
|
499
357
|
CHANNEL_PROMPTS = {
|
|
500
|
-
fizzy: PROMPT_FIZZY_CHANNEL,
|
|
501
358
|
discord: PROMPT_DISCORD_CHANNEL,
|
|
502
359
|
github: PROMPT_GITHUB_CHANNEL
|
|
503
360
|
}.freeze
|
|
@@ -505,30 +362,32 @@ CHANNEL_PROMPTS = {
|
|
|
505
362
|
# ---------------------------------------------------------------------------
|
|
506
363
|
# render_prompt — composes PROMPT_CORE + channel rules + situation template
|
|
507
364
|
#
|
|
508
|
-
# channel: :
|
|
365
|
+
# channel: :discord, :github, or plugin-registered channels (e.g., :fizzy, :linear)
|
|
509
366
|
# ---------------------------------------------------------------------------
|
|
510
|
-
DEFAULT_COLUMN_IDS = {
|
|
511
|
-
"right_now" => "03f5xa5q9fog9592pa1279dts",
|
|
512
|
-
"needs_review" => "03f5ykobhpsd78hbuvajtn8g8",
|
|
513
|
-
"uat" => "03fsmglsr6az06ppyotawsti8"
|
|
514
|
-
}.freeze
|
|
515
367
|
|
|
516
|
-
|
|
368
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
369
|
+
def render_prompt(template, vars = {}, brain_context: "", card_context: "", agent_name: AI_AGENT_NAME, channel: :discord, board_key: nil)
|
|
517
370
|
result = ""
|
|
518
371
|
result += "#{brain_context}\n" unless brain_context.empty?
|
|
519
372
|
result += card_context unless card_context.empty?
|
|
520
373
|
result += PROMPT_CORE
|
|
521
|
-
|
|
374
|
+
|
|
375
|
+
# Channel prompt: check plugin-registered prompts first, then built-in
|
|
376
|
+
plugin_prompt = Brainiac.channel_prompts[channel]
|
|
377
|
+
result += plugin_prompt || CHANNEL_PROMPTS.fetch(channel, PROMPT_DISCORD_CHANNEL)
|
|
378
|
+
|
|
522
379
|
result += template
|
|
523
380
|
|
|
524
|
-
# Pre-post comment check:
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
381
|
+
# Pre-post comment check: plugin-registered or built-in
|
|
382
|
+
plugin_pre_post = Brainiac.channel_pre_post_checks[channel]
|
|
383
|
+
if plugin_pre_post
|
|
384
|
+
result += plugin_pre_post
|
|
385
|
+
elsif channel == :github
|
|
386
|
+
result += PROMPT_PRE_POST_CHECK_GITHUB
|
|
529
387
|
end
|
|
530
388
|
|
|
531
|
-
|
|
389
|
+
# Reflection prompt — skip for Discord (causes crashes in post-task phase)
|
|
390
|
+
result += PROMPT_REFLECTION unless channel == :discord
|
|
532
391
|
|
|
533
392
|
vars["KNOWLEDGE_DIR"] ||= KNOWLEDGE_DIR
|
|
534
393
|
vars["MEMORY_DIR"] ||= memory_dir_for(agent_name)
|
|
@@ -537,9 +396,11 @@ def render_prompt(template, vars = {}, brain_context: "", card_context: "", agen
|
|
|
537
396
|
vars["AGENT_NAME"] ||= agent_name
|
|
538
397
|
|
|
539
398
|
# Populate column IDs from board config, falling back to defaults
|
|
540
|
-
DEFAULT_COLUMN_IDS
|
|
541
|
-
|
|
542
|
-
|
|
399
|
+
if defined?(DEFAULT_COLUMN_IDS)
|
|
400
|
+
DEFAULT_COLUMN_IDS.each do |col_name, default_id|
|
|
401
|
+
var_name = "#{col_name.upcase}_COLUMN_ID"
|
|
402
|
+
vars[var_name] ||= (board_key && board_column_id(board_key, col_name)) || default_id
|
|
403
|
+
end
|
|
543
404
|
end
|
|
544
405
|
|
|
545
406
|
# Touch memory file if CARD_ID is present — ensures file exists before agent tries to read it
|
|
@@ -556,6 +417,7 @@ def render_prompt(template, vars = {}, brain_context: "", card_context: "", agen
|
|
|
556
417
|
vars.each { |key, val| result.gsub!("{{#{key}}}", val.to_s) }
|
|
557
418
|
result
|
|
558
419
|
end
|
|
420
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
559
421
|
|
|
560
422
|
# Lean prompt for resumed sessions. The previous session already has the full context
|
|
561
423
|
# (role, persona, knowledge, core instructions, channel prompts). We only send the new
|
data/lib/brainiac/restart.rb
CHANGED
|
@@ -57,22 +57,40 @@ def execute_restart
|
|
|
57
57
|
|
|
58
58
|
Thread.new do
|
|
59
59
|
sleep 1
|
|
60
|
-
source_dir = File.expand_path("
|
|
60
|
+
source_dir = defined?(SERVER_ROOT) ? SERVER_ROOT : File.expand_path("../..", __dir__)
|
|
61
61
|
receiver_path = File.join(source_dir, "receiver.rb")
|
|
62
|
-
log_file = File.join(source_dir, "tmp", "brainiac-server.log")
|
|
63
|
-
FileUtils.mkdir_p(File.dirname(log_file))
|
|
64
62
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
# Determine if we're running in foreground mode.
|
|
64
|
+
# If stdin is a TTY or we weren't launched as a daemon, use exec to replace the process.
|
|
65
|
+
# This keeps the server in the foreground terminal.
|
|
66
|
+
foreground = $stdout.tty? || !File.exist?(File.join(BRAINIAC_DIR, "server.pid")) ||
|
|
67
|
+
File.read(File.join(BRAINIAC_DIR, "server.pid")).strip.to_i == Process.pid
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
if foreground
|
|
70
|
+
LOG.info "[Brainiac] Restarting in foreground mode (exec)..."
|
|
71
|
+
# Write PID file for the new process (same PID after exec)
|
|
72
|
+
File.write(File.join(BRAINIAC_DIR, "server.pid"), Process.pid.to_s)
|
|
70
73
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
# exec replaces this process — the terminal stays attached
|
|
75
|
+
Dir.chdir(source_dir)
|
|
76
|
+
exec("ruby", receiver_path)
|
|
77
|
+
else
|
|
78
|
+
# Daemon mode — spawn a new background process
|
|
79
|
+
log_file = File.join(source_dir, "tmp", "brainiac-server.log")
|
|
80
|
+
FileUtils.mkdir_p(File.dirname(log_file))
|
|
81
|
+
|
|
82
|
+
pid = spawn({ "PATH" => ENV.fetch("PATH", nil) }, "ruby", receiver_path,
|
|
83
|
+
chdir: source_dir, out: [log_file, "a"], err: %i[child out])
|
|
84
|
+
Process.detach(pid)
|
|
85
|
+
|
|
86
|
+
File.write(File.join(BRAINIAC_DIR, "server.pid"), pid.to_s)
|
|
87
|
+
|
|
88
|
+
LOG.info "[Brainiac] Stopping server, new instance started (PID: #{pid}) from #{source_dir}"
|
|
89
|
+
sleep 0.5
|
|
90
|
+
Sinatra::Application.quit!
|
|
91
|
+
sleep 0.5
|
|
92
|
+
exit!
|
|
93
|
+
end
|
|
76
94
|
end
|
|
77
95
|
end
|
|
78
96
|
|
data/lib/brainiac/routes/api.rb
CHANGED
|
@@ -39,7 +39,7 @@ def resolve_session_agent_name(card_key, info)
|
|
|
39
39
|
else
|
|
40
40
|
"Unknown"
|
|
41
41
|
end
|
|
42
|
-
|
|
42
|
+
agent_display_name(agent_key)
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def format_recent_sessions
|
|
@@ -109,7 +109,6 @@ post "/api/reload" do
|
|
|
109
109
|
reload_agent_registry!(force: true)
|
|
110
110
|
reload_user_registry!(force: true)
|
|
111
111
|
reload_github_config!(force: true)
|
|
112
|
-
reload_deployments_config!(force: true) if defined?(DEPLOYMENTS_CONFIG)
|
|
113
112
|
ReloadHooks.run_all!
|
|
114
113
|
{ status: "reloaded", projects: PROJECTS.keys, agents: all_agent_names.to_a, registry: AGENT_REGISTRY.keys,
|
|
115
114
|
users: USER_REGISTRY["users"].size }.to_json
|
|
@@ -128,7 +127,7 @@ get "/api/roles" do
|
|
|
128
127
|
if Dir.exist?(ROLES_DIR)
|
|
129
128
|
Dir.glob(File.join(ROLES_DIR, "*.md")).each do |f|
|
|
130
129
|
name = File.basename(f, ".md")
|
|
131
|
-
agents = AGENT_REGISTRY.select { |_, e| e.is_a?(Hash) && Array(e["role"]).include?(name) }.map { |k,
|
|
130
|
+
agents = AGENT_REGISTRY.select { |_, e| e.is_a?(Hash) && Array(e["role"]).include?(name) }.map { |k, _e| agent_display_name(k) }
|
|
132
131
|
roles << { name: name, agents: agents }
|
|
133
132
|
end
|
|
134
133
|
end
|
|
@@ -210,21 +209,6 @@ post "/api/skills/curate" do
|
|
|
210
209
|
result.to_json
|
|
211
210
|
end
|
|
212
211
|
|
|
213
|
-
# --- Card Index ---
|
|
214
|
-
|
|
215
|
-
get "/api/card-index" do
|
|
216
|
-
content_type :json
|
|
217
|
-
halt 404, { error: "Card index not enabled (fizzy handler disabled)" }.to_json unless defined?(CARD_INDEX)
|
|
218
|
-
|
|
219
|
-
query = params["q"]
|
|
220
|
-
if query && !query.empty?
|
|
221
|
-
similar = CARD_INDEX.find_similar_cards(query)
|
|
222
|
-
{ query: query, matches: similar, total_indexed: CARD_INDEX.size }.to_json
|
|
223
|
-
else
|
|
224
|
-
{ total: CARD_INDEX.size, cards: CARD_INDEX }.to_json
|
|
225
|
-
end
|
|
226
|
-
end
|
|
227
|
-
|
|
228
212
|
# --- Dispatch Depth ---
|
|
229
213
|
|
|
230
214
|
get "/api/dispatch-depth" do
|
data/lib/brainiac/users.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# User identity registry - resolves identities across platforms
|
|
4
|
-
# (Discord, GitHub
|
|
4
|
+
# (Discord, GitHub)
|
|
5
5
|
|
|
6
6
|
USERS_FILE = File.join(BRAINIAC_DIR, "users.json")
|
|
7
7
|
|
|
@@ -40,11 +40,6 @@ def find_user_by_github_username(username)
|
|
|
40
40
|
USER_REGISTRY["users"].find { |u| u.dig("identities", "github", "username") == username.to_s }
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
# Find user by Fizzy username
|
|
44
|
-
def find_user_by_fizzy_username(username)
|
|
45
|
-
USER_REGISTRY["users"].find { |u| u.dig("identities", "fizzy", "username") == username.to_s }
|
|
46
|
-
end
|
|
47
|
-
|
|
48
43
|
# Find user by canonical name
|
|
49
44
|
def find_user_by_canonical_name(name)
|
|
50
45
|
USER_REGISTRY["users"].find { |u| u["canonical_name"].downcase == name.downcase }
|
|
@@ -55,7 +50,6 @@ def find_user(identifier)
|
|
|
55
50
|
find_user_by_discord_id(identifier) ||
|
|
56
51
|
find_user_by_discord_username(identifier) ||
|
|
57
52
|
find_user_by_github_username(identifier) ||
|
|
58
|
-
find_user_by_fizzy_username(identifier) ||
|
|
59
53
|
find_user_by_canonical_name(identifier)
|
|
60
54
|
end
|
|
61
55
|
|
data/lib/brainiac/version.rb
CHANGED
data/lib/brainiac.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require_relative "brainiac/hooks"
|
|
1
2
|
require_relative "brainiac/config"
|
|
2
3
|
require_relative "brainiac/users"
|
|
3
4
|
require_relative "brainiac/agents"
|
|
@@ -5,7 +6,6 @@ require_relative "brainiac/brain"
|
|
|
5
6
|
require_relative "brainiac/skills"
|
|
6
7
|
require_relative "brainiac/sessions"
|
|
7
8
|
require_relative "brainiac/prompts"
|
|
8
|
-
require_relative "brainiac/planning"
|
|
9
9
|
require_relative "brainiac/helpers"
|
|
10
10
|
require_relative "brainiac/cron"
|
|
11
11
|
require_relative "brainiac/plugins"
|
|
@@ -48,9 +48,9 @@ def handle_deploy(env_key, deployment)
|
|
|
48
48
|
return unless deployment
|
|
49
49
|
|
|
50
50
|
prefill = deployment["status"] == "occupied" && deployment["card_number"] ? deployment["card_number"].to_s : ""
|
|
51
|
-
card_number = `timeout 60 zenity --entry --title="Deploy to #{env_key}" --text="
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
card_number = `timeout 60 zenity --entry --title="Deploy to #{env_key}" --text="Card number:"#{unless prefill.empty?
|
|
52
|
+
" --entry-text=#{Shellwords.escape(prefill)}"
|
|
53
|
+
end} 2>/dev/null`.strip
|
|
54
54
|
return if card_number.empty?
|
|
55
55
|
|
|
56
56
|
worktree = resolve_worktree(card_number)
|