brainiac 0.0.2 → 0.0.3
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
- checksums.yaml.gz.sig +0 -0
- data/Gemfile.lock +2 -2
- data/bin/brainiac +317 -0
- data/bin/brainiac-completion.bash +178 -0
- data/lib/brainiac/agents.rb +34 -0
- data/lib/brainiac/brain.rb +25 -0
- data/lib/brainiac/config.rb +4 -0
- data/lib/brainiac/cron.rb +9 -6
- data/lib/brainiac/handlers/discord.rb +233 -30
- data/lib/brainiac/handlers/fizzy.rb +129 -86
- data/lib/brainiac/helpers.rb +125 -41
- data/lib/brainiac/prompts.rb +97 -105
- data/lib/brainiac/version.rb +1 -1
- data/receiver.rb +13 -0
- data/templates/cli-providers/grok.json.example +15 -0
- data/templates/cli-providers/kiro.json.example +15 -0
- data/templates/roles/code-reviewer.md.example +28 -0
- data/templates/roles/general-engineer.md.example +36 -0
- data/templates/roles/test-engineer.md.example +33 -0
- data.tar.gz.sig +0 -0
- metadata +7 -1
- metadata.gz.sig +0 -0
data/lib/brainiac/helpers.rb
CHANGED
|
@@ -7,14 +7,9 @@ CLI_PROVIDERS_DIR = File.join(BRAINIAC_DIR, "cli-providers")
|
|
|
7
7
|
|
|
8
8
|
# --trust-all-tools alone doesn't bypass the non-interactive deny list in kiro-cli 1.29.8+.
|
|
9
9
|
# Adding --trust-tools with explicit tool names ensures write/exec tools are approved.
|
|
10
|
+
# This is now configured in the kiro provider's default_args instead of being hardcoded here.
|
|
10
11
|
TRUSTED_TOOLS = "execute_bash,fs_write,fs_read,code,grep,glob,web_search,web_fetch,use_subagent,use_aws"
|
|
11
12
|
|
|
12
|
-
def add_trust_tools!(cmd, agent_cli_args)
|
|
13
|
-
return if agent_cli_args.include?("--trust-tools")
|
|
14
|
-
|
|
15
|
-
cmd.push("--trust-tools", TRUSTED_TOOLS)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
13
|
# Clean up all worktrees associated with a card: the primary worktree and any
|
|
19
14
|
# cross-agent review worktrees (e.g. glados-fizzy-123-*, threepio-fizzy-123-*).
|
|
20
15
|
# Safe: skips worktrees with uncommitted changes.
|
|
@@ -49,24 +44,86 @@ def cleanup_card_worktrees(card_number, repo_path:, primary_worktree: nil, prima
|
|
|
49
44
|
LOG.info "Card ##{card_number}: cleaned up #{cleaned} worktree(s)" if cleaned.positive?
|
|
50
45
|
end
|
|
51
46
|
|
|
47
|
+
# Load a CLI provider config from ~/.brainiac/cli-providers/<name>.json.
|
|
48
|
+
# Returns a hash with normalized keys, or {} if not found.
|
|
49
|
+
def load_cli_provider(provider_name)
|
|
50
|
+
return {} unless provider_name
|
|
51
|
+
|
|
52
|
+
provider_file = File.join(CLI_PROVIDERS_DIR, "#{provider_name}.json")
|
|
53
|
+
return {} unless File.exist?(provider_file)
|
|
54
|
+
|
|
55
|
+
raw = JSON.parse(File.read(provider_file))
|
|
56
|
+
config = {
|
|
57
|
+
"agent_cli" => raw["binary"],
|
|
58
|
+
"agent_cli_args" => raw["default_args"],
|
|
59
|
+
"agent_model_flag" => raw["model_flag"],
|
|
60
|
+
"agent_effort_flag" => raw["effort_flag"],
|
|
61
|
+
"allowed_models" => raw["models"],
|
|
62
|
+
"allowed_efforts" => raw["efforts"]
|
|
63
|
+
}
|
|
64
|
+
# agent_flag: how the agent identity is passed (default: "--agent").
|
|
65
|
+
# Set to null/false in provider JSON to suppress passing agent name entirely.
|
|
66
|
+
# We must preserve the key even when nil so merges don't lose the "no agent flag" intent.
|
|
67
|
+
config["agent_flag"] = raw.key?("agent_flag") ? raw["agent_flag"] : "--agent"
|
|
68
|
+
# prompt_mode: "stdin" (default) or "flag" — how the prompt is delivered.
|
|
69
|
+
config["prompt_mode"] = raw["prompt_mode"] || "stdin"
|
|
70
|
+
config["prompt_flag"] = raw["prompt_flag"] if raw["prompt_flag"]
|
|
71
|
+
# resume_flag: when set, follow-up dispatches use this flag to continue the
|
|
72
|
+
# most recent session in the working directory (e.g. "-c" or "--continue").
|
|
73
|
+
config["resume_flag"] = raw["resume_flag"] if raw["resume_flag"]
|
|
74
|
+
# Compact nil values except agent_flag (which uses nil to mean "don't pass agent name")
|
|
75
|
+
agent_flag_value = config["agent_flag"]
|
|
76
|
+
config.compact!
|
|
77
|
+
config["agent_flag"] = agent_flag_value if raw.key?("agent_flag")
|
|
78
|
+
config
|
|
79
|
+
rescue JSON::ParserError => e
|
|
80
|
+
LOG.warn "Failed to parse CLI provider '#{provider_name}': #{e.message}"
|
|
81
|
+
{}
|
|
82
|
+
end
|
|
83
|
+
|
|
52
84
|
# Resolve CLI config for a project by merging provider defaults with project overrides.
|
|
53
|
-
# Priority:
|
|
54
|
-
def resolve_project_cli_config(project_config)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
85
|
+
# Priority: cli_provider_override > agent-level cli_provider > project-level cli_provider > DEFAULT_PROJECT
|
|
86
|
+
def resolve_project_cli_config(project_config, cli_provider_override: nil, agent_name: nil)
|
|
87
|
+
# Determine which CLI provider to use (priority: override > agent > project)
|
|
88
|
+
provider_name = cli_provider_override
|
|
89
|
+
provider_name ||= agent_cli_provider_for(agent_name) if agent_name
|
|
90
|
+
provider_name ||= project_config["cli_provider"]
|
|
91
|
+
|
|
92
|
+
provider_config = load_cli_provider(provider_name)
|
|
93
|
+
|
|
94
|
+
DEFAULT_PROJECT.merge(provider_config).merge(project_config).tap do |resolved|
|
|
95
|
+
# If an override or agent-level provider was used, it should win over the
|
|
96
|
+
# project-level cli_provider's config. Re-apply the override provider on top.
|
|
97
|
+
resolved.merge!(provider_config) if provider_name && provider_name != project_config["cli_provider"]
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Get the cli_provider configured at the agent level in agents.json.
|
|
102
|
+
def agent_cli_provider_for(agent_name)
|
|
103
|
+
return nil unless agent_name
|
|
104
|
+
|
|
105
|
+
key = agent_name.downcase.gsub(/[^a-z0-9-]/, "-")
|
|
106
|
+
entry = AGENT_REGISTRY[key]
|
|
107
|
+
return nil unless entry.is_a?(Hash)
|
|
108
|
+
|
|
109
|
+
entry["cli_provider"]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Detect CLI provider override from inline [cli:X] tag or Fizzy card tags.
|
|
113
|
+
# Returns the provider name (e.g. "grok") or nil.
|
|
114
|
+
def detect_cli_provider(text: "", tags: [])
|
|
115
|
+
# Inline tag: [cli:grok]
|
|
116
|
+
if (match = text.match(/\[cli:(\w+)\]/i))
|
|
117
|
+
return match[1].downcase
|
|
67
118
|
end
|
|
68
119
|
|
|
69
|
-
|
|
120
|
+
# Fizzy card tags: cli-grok
|
|
121
|
+
tags.each do |tag|
|
|
122
|
+
name = (tag.is_a?(Hash) ? tag["name"] : tag).to_s.downcase
|
|
123
|
+
return name.sub("cli-", "") if name.start_with?("cli-")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
nil
|
|
70
127
|
end
|
|
71
128
|
|
|
72
129
|
# Copy gitignored files matching .worktreeinclude patterns from repo to worktree.
|
|
@@ -502,13 +559,17 @@ rescue StandardError => e
|
|
|
502
559
|
end
|
|
503
560
|
|
|
504
561
|
def run_agent(prompt, project_config:, chdir: nil, log_name: "agent", model: nil, effort: nil, agent_name: nil, card_number: nil, comment_id: nil,
|
|
505
|
-
source: nil, source_context: {}, skip_column_move: false)
|
|
506
|
-
resolved = resolve_project_cli_config(project_config)
|
|
562
|
+
source: nil, source_context: {}, skip_column_move: false, cli_provider: nil, resume: false)
|
|
563
|
+
resolved = resolve_project_cli_config(project_config, cli_provider_override: cli_provider, agent_name: agent_name)
|
|
507
564
|
chdir ||= resolved["repo_path"]
|
|
508
565
|
model ||= resolved["agent_model"]
|
|
509
566
|
effort ||= resolved["agent_effort"]
|
|
510
567
|
agent_config_name = agent_name&.downcase&.gsub(/[^a-z0-9-]/, "-")
|
|
511
568
|
|
|
569
|
+
# Auto-resume: if the provider supports session resume and we're in a worktree
|
|
570
|
+
# that has had a previous session, resume it. Only applies to follow-ups (not first dispatch).
|
|
571
|
+
should_resume = resume && resolved["resume_flag"]
|
|
572
|
+
|
|
512
573
|
ensure_fizzy_yaml!(chdir, project_config)
|
|
513
574
|
Thread.new { scrub_invalid_attachments!(chdir) }
|
|
514
575
|
|
|
@@ -517,24 +578,22 @@ def run_agent(prompt, project_config:, chdir: nil, log_name: "agent", model: nil
|
|
|
517
578
|
FileUtils.mkdir_p(File.dirname(log_file))
|
|
518
579
|
|
|
519
580
|
prompt_file = write_agent_prompt_file(prompt, log_name, timestamp)
|
|
520
|
-
cmd = build_agent_cmd(resolved, agent_config_name: agent_config_name, model: model, effort: effort)
|
|
581
|
+
cmd = build_agent_cmd(resolved, agent_config_name: agent_config_name, model: model, effort: effort, prompt_file: prompt_file, resume: should_resume)
|
|
582
|
+
prompt_mode = resolved["prompt_mode"] || "stdin"
|
|
583
|
+
|
|
521
584
|
spawn_env = agent_env_for(agent_name)
|
|
522
585
|
|
|
523
586
|
LOG.info "Running #{resolved["agent_cli"]} in #{chdir}, logging to #{log_file}"
|
|
524
587
|
LOG.info "Prompt written to #{prompt_file}"
|
|
525
|
-
LOG.info "Command: #{cmd.join(" ")}"
|
|
588
|
+
LOG.info "Command: #{cmd.join(" ")}#{" (resuming session)" if should_resume}"
|
|
526
589
|
LOG.info "Injecting #{spawn_env.size} env var(s) for agent #{agent_name}: #{spawn_env.keys.join(", ")}" unless spawn_env.empty?
|
|
527
590
|
|
|
528
|
-
head_before = nil
|
|
529
591
|
project_key_for_restart = PROJECTS.find { |_k, v| v == project_config }&.first
|
|
530
|
-
if project_key_for_restart == "brainiac"
|
|
531
|
-
head_before, = Open3.capture2("git", "rev-parse", "HEAD", chdir: chdir)
|
|
532
|
-
head_before = head_before.strip
|
|
533
|
-
end
|
|
592
|
+
head_before, status_before = capture_git_state(chdir) if project_key_for_restart == "brainiac"
|
|
534
593
|
|
|
535
594
|
pid = spawn(spawn_env, *cmd,
|
|
536
595
|
chdir: chdir,
|
|
537
|
-
in: prompt_file,
|
|
596
|
+
**(prompt_mode == "stdin" ? { in: prompt_file } : {}),
|
|
538
597
|
out: [log_file, "w"],
|
|
539
598
|
err: %i[child out])
|
|
540
599
|
|
|
@@ -546,7 +605,8 @@ def run_agent(prompt, project_config:, chdir: nil, log_name: "agent", model: nil
|
|
|
546
605
|
prompt_file: prompt_file, chdir: chdir, source: source,
|
|
547
606
|
source_context: source_context, project_config: project_config,
|
|
548
607
|
card_number: card_number, skip_column_move: skip_column_move,
|
|
549
|
-
head_before: head_before,
|
|
608
|
+
head_before: head_before, status_before: status_before,
|
|
609
|
+
project_key_for_restart: project_key_for_restart
|
|
550
610
|
)
|
|
551
611
|
end
|
|
552
612
|
|
|
@@ -578,13 +638,28 @@ def write_agent_prompt_file(prompt, log_name, timestamp)
|
|
|
578
638
|
end
|
|
579
639
|
|
|
580
640
|
# Build the CLI command array for an agent invocation.
|
|
581
|
-
|
|
641
|
+
# When prompt_file is provided and prompt_mode is "flag", appends the prompt as a CLI argument.
|
|
642
|
+
# When resume is true and the provider has a resume_flag, adds it to continue the last session.
|
|
643
|
+
def build_agent_cmd(resolved, agent_config_name: nil, model: nil, effort: nil, prompt_file: nil, resume: false)
|
|
582
644
|
cmd = [resolved["agent_cli"]]
|
|
583
|
-
|
|
645
|
+
# agent_flag controls how the agent identity is passed. Defaults to "--agent".
|
|
646
|
+
# Provider configs can set it to a different flag or null to suppress entirely.
|
|
647
|
+
agent_flag = resolved.key?("agent_flag") ? resolved["agent_flag"] : "--agent"
|
|
648
|
+
cmd.push(agent_flag, agent_config_name) if agent_flag && agent_config_name
|
|
584
649
|
cmd.concat(resolved["agent_cli_args"].split)
|
|
585
|
-
|
|
586
|
-
|
|
650
|
+
# Only pass --model if the model is a valid ID for this provider.
|
|
651
|
+
# "auto" means "let the CLI choose" — skip passing it unless the provider explicitly maps it.
|
|
652
|
+
if model && resolved["agent_model_flag"] && !resolved["agent_model_flag"].empty?
|
|
653
|
+
allowed = resolved["allowed_models"] || {}
|
|
654
|
+
# Pass the model if it's a mapped value (e.g. "claude-opus-4.6") or the key itself is mapped
|
|
655
|
+
is_known = allowed.value?(model) || allowed.key?(model)
|
|
656
|
+
cmd.push(resolved["agent_model_flag"], model) if is_known
|
|
657
|
+
end
|
|
587
658
|
cmd.push(resolved["agent_effort_flag"], effort) if resolved["agent_effort_flag"] && !resolved["agent_effort_flag"].empty? && effort
|
|
659
|
+
# Resume the most recent session in the working directory (for multi-turn CLIs like grok)
|
|
660
|
+
cmd.push(resolved["resume_flag"]) if resume && resolved["resume_flag"]
|
|
661
|
+
# prompt_mode: "flag" passes the prompt file path via the configured prompt_flag (e.g. --prompt-file).
|
|
662
|
+
cmd.push(resolved["prompt_flag"], prompt_file) if prompt_file && resolved["prompt_mode"] == "flag" && resolved["prompt_flag"]
|
|
588
663
|
cmd
|
|
589
664
|
end
|
|
590
665
|
|
|
@@ -621,7 +696,7 @@ def handle_agent_completion(**ctx)
|
|
|
621
696
|
end
|
|
622
697
|
|
|
623
698
|
brain_push(message: "#{ctx[:agent_config_name] || "agent"}: #{ctx[:log_name]}")
|
|
624
|
-
check_brainiac_restart(ctx[:head_before], ctx[:chdir], ctx[:project_key_for_restart], ctx[:agent_config_name])
|
|
699
|
+
check_brainiac_restart(ctx[:head_before], ctx[:status_before], ctx[:chdir], ctx[:project_key_for_restart], ctx[:agent_config_name])
|
|
625
700
|
end
|
|
626
701
|
|
|
627
702
|
def handle_fizzy_post_session(fizzy_card, exit_status, signaled, agent_name, chdir, source, source_context, project_config, skip_column_move)
|
|
@@ -672,12 +747,21 @@ def handle_plan_finalization(prompt_file, agent_name, project_config)
|
|
|
672
747
|
end
|
|
673
748
|
end
|
|
674
749
|
|
|
675
|
-
|
|
750
|
+
# Capture git HEAD and working tree status for a directory.
|
|
751
|
+
# Returns [head_sha, status_porcelain] or [nil, nil] on failure.
|
|
752
|
+
def capture_git_state(chdir)
|
|
753
|
+
head, = Open3.capture2("git", "rev-parse", "HEAD", chdir: chdir)
|
|
754
|
+
status, = Open3.capture2("git", "status", "--porcelain", chdir: chdir)
|
|
755
|
+
[head.strip, status.strip]
|
|
756
|
+
rescue StandardError
|
|
757
|
+
[nil, nil]
|
|
758
|
+
end
|
|
759
|
+
|
|
760
|
+
def check_brainiac_restart(head_before, status_before, chdir, project_key_for_restart, agent_config_name)
|
|
676
761
|
return unless project_key_for_restart == "brainiac" && head_before
|
|
677
762
|
|
|
678
|
-
head_after, =
|
|
679
|
-
|
|
680
|
-
if head_after.strip != head_before || !git_status.strip.empty?
|
|
763
|
+
head_after, status_after = capture_git_state(chdir)
|
|
764
|
+
if head_after != head_before || status_after != (status_before || "")
|
|
681
765
|
queue_brainiac_restart(agent_config_name || "agent")
|
|
682
766
|
else
|
|
683
767
|
LOG.info "[Brainiac] #{agent_config_name || "agent"} session on brainiac had no changes — skipping restart"
|
data/lib/brainiac/prompts.rb
CHANGED
|
@@ -24,24 +24,13 @@ PROMPT_CORE = <<~PROMPT
|
|
|
24
24
|
Memory files MAY exist at `{{MEMORY_DIR}}/` — this is inside the brain, so they survive worktree deletion.
|
|
25
25
|
|
|
26
26
|
**At the very start of every session:**
|
|
27
|
-
1. Read `{{MEMORY_DIR}}/card-{{CARD_ID}}.md`. If it contains content, it has context from your previous sessions
|
|
27
|
+
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
28
|
|
|
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
|
|
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. If you need the full text of a truncated comment, run: `fizzy comment show COMMENT_ID --card CARD_NUMBER`
|
|
30
30
|
|
|
31
31
|
**Before you finish every session (even if you didn't complete the task):**
|
|
32
|
-
2.
|
|
33
|
-
|
|
34
|
-
Write in a format optimized for AI consumption. Include:
|
|
35
|
-
- Current status of the task (not started / in progress / blocked / done)
|
|
36
|
-
- What you accomplished this session
|
|
37
|
-
- Key decisions made and why
|
|
38
|
-
- Questions you asked and answers you received
|
|
39
|
-
- Open questions still waiting for answers
|
|
40
|
-
- Relevant file paths, branch state, PR URLs
|
|
41
|
-
- Anything that would help a fresh instance of you pick up exactly where you left off
|
|
42
|
-
- A brief timeline of sessions (append, don't overwrite previous entries)
|
|
43
|
-
- The exact comment IDs you posted this session (so future sessions can detect duplicates)
|
|
44
|
-
- A condensed summary of the full comment history (so future sessions don't need the raw comments — your memory is the authoritative record of what was discussed)
|
|
32
|
+
2. Update your memory file at `{{MEMORY_DIR}}/card-{{CARD_ID}}.md`.
|
|
33
|
+
Write what future-you needs to pick up where you left off. Use your judgement on what's important — status, decisions, open questions, file paths, PR URLs, timeline of sessions.
|
|
45
34
|
|
|
46
35
|
## Brain (Long-Term Memory via qmd)
|
|
47
36
|
You have a long-term memory called the "brain" that persists across ALL sessions and ALL cards.
|
|
@@ -63,17 +52,9 @@ PROMPT_CORE = <<~PROMPT
|
|
|
63
52
|
Standard unix commands (cd, ls, grep, cat, git, curl, etc.) don't need a brain search.
|
|
64
53
|
But for project-specific tools, do NOT guess at flags or syntax — wrong commands waste time and tokens. Look it up first.
|
|
65
54
|
|
|
66
|
-
**When to save knowledge
|
|
67
|
-
-
|
|
68
|
-
|
|
69
|
-
- You discover a non-obvious gotcha that would bite future-you → record it
|
|
70
|
-
- A major workflow or process changes → update the relevant doc
|
|
71
|
-
|
|
72
|
-
**Do NOT save knowledge for:**
|
|
73
|
-
- Routine card work (bug fixes, small features, standard implementations)
|
|
74
|
-
- Things that are already documented in the codebase (READMEs, comments, etc.)
|
|
75
|
-
- Minor corrections or one-off fixes
|
|
76
|
-
- Information that's only relevant to the current card (that goes in memory, not knowledge)
|
|
55
|
+
**When to save knowledge:** Be selective — only save significant architecture decisions,
|
|
56
|
+
non-obvious gotchas, major workflow changes, or things the user explicitly asks you to remember.
|
|
57
|
+
Routine card work and things already documented in the codebase don't need brain entries.
|
|
77
58
|
|
|
78
59
|
Organize files like:
|
|
79
60
|
- `{{KNOWLEDGE_DIR}}/projects/marketplace.md`
|
|
@@ -100,32 +81,17 @@ PROMPT_CORE = <<~PROMPT
|
|
|
100
81
|
- Brain knowledge (`{{KNOWLEDGE_DIR}}/`) = permanent technical knowledge (shared across all agents)
|
|
101
82
|
- Brain persona (`{{PERSONA_DIR}}/`) = permanent communication style (yours only)
|
|
102
83
|
|
|
103
|
-
## Communication Rules
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
Before posting ANY comment or response:
|
|
107
|
-
1. Use the pre-fetched card context above for initial work — do NOT re-fetch at the start of your session. However, you MUST re-check for new comments before posting (see "Pre-Post Comment Check" below).
|
|
108
|
-
2. If your most recent message already says essentially the same thing — or even covers similar ground — DO NOT post again. Just move on silently.
|
|
109
|
-
3. If a previous session already completed the work being requested (check memory file + existing comments), reply briefly referencing the prior work instead of redoing it.
|
|
110
|
-
4. Never post the same status update, summary, or question twice.
|
|
111
|
-
5. Combine all of your updates into a single message at the end of your work. Do NOT post incremental status updates (e.g. "looking into it", "starting work", "almost done"). One final summary is enough.
|
|
112
|
-
6. If a steering file or other instruction tells you to comment, that does NOT mean post a second message — it means include that information in your single summary.
|
|
84
|
+
## Communication Rules
|
|
85
|
+
Post only **once per session** — combine all updates into a single message at the end of your work.
|
|
86
|
+
Do not post incremental status updates. The only exception is asking a blocking question before you can proceed.
|
|
113
87
|
|
|
114
|
-
|
|
88
|
+
Before posting:
|
|
89
|
+
1. Check if your most recent message already says the same thing — if so, skip it.
|
|
90
|
+
2. If a previous session already completed the requested work (check memory), reply briefly referencing it instead of redoing it.
|
|
115
91
|
|
|
116
92
|
## Clarifying Questions (MANDATORY when uncertain)
|
|
117
93
|
|
|
118
|
-
If the task is ambiguous
|
|
119
|
-
- Ask specific questions before starting work
|
|
120
|
-
- Don't guess at user intent
|
|
121
|
-
- Don't make assumptions about scope or approach
|
|
122
|
-
- Better to ask once than implement wrong twice
|
|
123
|
-
|
|
124
|
-
Examples of when to ask:
|
|
125
|
-
- "Should this apply to X or just Y?"
|
|
126
|
-
- "Do you want me to update the existing flow or create a new one?"
|
|
127
|
-
- "This could mean A or B — which one?"
|
|
128
|
-
|
|
94
|
+
If the task is ambiguous or you're uncertain about requirements, ask before starting.
|
|
129
95
|
If you're 90% sure, proceed. If you're 60% sure, ask.
|
|
130
96
|
|
|
131
97
|
## Subagents (Delegating Work)
|
|
@@ -221,53 +187,31 @@ PROMPT
|
|
|
221
187
|
# sees its task first and reflects only after completing it.
|
|
222
188
|
# ---------------------------------------------------------------------------
|
|
223
189
|
PROMPT_REFLECTION = <<~PROMPT
|
|
224
|
-
## Post-
|
|
225
|
-
|
|
226
|
-
After you've posted your comment/response and finished your work, reflect on the session.
|
|
227
|
-
This happens at the end so your visible output isn't delayed.
|
|
190
|
+
## Post-Session Reflection (after posting your response and updating memory)
|
|
228
191
|
|
|
229
|
-
### Step 1:
|
|
230
|
-
`qmd search "personality tone voice" -c {{PERSONA_COLLECTION}}`
|
|
192
|
+
### Step 1: Check persona relevance
|
|
231
193
|
`qmd search "{{COMMENT_CREATOR}}" -c {{PERSONA_COLLECTION}}`
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
-
|
|
243
|
-
-
|
|
244
|
-
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
**Skills — should I extract a reusable workflow?**
|
|
254
|
-
- Did this session involve a multi-step procedure that I (or another agent) might repeat?
|
|
255
|
-
- Did I recover from errors and discover a reliable sequence of steps?
|
|
256
|
-
- Was there a non-obvious workflow (build, deploy, debug, test) that took 5+ tool calls to get right?
|
|
257
|
-
- If yes: create a SKILL.md file at `{{KNOWLEDGE_DIR}}/skills/<skill-name>/SKILL.md` with YAML frontmatter:
|
|
258
|
-
```
|
|
259
|
-
---
|
|
260
|
-
name: skill-name-slug
|
|
261
|
-
description: One-line description of when to use this skill
|
|
262
|
-
tags: [relevant, tags]
|
|
263
|
-
---
|
|
264
|
-
Step-by-step procedural content...
|
|
265
|
-
```
|
|
266
|
-
- If no clear reusable workflow emerged, skip this.
|
|
267
|
-
|
|
268
|
-
### Step 3: Update the brain (or consciously decide not to)
|
|
269
|
-
If anything needs saving, write or update the relevant file(s).
|
|
270
|
-
If nothing needs updating, that's fine — but you must have actively considered it.
|
|
194
|
+
If no results, this might be someone new worth noting.
|
|
195
|
+
|
|
196
|
+
### Step 2: Decide what to update
|
|
197
|
+
Consider the interaction and ask:
|
|
198
|
+
|
|
199
|
+
**Persona** — Did the user give feedback (explicit or implicit) on your tone or style?
|
|
200
|
+
Is this someone new? Did they seem frustrated or pleased? Update persona files if so.
|
|
201
|
+
Periodically condense persona files that have grown large — distill into patterns.
|
|
202
|
+
|
|
203
|
+
**Knowledge** — High bar. Only save if:
|
|
204
|
+
- User explicitly asked you to remember something
|
|
205
|
+
- A significant architecture decision or convention was established
|
|
206
|
+
- You discovered a non-obvious gotcha
|
|
207
|
+
- A major workflow changed
|
|
208
|
+
|
|
209
|
+
**Skills** — Did this session involve a multi-step procedure (5+ tool calls) that you or
|
|
210
|
+
another agent might repeat? If so, save it at `{{KNOWLEDGE_DIR}}/skills/<name>/SKILL.md`
|
|
211
|
+
with YAML frontmatter (name, description, tags).
|
|
212
|
+
|
|
213
|
+
### Step 3: Update the brain or move on
|
|
214
|
+
Write/update relevant files if needed. If nothing warrants saving, move on.
|
|
271
215
|
|
|
272
216
|
PROMPT
|
|
273
217
|
|
|
@@ -419,7 +363,7 @@ PROMPT_CARD_ASSIGNED = <<~'PROMPT'
|
|
|
419
363
|
You have been assigned Fizzy card #{{CARD_NUMBER}}: "{{CARD_TITLE}}".
|
|
420
364
|
You are on branch "{{BRANCH}}" in a fresh worktree.
|
|
421
365
|
Implement the task, commit, push, and open a PR (link back to Fizzy).
|
|
422
|
-
When you're done, post
|
|
366
|
+
When you're done, post a comment on the card with a concise summary, PR link, and branch name.
|
|
423
367
|
|
|
424
368
|
**MANDATORY: Always include the branch name in your comment.** Use this format:
|
|
425
369
|
`<p><strong>Branch:</strong> <code>{{BRANCH}}</code></p>`
|
|
@@ -435,9 +379,8 @@ PROMPT_FOLLOWUP_WORKTREE = <<~'PROMPT'
|
|
|
435
379
|
"""
|
|
436
380
|
|
|
437
381
|
The card and its full comment history are provided above. Focus your response on the comment above.
|
|
438
|
-
If you've already addressed this exact request in a previous session (check your memory file), reply
|
|
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.
|
|
439
383
|
Otherwise, make the requested changes, commit, push, and update the PR.
|
|
440
|
-
Post ONE comment on the card with a concise summary of what you changed. Do not post multiple comments.
|
|
441
384
|
PROMPT
|
|
442
385
|
|
|
443
386
|
PROMPT_FOLLOWUP_NO_WORKTREE = <<~PROMPT
|
|
@@ -449,7 +392,7 @@ PROMPT_FOLLOWUP_NO_WORKTREE = <<~PROMPT
|
|
|
449
392
|
"""
|
|
450
393
|
|
|
451
394
|
The card and its full comment history are provided above. Focus your response on the comment above.
|
|
452
|
-
If you've already addressed this exact request in a previous session (check your memory file), reply
|
|
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.
|
|
453
396
|
Otherwise, respond accordingly — that could include doing work on a new or existing branch.
|
|
454
397
|
PROMPT
|
|
455
398
|
|
|
@@ -461,8 +404,6 @@ PROMPT_MENTION = <<~PROMPT
|
|
|
461
404
|
- Investigate the codebase and provide your thoughts
|
|
462
405
|
- Make exploratory changes or create test files (they won't pollute the main branch)
|
|
463
406
|
- Create a PR if your exploration leads to a concrete solution
|
|
464
|
-
|
|
465
|
-
If you comment on the card, do so exactly once with everything you need to say.
|
|
466
407
|
PROMPT
|
|
467
408
|
|
|
468
409
|
PROMPT_CROSS_AGENT_REVIEW = <<~'PROMPT'
|
|
@@ -484,10 +425,7 @@ PROMPT_CROSS_AGENT_REVIEW = <<~'PROMPT'
|
|
|
484
425
|
|
|
485
426
|
**IMPORTANT: Do NOT @mention any other agents in your response.** You were brought in for
|
|
486
427
|
a one-shot review. If you think another agent should be involved, say so in plain text
|
|
487
|
-
|
|
488
|
-
Tagging agents creates automated dispatches and can cause infinite loops.
|
|
489
|
-
|
|
490
|
-
Post ONE comment on the card with your thoughts. Do not post multiple comments.
|
|
428
|
+
but do NOT use @Agent syntax — tagging agents creates automated dispatches.
|
|
491
429
|
PROMPT
|
|
492
430
|
|
|
493
431
|
PROMPT_DISCORD = <<~'PROMPT'
|
|
@@ -518,7 +456,7 @@ PROMPT_GITHUB_PR_COMMENT = <<~'PROMPT'
|
|
|
518
456
|
1. Read the comment and understand what's being requested
|
|
519
457
|
2. Make any necessary changes
|
|
520
458
|
3. Commit and push your updates
|
|
521
|
-
4.
|
|
459
|
+
4. Reply on the PR summarizing what you changed
|
|
522
460
|
|
|
523
461
|
You are in the worktree at {{WORKTREE_PATH}}.
|
|
524
462
|
PROMPT
|
|
@@ -533,7 +471,7 @@ PROMPT_GITHUB_PR_REVIEW = <<~'PROMPT'
|
|
|
533
471
|
2. Address each piece of feedback
|
|
534
472
|
3. Make the necessary code changes
|
|
535
473
|
4. Commit and push your updates
|
|
536
|
-
5. Post
|
|
474
|
+
5. Post a comment on the PR summarizing the changes
|
|
537
475
|
|
|
538
476
|
You are in the worktree at {{WORKTREE_PATH}}.
|
|
539
477
|
PROMPT
|
|
@@ -618,3 +556,57 @@ def render_prompt(template, vars = {}, brain_context: "", card_context: "", agen
|
|
|
618
556
|
vars.each { |key, val| result.gsub!("{{#{key}}}", val.to_s) }
|
|
619
557
|
result
|
|
620
558
|
end
|
|
559
|
+
|
|
560
|
+
# Lean prompt for resumed sessions. The previous session already has the full context
|
|
561
|
+
# (role, persona, knowledge, core instructions, channel prompts). We only send the new
|
|
562
|
+
# comment and any fresh card context so the agent knows what changed.
|
|
563
|
+
def render_resume_prompt(comment_body:, comment_creator:, comment_id:, card_number: nil, agent_name: AI_AGENT_NAME)
|
|
564
|
+
# Touch memory file (same as render_prompt does)
|
|
565
|
+
memory_dir = memory_dir_for(agent_name)
|
|
566
|
+
card_id = card_number || "unknown"
|
|
567
|
+
memory_file = File.join(memory_dir, "card-#{card_id}.md")
|
|
568
|
+
FileUtils.mkdir_p(memory_dir)
|
|
569
|
+
FileUtils.touch(memory_file)
|
|
570
|
+
|
|
571
|
+
lines = []
|
|
572
|
+
lines << "## Resumed Session — New Follow-up Comment"
|
|
573
|
+
lines << ""
|
|
574
|
+
lines << "This is a continuation of your previous session on this card."
|
|
575
|
+
lines << "All prior context, instructions, and your previous work are still in this conversation."
|
|
576
|
+
lines << ""
|
|
577
|
+
lines << "### New Comment from #{comment_creator} (comment ID: #{comment_id})"
|
|
578
|
+
lines << ""
|
|
579
|
+
lines << comment_body
|
|
580
|
+
lines << ""
|
|
581
|
+
lines << "---"
|
|
582
|
+
lines << "Respond to this comment. All your previous instructions still apply."
|
|
583
|
+
|
|
584
|
+
lines.join("\n")
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
# Lean resume prompt for Discord threads. The previous session has full context
|
|
588
|
+
# (role, persona, knowledge, instructions). We only send the new message + channel history.
|
|
589
|
+
def render_discord_resume_prompt(message_body:, discord_user:, response_file:, agent_name: AI_AGENT_NAME, card_id: nil)
|
|
590
|
+
memory_dir = memory_dir_for(agent_name)
|
|
591
|
+
if card_id
|
|
592
|
+
memory_file = File.join(memory_dir, "card-#{card_id}.md")
|
|
593
|
+
FileUtils.mkdir_p(memory_dir)
|
|
594
|
+
FileUtils.touch(memory_file)
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
lines = []
|
|
598
|
+
lines << "## Resumed Session — New Discord Message"
|
|
599
|
+
lines << ""
|
|
600
|
+
lines << "This is a continuation of your previous session in this thread."
|
|
601
|
+
lines << "All prior context, instructions, and your previous work are still in this conversation."
|
|
602
|
+
lines << ""
|
|
603
|
+
lines << "### New Message from #{discord_user}"
|
|
604
|
+
lines << ""
|
|
605
|
+
lines << message_body
|
|
606
|
+
lines << ""
|
|
607
|
+
lines << "---"
|
|
608
|
+
lines << "**IMPORTANT: Write your response to `#{response_file}`. Do NOT reply via stdout.**"
|
|
609
|
+
lines << "All your previous instructions still apply (memory, persona, one message per session, etc.)."
|
|
610
|
+
|
|
611
|
+
lines.join("\n")
|
|
612
|
+
end
|
data/lib/brainiac/version.rb
CHANGED
data/receiver.rb
CHANGED
|
@@ -433,6 +433,19 @@ get "/api/agents" do
|
|
|
433
433
|
{ default: AI_AGENT_NAME, agents: discover_kiro_agents, all_known: all_agent_names.to_a, roster: agent_roster }.to_json
|
|
434
434
|
end
|
|
435
435
|
|
|
436
|
+
get "/api/roles" do
|
|
437
|
+
content_type :json
|
|
438
|
+
roles = []
|
|
439
|
+
if Dir.exist?(ROLES_DIR)
|
|
440
|
+
Dir.glob(File.join(ROLES_DIR, "*.md")).each do |f|
|
|
441
|
+
name = File.basename(f, ".md")
|
|
442
|
+
agents = AGENT_REGISTRY.select { |_, e| e.is_a?(Hash) && Array(e["role"]).include?(name) }.map { |k, e| e["fizzy_name"] || k.capitalize }
|
|
443
|
+
roles << { name: name, agents: agents }
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
{ roles: roles, dir: ROLES_DIR }.to_json
|
|
447
|
+
end
|
|
448
|
+
|
|
436
449
|
get "/api/users" do
|
|
437
450
|
content_type :json
|
|
438
451
|
reload_user_registry!
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"binary": "grok",
|
|
3
|
+
"default_args": "--always-approve",
|
|
4
|
+
"agent_flag": null,
|
|
5
|
+
"model_flag": "--model",
|
|
6
|
+
"effort_flag": "--effort",
|
|
7
|
+
"prompt_mode": "flag",
|
|
8
|
+
"prompt_flag": "--prompt-file",
|
|
9
|
+
"resume_flag": "-c",
|
|
10
|
+
"models": {
|
|
11
|
+
"build": "grok-build",
|
|
12
|
+
"composer": "grok-composer-2.5-fast",
|
|
13
|
+
"auto": "grok-composer-2.5-fast"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"binary": "kiro-cli",
|
|
3
|
+
"default_args": "chat --trust-all-tools --no-interactive --trust-tools execute_bash,fs_write,fs_read,code,grep,glob,web_search,web_fetch,use_subagent,use_aws",
|
|
4
|
+
"agent_flag": "--agent",
|
|
5
|
+
"model_flag": "--model",
|
|
6
|
+
"effort_flag": "--effort",
|
|
7
|
+
"prompt_mode": "stdin",
|
|
8
|
+
"resume_flag": "--resume",
|
|
9
|
+
"models": {
|
|
10
|
+
"opus": "claude-opus-4.6",
|
|
11
|
+
"sonnet": "claude-sonnet-4.6",
|
|
12
|
+
"haiku": "claude-haiku-4.5"
|
|
13
|
+
},
|
|
14
|
+
"efforts": ["low", "medium", "high", "xhigh", "max"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Role: Code Reviewer
|
|
2
|
+
|
|
3
|
+
Your primary responsibility is reviewing code changes for correctness, maintainability, and adherence to project conventions. You do NOT make code changes yourself.
|
|
4
|
+
|
|
5
|
+
## Primary Responsibilities
|
|
6
|
+
|
|
7
|
+
- **Correctness**: Verify logic is sound and handles edge cases
|
|
8
|
+
- **Style**: Ensure code follows project conventions and patterns
|
|
9
|
+
- **Architecture**: Flag design issues or unnecessary complexity
|
|
10
|
+
- **Security**: Identify potential vulnerabilities
|
|
11
|
+
- **Performance**: Note obvious performance concerns
|
|
12
|
+
|
|
13
|
+
## Review Approach
|
|
14
|
+
|
|
15
|
+
1. **Read-only**: You review and comment but do not modify code
|
|
16
|
+
2. **Be specific**: Point to exact lines and explain why something is a concern
|
|
17
|
+
3. **Be constructive**: Suggest alternatives, don't just criticize
|
|
18
|
+
4. **Prioritize**: Distinguish blocking issues from nitpicks
|
|
19
|
+
5. **Acknowledge good work**: Call out clever solutions or clean patterns
|
|
20
|
+
|
|
21
|
+
## What to Look For
|
|
22
|
+
|
|
23
|
+
- Off-by-one errors, nil handling, race conditions
|
|
24
|
+
- Missing error handling or unhelpful error messages
|
|
25
|
+
- Unnecessary coupling between components
|
|
26
|
+
- Magic numbers or unclear naming
|
|
27
|
+
- Breaking changes to public APIs
|
|
28
|
+
- Missing or outdated documentation
|