brainiac-fizzy 0.0.9 → 0.0.11
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/lib/brainiac/plugins/fizzy/delegators.rb +53 -0
- data/lib/brainiac/plugins/fizzy/handlers/assignment.rb +14 -2
- data/lib/brainiac/plugins/fizzy/handlers/comments.rb +54 -20
- data/lib/brainiac/plugins/fizzy/prompts.rb +89 -0
- data/lib/brainiac/plugins/fizzy/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 50d4aa7e239474b5b5046d0d0e98d4b5d1d5aa7faf5c8ff3fe4a69207fa477d2
|
|
4
|
+
data.tar.gz: a622fb92c3fef1d669ea2f910968c2295929a95bb32319541513a47ccdf7addc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4a60a03934e9dd915781e244fb2c8b7d964c4be448d5be686a6f15ada36a6310768f9714c5968cf6dfd27328dc4c409aac59d3b154a32a661f1c40e48aa5373c
|
|
7
|
+
data.tar.gz: 965e79fa73936549609c41db26c35cb8bce914362df9a3a7dd5785f6e2edddce8c185e4435c946157fd8a9ef780291cadf5dee1deb104b5de5f67eea34a3633f
|
|
@@ -134,6 +134,59 @@ PROMPT_FOLLOWUP_WORKTREE = Brainiac::Plugins::Fizzy::Prompts::FOLLOWUP_WORKTREE
|
|
|
134
134
|
PROMPT_FOLLOWUP_NO_WORKTREE = Brainiac::Plugins::Fizzy::Prompts::FOLLOWUP_NO_WORKTREE
|
|
135
135
|
PROMPT_MENTION = Brainiac::Plugins::Fizzy::Prompts::MENTION
|
|
136
136
|
PROMPT_CROSS_AGENT_REVIEW = Brainiac::Plugins::Fizzy::Prompts::CROSS_AGENT_REVIEW
|
|
137
|
+
PROMPT_PLANNING_MODE = Brainiac::Plugins::Fizzy::Prompts::PLANNING_MODE
|
|
138
|
+
|
|
139
|
+
# Render planning mode prompt — identical to render_prompt but inserts the planning
|
|
140
|
+
# instructions between PROMPT_CORE and the channel rules. This was originally defined
|
|
141
|
+
# in brainiac core's planning.rb but belongs here after the fizzy extraction.
|
|
142
|
+
def render_planning_prompt(situation_template, vars = {}, brain_context: "", card_context: "", agent_name: AI_AGENT_NAME,
|
|
143
|
+
channel: :fizzy, board_key: nil)
|
|
144
|
+
plans_dir = Brainiac::Plugins::Fizzy::Planning::PLANS_DIR
|
|
145
|
+
plan_file = File.join(plans_dir, "card-#{vars["CARD_ID"]}-plan.md")
|
|
146
|
+
|
|
147
|
+
result = ""
|
|
148
|
+
result += "#{brain_context}\n" unless brain_context.empty?
|
|
149
|
+
result += card_context unless card_context.empty?
|
|
150
|
+
result += PROMPT_CORE
|
|
151
|
+
result += PROMPT_PLANNING_MODE.gsub("{{PLAN_FILE}}", plan_file)
|
|
152
|
+
result += CHANNEL_PROMPTS.fetch(channel, PROMPT_FIZZY_CHANNEL)
|
|
153
|
+
result += situation_template
|
|
154
|
+
|
|
155
|
+
# Pre-post comment check (same as render_prompt)
|
|
156
|
+
case channel
|
|
157
|
+
when :fizzy then result += PROMPT_PRE_POST_CHECK_FIZZY
|
|
158
|
+
when :github then result += PROMPT_PRE_POST_CHECK_GITHUB
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
result += PROMPT_REFLECTION
|
|
162
|
+
|
|
163
|
+
planning_vars = vars.merge("PLAN_FILE" => plan_file)
|
|
164
|
+
planning_vars["KNOWLEDGE_DIR"] ||= KNOWLEDGE_DIR
|
|
165
|
+
planning_vars["MEMORY_DIR"] ||= memory_dir_for(agent_name)
|
|
166
|
+
planning_vars["PERSONA_DIR"] ||= persona_dir_for(agent_name)
|
|
167
|
+
planning_vars["PERSONA_COLLECTION"] ||= persona_collection_for(agent_name)
|
|
168
|
+
planning_vars["AGENT_NAME"] ||= agent_name
|
|
169
|
+
|
|
170
|
+
# Populate column IDs from board config, falling back to defaults
|
|
171
|
+
DEFAULT_COLUMN_IDS.each do |col_name, default_id|
|
|
172
|
+
var_name = "#{col_name.upcase}_COLUMN_ID"
|
|
173
|
+
planning_vars[var_name] ||= (board_key && board_column_id(board_key, col_name)) || default_id
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Touch memory file if CARD_ID is present — ensures file exists before agent tries to read it
|
|
177
|
+
if vars["CARD_ID"]
|
|
178
|
+
memory_file = File.join(planning_vars["MEMORY_DIR"], "card-#{vars["CARD_ID"]}.md")
|
|
179
|
+
FileUtils.mkdir_p(planning_vars["MEMORY_DIR"])
|
|
180
|
+
FileUtils.touch(memory_file)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
roster = agent_roster
|
|
184
|
+
roster_lines = roster.map { |_key, display| " - @#{display}" }.join("\n")
|
|
185
|
+
planning_vars["AGENT_ROSTER"] ||= roster_lines
|
|
186
|
+
|
|
187
|
+
planning_vars.each { |key, val| result.gsub!("{{#{key}}}", val.to_s) }
|
|
188
|
+
result
|
|
189
|
+
end
|
|
137
190
|
|
|
138
191
|
# Config constants — handler files reference these as top-level constants.
|
|
139
192
|
# These are evaluated after Config.load! has been called (during plugin register).
|
|
@@ -46,11 +46,23 @@ def handle_card_assigned(payload)
|
|
|
46
46
|
react_to_assignment(card_number, repo_path, assigned_agent)
|
|
47
47
|
worktree_path = setup_assigned_worktree(repo_path, branch, card_internal_id, card_number, project_key, assigned_agent)
|
|
48
48
|
|
|
49
|
+
initial_cli = detect_cli_provider(tags: tags)
|
|
50
|
+
initial_model = detect_model(project_config, tags: tags)
|
|
51
|
+
initial_effort = detect_effort(project_config, tags: tags)
|
|
52
|
+
|
|
53
|
+
# Persist initial overrides from card tags to the work item
|
|
54
|
+
resolve_work_item_overrides(
|
|
55
|
+
branch: branch,
|
|
56
|
+
inline_cli_provider: initial_cli,
|
|
57
|
+
inline_model: initial_model,
|
|
58
|
+
inline_effort: initial_effort
|
|
59
|
+
)
|
|
60
|
+
|
|
49
61
|
dispatch_assigned_card(
|
|
50
62
|
card_number: card_number, card_internal_id: card_internal_id, title: title, tags: tags,
|
|
51
63
|
branch: branch, worktree_path: worktree_path, project_config: project_config, project_key: project_key,
|
|
52
|
-
agent_name: assigned_agent, model:
|
|
53
|
-
effort:
|
|
64
|
+
agent_name: assigned_agent, model: initial_model,
|
|
65
|
+
effort: initial_effort, cli_provider_override: initial_cli
|
|
54
66
|
)
|
|
55
67
|
end
|
|
56
68
|
|
|
@@ -17,7 +17,7 @@ CommentContext = Struct.new(
|
|
|
17
17
|
:mentioned_agent, :agent_name, :is_cross_agent_mention,
|
|
18
18
|
:project_config, :project_key, :card_number, :worktree,
|
|
19
19
|
:model, :effort, :deploy_intent, :cli_provider_override,
|
|
20
|
-
:comment_vars, :card_tags, :worktree_override,
|
|
20
|
+
:comment_vars, :card_tags, :worktree_override, :fresh,
|
|
21
21
|
keyword_init: true
|
|
22
22
|
)
|
|
23
23
|
|
|
@@ -70,6 +70,21 @@ def handle_comment(payload)
|
|
|
70
70
|
project_config: project_config, project_key: project_key
|
|
71
71
|
)
|
|
72
72
|
|
|
73
|
+
# --- Intent gate (mirrors brainiac-discord pattern) ---
|
|
74
|
+
# "Directly addressed" for Fizzy: agent is assigned to the card OR explicitly @mentioned.
|
|
75
|
+
# All current dispatch paths satisfy one of these, so intent is always bypassed today.
|
|
76
|
+
# This infrastructure supports future scenarios where agents observe cards they aren't
|
|
77
|
+
# assigned to or mentioned on (ambient watchers).
|
|
78
|
+
directly_addressed = mentioned_agent || (card_info && card_info["agent"]&.downcase == agent_name.downcase)
|
|
79
|
+
unless directly_addressed
|
|
80
|
+
card_number_for_intent = card_info&.dig("number") || eventable.dig("card", "number")
|
|
81
|
+
intent_ctx = card_number_for_intent ? fetch_intent_context(card_number_for_intent, repo_path: project_config["repo_path"], agent_name: agent_name) : nil
|
|
82
|
+
if intent_skip?(ctx.plain_text, agent_name: agent_name, source: :fizzy,
|
|
83
|
+
channel: "Fizzy card ##{card_number_for_intent || card_internal_id}", context: intent_ctx)
|
|
84
|
+
return [200, { status: "intent_skip", agent: agent_name }.to_json]
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
73
88
|
# --- Route to appropriate sub-handler ---
|
|
74
89
|
if is_cross_agent_mention
|
|
75
90
|
handle_cross_agent_mention(ctx)
|
|
@@ -91,18 +106,42 @@ def build_comment_context(eventable:, plain_text:, tags:, card_internal_id:, car
|
|
|
91
106
|
card_tags = eventable.dig("card", "tags") || []
|
|
92
107
|
clean_text = tags[:clean_text]
|
|
93
108
|
|
|
109
|
+
inline_cli = detect_cli_provider(text: plain_text, tags: card_tags)
|
|
110
|
+
inline_model = detect_model(project_config, text: plain_text)
|
|
111
|
+
inline_effort = detect_effort(project_config, tags: card_tags, text: plain_text)
|
|
112
|
+
|
|
113
|
+
# Resolve overrides against the work item — merges stored overrides with inline tags
|
|
114
|
+
# and persists any new inline tags for future dispatches.
|
|
115
|
+
branch = card_info["branch"] if card_info.is_a?(Hash)
|
|
116
|
+
if branch
|
|
117
|
+
resolved = resolve_work_item_overrides(
|
|
118
|
+
branch: branch,
|
|
119
|
+
inline_cli_provider: inline_cli,
|
|
120
|
+
inline_model: inline_model,
|
|
121
|
+
inline_effort: inline_effort
|
|
122
|
+
)
|
|
123
|
+
effective_cli = resolved[:cli_provider]
|
|
124
|
+
effective_model = resolved[:model]
|
|
125
|
+
effective_effort = resolved[:effort]
|
|
126
|
+
else
|
|
127
|
+
effective_cli = inline_cli
|
|
128
|
+
effective_model = inline_model
|
|
129
|
+
effective_effort = inline_effort
|
|
130
|
+
end
|
|
131
|
+
|
|
94
132
|
CommentContext.new(
|
|
95
133
|
eventable: eventable, plain_text: clean_text, card_internal_id: card_internal_id,
|
|
96
134
|
card_info: card_info, comment_id: comment_id, creator_name: creator_name,
|
|
97
135
|
creator_is_agent: creator_is_agent, mentioned_agent: mentioned_agent,
|
|
98
136
|
agent_name: agent_name, is_cross_agent_mention: is_cross_agent_mention,
|
|
99
137
|
project_config: project_config, project_key: project_key,
|
|
100
|
-
model:
|
|
101
|
-
effort:
|
|
138
|
+
model: effective_model,
|
|
139
|
+
effort: effective_effort,
|
|
102
140
|
deploy_intent: deploy_intent,
|
|
103
|
-
cli_provider_override:
|
|
141
|
+
cli_provider_override: effective_cli,
|
|
104
142
|
card_tags: card_tags,
|
|
105
143
|
worktree_override: resolve_worktree_override(tags, project_config),
|
|
144
|
+
fresh: tags[:fresh],
|
|
106
145
|
comment_vars: {
|
|
107
146
|
"COMMENT_CREATOR" => creator_name || "Unknown",
|
|
108
147
|
"COMMENT_ID" => comment_id.to_s,
|
|
@@ -362,17 +401,14 @@ def dispatch_cross_agent_review(ctx, card_key:, card_number:, card_assigned_agen
|
|
|
362
401
|
review_worktree_path, review_branch = setup_cross_agent_worktree(ctx, card_number)
|
|
363
402
|
prompt = build_cross_agent_prompt(ctx, card_number, card_assigned_agent, review_worktree_path, review_branch)
|
|
364
403
|
|
|
365
|
-
intent_ctx = fetch_intent_context(card_number, repo_path: ctx.project_config["repo_path"], agent_name: ctx.agent_name)
|
|
366
404
|
pid, log_file = run_agent(prompt,
|
|
367
405
|
project_config: ctx.project_config, chdir: review_worktree_path,
|
|
368
406
|
log_name: "review-#{ctx.agent_name.downcase}-#{card_number || ctx.card_internal_id}",
|
|
369
407
|
model: ctx.model, effort: ctx.effort, agent_name: ctx.agent_name,
|
|
370
408
|
card_number: card_number, comment_id: ctx.comment_id,
|
|
371
409
|
source: :fizzy, source_context: { card_number: card_number },
|
|
372
|
-
cli_provider: ctx.cli_provider_override
|
|
373
|
-
|
|
374
|
-
context: intent_ctx)
|
|
375
|
-
return [200, { status: "intent_skip", agent: ctx.agent_name, card: card_number }.to_json] unless pid
|
|
410
|
+
cli_provider: ctx.cli_provider_override)
|
|
411
|
+
return [200, { status: "dispatch_failed", agent: ctx.agent_name, card: card_number }.to_json] unless pid
|
|
376
412
|
|
|
377
413
|
register_session(card_key, pid, log_file: log_file, supersede_key: card_key, agent_name: ctx.agent_name)
|
|
378
414
|
|
|
@@ -470,17 +506,14 @@ end
|
|
|
470
506
|
def dispatch_new_mention(ctx, card_key:, card_number:, card_title:, branch:, worktree_path:)
|
|
471
507
|
prompt = build_mention_prompt(ctx, card_number, card_title, branch, worktree_path)
|
|
472
508
|
|
|
473
|
-
intent_ctx = fetch_intent_context(card_number, repo_path: ctx.project_config["repo_path"], agent_name: ctx.agent_name)
|
|
474
509
|
pid, log_file = run_agent(prompt,
|
|
475
510
|
project_config: ctx.project_config, chdir: worktree_path,
|
|
476
511
|
log_name: "mention-#{card_number || ctx.card_internal_id}",
|
|
477
512
|
model: ctx.model, effort: ctx.effort, agent_name: ctx.agent_name,
|
|
478
513
|
card_number: card_number, comment_id: ctx.comment_id,
|
|
479
514
|
source: :fizzy, cli_provider: ctx.cli_provider_override,
|
|
480
|
-
source_context: { card_number: card_number }
|
|
481
|
-
|
|
482
|
-
context: intent_ctx)
|
|
483
|
-
return [200, { status: "intent_skip", agent: ctx.agent_name, card: card_number }.to_json] unless pid
|
|
515
|
+
source_context: { card_number: card_number })
|
|
516
|
+
return [200, { status: "dispatch_failed", agent: ctx.agent_name, card: card_number }.to_json] unless pid
|
|
484
517
|
|
|
485
518
|
register_session(card_key, pid, log_file: log_file, supersede_key: card_key, agent_name: ctx.agent_name)
|
|
486
519
|
|
|
@@ -494,11 +527,15 @@ def dispatch_followup_comment(ctx, card_key:, card_number:, work_dir:)
|
|
|
494
527
|
effort = detect_effort(ctx.project_config, tags: card_tags, text: ctx.plain_text)
|
|
495
528
|
|
|
496
529
|
is_worktree = work_dir != ctx.project_config["repo_path"]
|
|
497
|
-
should_resume = is_worktree && resume_viable?(
|
|
530
|
+
should_resume = is_worktree && !ctx.fresh && resume_viable?(
|
|
498
531
|
project_config: ctx.project_config, cli_provider: ctx.cli_provider_override,
|
|
499
532
|
agent_name: ctx.agent_name, chdir: work_dir
|
|
500
533
|
)
|
|
501
534
|
|
|
535
|
+
if ctx.fresh && is_worktree
|
|
536
|
+
LOG.info "[Fizzy] [fresh] tag — forcing new session instead of resuming on card #{card_number || ctx.card_internal_id}"
|
|
537
|
+
end
|
|
538
|
+
|
|
502
539
|
prompt = if should_resume
|
|
503
540
|
LOG.info "[Resume] Using lean prompt for follow-up on card #{card_number || ctx.card_internal_id}"
|
|
504
541
|
render_resume_prompt(
|
|
@@ -509,7 +546,6 @@ def dispatch_followup_comment(ctx, card_key:, card_number:, work_dir:)
|
|
|
509
546
|
build_followup_prompt(ctx, card_number, card_tags, work_dir)
|
|
510
547
|
end
|
|
511
548
|
|
|
512
|
-
intent_ctx = fetch_intent_context(card_number, repo_path: ctx.project_config["repo_path"], agent_name: ctx.agent_name)
|
|
513
549
|
pid, log_file = run_agent(prompt,
|
|
514
550
|
project_config: ctx.project_config, chdir: work_dir,
|
|
515
551
|
log_name: "followup-#{card_number || ctx.card_internal_id}",
|
|
@@ -519,10 +555,8 @@ def dispatch_followup_comment(ctx, card_key:, card_number:, work_dir:)
|
|
|
519
555
|
source_context: {
|
|
520
556
|
card_number: card_number, card_internal_id: ctx.card_internal_id,
|
|
521
557
|
deploy_intent: ctx.deploy_intent
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
context: intent_ctx)
|
|
525
|
-
return [200, { status: "intent_skip", agent: ctx.agent_name, card: card_number }.to_json] unless pid
|
|
558
|
+
})
|
|
559
|
+
return [200, { status: "dispatch_failed", agent: ctx.agent_name, card: card_number }.to_json] unless pid
|
|
526
560
|
|
|
527
561
|
register_session(card_key, pid, log_file: log_file, supersede_key: card_key, agent_name: ctx.agent_name)
|
|
528
562
|
|
|
@@ -116,6 +116,95 @@ module Brainiac
|
|
|
116
116
|
Run any UAT testing steps defined in the card or acceptance criteria.
|
|
117
117
|
Post results as a comment on the card.
|
|
118
118
|
PROMPT
|
|
119
|
+
|
|
120
|
+
PLANNING_MODE = <<~PROMPT
|
|
121
|
+
## Planning Mode (ACTIVE)
|
|
122
|
+
|
|
123
|
+
You are in **planning mode**. Your job is to gather requirements and break down the work into actionable tasks.
|
|
124
|
+
|
|
125
|
+
### Your Role
|
|
126
|
+
- Ask clarifying questions to understand the problem, constraints, and desired outcome
|
|
127
|
+
- Continue asking until you have a clear picture (don't rush to a plan)
|
|
128
|
+
- Understand user intent naturally — "go ahead", "that's enough", "proceed" all mean the same thing
|
|
129
|
+
- When you have enough information OR the user signals they're ready, generate the plan
|
|
130
|
+
|
|
131
|
+
### Question Guidelines
|
|
132
|
+
- Ask specific, focused questions (not generic "anything else?")
|
|
133
|
+
- Build on previous answers — reference what you've learned
|
|
134
|
+
- Prioritize questions that would significantly change the approach
|
|
135
|
+
- If you're 90% confident, proceed. If you're 60% confident, ask.
|
|
136
|
+
|
|
137
|
+
### When to Stop Asking
|
|
138
|
+
The user will signal they're ready in natural language:
|
|
139
|
+
- "go ahead", "proceed", "that's enough", "looks good", "yeah do it"
|
|
140
|
+
- "I think you have enough", "start working", "make the plan"
|
|
141
|
+
|
|
142
|
+
You should also stop if:
|
|
143
|
+
- You've asked 5+ questions and have a clear understanding
|
|
144
|
+
- The user is getting impatient or frustrated
|
|
145
|
+
- The remaining unknowns are minor details you can decide yourself
|
|
146
|
+
|
|
147
|
+
### Generating the Plan
|
|
148
|
+
When ready, create a plan file at `{{PLAN_FILE}}` with this structure:
|
|
149
|
+
|
|
150
|
+
```markdown
|
|
151
|
+
# Feature: [Title]
|
|
152
|
+
|
|
153
|
+
## Problem Statement
|
|
154
|
+
[What we're solving and why]
|
|
155
|
+
|
|
156
|
+
## Requirements
|
|
157
|
+
- Requirement 1
|
|
158
|
+
- Requirement 2
|
|
159
|
+
- Requirement 3
|
|
160
|
+
|
|
161
|
+
## Approach
|
|
162
|
+
[High-level strategy and key decisions]
|
|
163
|
+
|
|
164
|
+
## Task Breakdown
|
|
165
|
+
### Task 1: [Clear, actionable title]
|
|
166
|
+
- **Objective**: [What this task accomplishes]
|
|
167
|
+
- **Approach**: [How to implement it]
|
|
168
|
+
- **Demo**: [What "done" looks like]
|
|
169
|
+
|
|
170
|
+
### Task 2: [Clear, actionable title]
|
|
171
|
+
- **Objective**: [What this task accomplishes]
|
|
172
|
+
- **Approach**: [How to implement it]
|
|
173
|
+
- **Demo**: [What "done" looks like]
|
|
174
|
+
|
|
175
|
+
[Continue for all tasks...]
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Memory Management
|
|
179
|
+
Log every question and answer to your memory file in this format:
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
## Planning Q&A
|
|
183
|
+
Q: [Your question]
|
|
184
|
+
A: [User's answer]
|
|
185
|
+
|
|
186
|
+
Q: [Next question]
|
|
187
|
+
A: [User's answer]
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Also track:
|
|
191
|
+
- `planning_complete: false` (update to `true` when plan is generated)
|
|
192
|
+
- Key decisions and constraints discovered
|
|
193
|
+
- Any blockers or unknowns that remain
|
|
194
|
+
|
|
195
|
+
### After Planning
|
|
196
|
+
Once you've written the plan file:
|
|
197
|
+
1. Update memory with `planning_complete: true`
|
|
198
|
+
2. Post a comment summarizing the plan and linking to the file
|
|
199
|
+
3. The system will automatically create Fizzy steps from your task breakdown
|
|
200
|
+
|
|
201
|
+
### Important
|
|
202
|
+
- You are READ-ONLY during planning — no code changes, no commits
|
|
203
|
+
- Focus on understanding the problem, not solving it yet
|
|
204
|
+
- The plan should be detailed enough that any agent could execute it
|
|
205
|
+
- Task titles should be clear and actionable (they become Fizzy step names)
|
|
206
|
+
|
|
207
|
+
PROMPT
|
|
119
208
|
end
|
|
120
209
|
end
|
|
121
210
|
end
|