zillacore 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +126 -0
- data/README.md +1166 -0
- data/Rakefile +12 -0
- data/bin/zillacore +1521 -0
- data/certs/stowzilla.pem +26 -0
- data/docs/waybar-config.md +96 -0
- data/lib/user_registry.rb +159 -0
- data/lib/zillacore/agents.rb +203 -0
- data/lib/zillacore/brain.rb +197 -0
- data/lib/zillacore/card_index.rb +389 -0
- data/lib/zillacore/config.rb +263 -0
- data/lib/zillacore/cron.rb +629 -0
- data/lib/zillacore/deployments.rb +258 -0
- data/lib/zillacore/handlers/discord.rb +1643 -0
- data/lib/zillacore/handlers/fizzy.rb +1249 -0
- data/lib/zillacore/handlers/github.rb +598 -0
- data/lib/zillacore/handlers/zoho.rb +487 -0
- data/lib/zillacore/helpers.rb +760 -0
- data/lib/zillacore/planning.rb +237 -0
- data/lib/zillacore/prompts.rb +620 -0
- data/lib/zillacore/sessions.rb +282 -0
- data/lib/zillacore/skills.rb +276 -0
- data/lib/zillacore/users.rb +76 -0
- data/lib/zillacore/version.rb +6 -0
- data/lib/zillacore/zoho_mail_api.rb +109 -0
- data/lib/zillacore.rb +10 -0
- data/monitor/daemon.rb +99 -0
- data/monitor/deploy-env-macos.rb +131 -0
- data/monitor/menubar.rb +295 -0
- data/monitor/open-action.sh +15 -0
- data/monitor/setup-menubar.rb +78 -0
- data/monitor/setup-waybar-deploy-envs.rb +121 -0
- data/monitor/setup-waybar-deployments.rb +96 -0
- data/monitor/setup-waybar-module.rb +113 -0
- data/monitor/setup-xbar-plugin.rb +35 -0
- data/monitor/view-logs-macos.rb +210 -0
- data/monitor/view-logs-rofi.rb +194 -0
- data/monitor/view-logs.rb +119 -0
- data/monitor/waybar-config-updater.rb +56 -0
- data/monitor/waybar-deploy-env.rb +206 -0
- data/monitor/waybar-deployments.rb +239 -0
- data/monitor/waybar.rb +146 -0
- data/monitor/xbar.3s.rb +179 -0
- data/receiver.rb +956 -0
- data/templates/agents.json.example +10 -0
- data/templates/discord.json.example +17 -0
- data/templates/fizzy.json.example +24 -0
- data/templates/github.json.example +4 -0
- data/templates/testflight.json.example +8 -0
- data/templates/users.json.example +121 -0
- data/templates/zoho.json.example +27 -0
- data/views/dashboard.erb +437 -0
- data/zillacore.gemspec +30 -0
- data.tar.gz.sig +2 -0
- metadata +235 -0
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Planning mode: interactive requirement gathering and task breakdown.
|
|
4
|
+
#
|
|
5
|
+
# Triggered by [plan] tag in Discord or 'plan' tag in Fizzy cards.
|
|
6
|
+
# Agent asks clarifying questions, logs Q&A to memory, generates a plan
|
|
7
|
+
# markdown file, and creates Fizzy steps for each task.
|
|
8
|
+
|
|
9
|
+
PLANS_DIR = File.join(ZILLACORE_DIR, "plans")
|
|
10
|
+
FileUtils.mkdir_p(PLANS_DIR)
|
|
11
|
+
|
|
12
|
+
# Detect if a message/card should trigger planning mode.
|
|
13
|
+
# Returns: { mode: :planning, card_id: '...', card_number: 123 } or nil
|
|
14
|
+
def detect_planning_mode(text:, tags: [], card_internal_id: nil, card_number: nil)
|
|
15
|
+
# Discord: [plan] anywhere in message
|
|
16
|
+
# Fizzy: 'plan' tag on card
|
|
17
|
+
has_plan_tag = text.match?(/\[plan\]/i) || tags.any? { |t| (t.is_a?(Hash) ? t["name"] : t).to_s.downcase == "plan" }
|
|
18
|
+
return nil unless has_plan_tag
|
|
19
|
+
|
|
20
|
+
{
|
|
21
|
+
mode: :planning,
|
|
22
|
+
card_id: card_internal_id || "discord-#{Time.now.to_i}",
|
|
23
|
+
card_number: card_number
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Check if planning is complete for a given card.
|
|
28
|
+
# Planning is complete when:
|
|
29
|
+
# 1. A plan file exists at PLANS_DIR/card-{id}-plan.md
|
|
30
|
+
# 2. Memory file indicates planning_complete: true
|
|
31
|
+
def planning_complete?(card_id, agent_name)
|
|
32
|
+
memory_file = File.join(memory_dir_for(agent_name), "card-#{card_id}.md")
|
|
33
|
+
return false unless File.exist?(memory_file)
|
|
34
|
+
|
|
35
|
+
memory_content = File.read(memory_file)
|
|
36
|
+
memory_content.match?(/planning_complete:\s*true/i)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Generate plan markdown from memory Q&A and create Fizzy steps.
|
|
40
|
+
# This is called when the agent determines it has enough information.
|
|
41
|
+
def finalize_plan(card_id:, card_number:, agent_name:, project_key:, repo_path:)
|
|
42
|
+
memory_file = File.join(memory_dir_for(agent_name), "card-#{card_id}.md")
|
|
43
|
+
unless File.exist?(memory_file)
|
|
44
|
+
LOG.error "[Planning] Cannot finalize plan — no memory file found for card #{card_id}"
|
|
45
|
+
return { success: false, error: "No memory file found" }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
memory_content = File.read(memory_file)
|
|
49
|
+
|
|
50
|
+
# Extract Q&A from memory (agents should format it consistently)
|
|
51
|
+
# Expected format in memory:
|
|
52
|
+
# ## Planning Q&A
|
|
53
|
+
# Q: What's the goal?
|
|
54
|
+
# A: Build a feature that does X
|
|
55
|
+
# Q: Should it support Y?
|
|
56
|
+
# A: Yes, and also Z
|
|
57
|
+
|
|
58
|
+
plan_file = File.join(PLANS_DIR, "card-#{card_id}-plan.md")
|
|
59
|
+
|
|
60
|
+
# The agent should have already written the plan to the file during its session.
|
|
61
|
+
# This function just validates it exists and creates Fizzy steps.
|
|
62
|
+
unless File.exist?(plan_file)
|
|
63
|
+
LOG.error "[Planning] Plan file not found at #{plan_file}"
|
|
64
|
+
return { success: false, error: "Plan file not generated by agent" }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
plan_content = File.read(plan_file)
|
|
68
|
+
|
|
69
|
+
# Parse tasks from plan markdown.
|
|
70
|
+
# Expected format:
|
|
71
|
+
# ## Task Breakdown
|
|
72
|
+
# ### Task 1: Title
|
|
73
|
+
# ### Task 2: Title
|
|
74
|
+
tasks = []
|
|
75
|
+
plan_content.scan(/^###\s+Task\s+\d+:\s+(.+)$/i) do |match|
|
|
76
|
+
tasks << match[0].strip
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
if tasks.empty?
|
|
80
|
+
LOG.warn "[Planning] No tasks found in plan file #{plan_file}"
|
|
81
|
+
return { success: false, error: "No tasks found in plan" }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Create Fizzy steps for each task
|
|
85
|
+
if card_number
|
|
86
|
+
LOG.info "[Planning] Creating #{tasks.size} Fizzy steps for card ##{card_number}"
|
|
87
|
+
tasks.each do |task_title|
|
|
88
|
+
run_cmd("fizzy", "step", "create", "--card", card_number.to_s, "--content", task_title,
|
|
89
|
+
chdir: repo_path, env: fizzy_env_for(agent_name))
|
|
90
|
+
LOG.info "[Planning] Created step: #{task_title}"
|
|
91
|
+
rescue StandardError => e
|
|
92
|
+
LOG.error "[Planning] Failed to create step '#{task_title}': #{e.message}"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Mark planning as complete in memory
|
|
97
|
+
updated_memory = memory_content.sub(/planning_complete:\s*false/i, "planning_complete: true")
|
|
98
|
+
updated_memory += "\n\nplanning_complete: true\n" unless updated_memory.include?("planning_complete: true")
|
|
99
|
+
File.write(memory_file, updated_memory)
|
|
100
|
+
|
|
101
|
+
LOG.info "[Planning] Plan finalized for card #{card_id}: #{plan_file}"
|
|
102
|
+
{ success: true, plan_file: plan_file, tasks: tasks }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Planning mode prompt — prepended to the core prompt.
|
|
106
|
+
PROMPT_PLANNING_MODE = <<~PROMPT
|
|
107
|
+
## Planning Mode (ACTIVE)
|
|
108
|
+
|
|
109
|
+
You are in **planning mode**. Your job is to gather requirements and break down the work into actionable tasks.
|
|
110
|
+
|
|
111
|
+
### Your Role
|
|
112
|
+
- Ask clarifying questions to understand the problem, constraints, and desired outcome
|
|
113
|
+
- Continue asking until you have a clear picture (don't rush to a plan)
|
|
114
|
+
- Understand user intent naturally — "go ahead", "that's enough", "proceed" all mean the same thing
|
|
115
|
+
- When you have enough information OR the user signals they're ready, generate the plan
|
|
116
|
+
|
|
117
|
+
### Question Guidelines
|
|
118
|
+
- Ask specific, focused questions (not generic "anything else?")
|
|
119
|
+
- Build on previous answers — reference what you've learned
|
|
120
|
+
- Prioritize questions that would significantly change the approach
|
|
121
|
+
- If you're 90% confident, proceed. If you're 60% confident, ask.
|
|
122
|
+
|
|
123
|
+
### When to Stop Asking
|
|
124
|
+
The user will signal they're ready in natural language:
|
|
125
|
+
- "go ahead", "proceed", "that's enough", "looks good", "yeah do it"
|
|
126
|
+
- "I think you have enough", "start working", "make the plan"
|
|
127
|
+
|
|
128
|
+
You should also stop if:
|
|
129
|
+
- You've asked 5+ questions and have a clear understanding
|
|
130
|
+
- The user is getting impatient or frustrated
|
|
131
|
+
- The remaining unknowns are minor details you can decide yourself
|
|
132
|
+
|
|
133
|
+
### Generating the Plan
|
|
134
|
+
When ready, create a plan file at `{{PLAN_FILE}}` with this structure:
|
|
135
|
+
|
|
136
|
+
```markdown
|
|
137
|
+
# Feature: [Title]
|
|
138
|
+
|
|
139
|
+
## Problem Statement
|
|
140
|
+
[What we're solving and why]
|
|
141
|
+
|
|
142
|
+
## Requirements
|
|
143
|
+
- Requirement 1
|
|
144
|
+
- Requirement 2
|
|
145
|
+
- Requirement 3
|
|
146
|
+
|
|
147
|
+
## Approach
|
|
148
|
+
[High-level strategy and key decisions]
|
|
149
|
+
|
|
150
|
+
## Task Breakdown
|
|
151
|
+
### Task 1: [Clear, actionable title]
|
|
152
|
+
- **Objective**: [What this task accomplishes]
|
|
153
|
+
- **Approach**: [How to implement it]
|
|
154
|
+
- **Demo**: [What "done" looks like]
|
|
155
|
+
|
|
156
|
+
### Task 2: [Clear, actionable title]
|
|
157
|
+
- **Objective**: [What this task accomplishes]
|
|
158
|
+
- **Approach**: [How to implement it]
|
|
159
|
+
- **Demo**: [What "done" looks like]
|
|
160
|
+
|
|
161
|
+
[Continue for all tasks...]
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Memory Management
|
|
165
|
+
Log every question and answer to your memory file in this format:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
## Planning Q&A
|
|
169
|
+
Q: [Your question]
|
|
170
|
+
A: [User's answer]
|
|
171
|
+
|
|
172
|
+
Q: [Next question]
|
|
173
|
+
A: [User's answer]
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Also track:
|
|
177
|
+
- `planning_complete: false` (update to `true` when plan is generated)
|
|
178
|
+
- Key decisions and constraints discovered
|
|
179
|
+
- Any blockers or unknowns that remain
|
|
180
|
+
|
|
181
|
+
### After Planning
|
|
182
|
+
Once you've written the plan file:
|
|
183
|
+
1. Update memory with `planning_complete: true`
|
|
184
|
+
2. Post a comment summarizing the plan and linking to the file
|
|
185
|
+
3. The system will automatically create Fizzy steps from your task breakdown
|
|
186
|
+
|
|
187
|
+
### Important
|
|
188
|
+
- You are READ-ONLY during planning — no code changes, no commits
|
|
189
|
+
- Focus on understanding the problem, not solving it yet
|
|
190
|
+
- The plan should be detailed enough that any agent could execute it
|
|
191
|
+
- Task titles should be clear and actionable (they become Fizzy step names)
|
|
192
|
+
|
|
193
|
+
PROMPT
|
|
194
|
+
|
|
195
|
+
# Render planning mode prompt with appropriate channel rules.
|
|
196
|
+
def render_planning_prompt(situation_template, vars = {}, brain_context: "", card_context: "", agent_name: AI_AGENT_NAME, channel: :fizzy,
|
|
197
|
+
board_key: nil)
|
|
198
|
+
result = ""
|
|
199
|
+
result += "#{brain_context}\n" unless brain_context.empty?
|
|
200
|
+
result += card_context unless card_context.empty?
|
|
201
|
+
result += PROMPT_CORE
|
|
202
|
+
|
|
203
|
+
# Add planning mode instructions BEFORE channel rules
|
|
204
|
+
plan_file = File.join(PLANS_DIR, "card-#{vars["CARD_ID"]}-plan.md")
|
|
205
|
+
planning_vars = vars.merge("PLAN_FILE" => plan_file)
|
|
206
|
+
result += PROMPT_PLANNING_MODE.gsub("{{PLAN_FILE}}", plan_file)
|
|
207
|
+
|
|
208
|
+
result += CHANNEL_PROMPTS.fetch(channel, PROMPT_FIZZY_CHANNEL)
|
|
209
|
+
result += situation_template
|
|
210
|
+
result += PROMPT_REFLECTION
|
|
211
|
+
|
|
212
|
+
planning_vars["KNOWLEDGE_DIR"] ||= KNOWLEDGE_DIR
|
|
213
|
+
planning_vars["MEMORY_DIR"] ||= memory_dir_for(agent_name)
|
|
214
|
+
planning_vars["PERSONA_DIR"] ||= persona_dir_for(agent_name)
|
|
215
|
+
planning_vars["PERSONA_COLLECTION"] ||= persona_collection_for(agent_name)
|
|
216
|
+
planning_vars["AGENT_NAME"] ||= agent_name
|
|
217
|
+
|
|
218
|
+
# Populate column IDs from board config, falling back to defaults
|
|
219
|
+
DEFAULT_COLUMN_IDS.each do |col_name, default_id|
|
|
220
|
+
var_name = "#{col_name.upcase}_COLUMN_ID"
|
|
221
|
+
planning_vars[var_name] ||= (board_key && board_column_id(board_key, col_name)) || default_id
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Touch memory file if CARD_ID is present — ensures file exists before agent tries to read it
|
|
225
|
+
if vars["CARD_ID"]
|
|
226
|
+
memory_file = File.join(planning_vars["MEMORY_DIR"], "card-#{vars["CARD_ID"]}.md")
|
|
227
|
+
FileUtils.mkdir_p(planning_vars["MEMORY_DIR"])
|
|
228
|
+
FileUtils.touch(memory_file)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
roster = agent_roster
|
|
232
|
+
roster_lines = roster.map { |_key, display| " - @#{display}" }.join("\n")
|
|
233
|
+
planning_vars["AGENT_ROSTER"] ||= roster_lines
|
|
234
|
+
|
|
235
|
+
planning_vars.each { |key, val| result.gsub!("{{#{key}}}", val.to_s) }
|
|
236
|
+
result
|
|
237
|
+
end
|