vibe-forge 0.3.12 → 0.8.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.
- package/.claude/commands/clear-attention.md +63 -63
- package/.claude/commands/compact-context.md +52 -0
- package/.claude/commands/configure-vcs.md +102 -0
- package/.claude/commands/forge.md +218 -171
- package/.claude/commands/need-help.md +77 -77
- package/.claude/commands/update-status.md +64 -64
- package/.claude/commands/worker-loop.md +106 -106
- package/.claude/hooks/worker-loop.js +217 -0
- package/.claude/scripts/setup-worker-loop.sh +45 -45
- package/.claude/settings.json +89 -0
- package/LICENSE +21 -21
- package/README.md +253 -230
- package/agents/aegis/personality.md +303 -269
- package/agents/anvil/personality.md +278 -211
- package/agents/architect/personality.md +260 -0
- package/agents/crucible/personality.md +362 -285
- package/agents/crucible-x/personality.md +210 -0
- package/agents/ember/personality.md +293 -245
- package/agents/flux/personality.md +248 -0
- package/agents/furnace/personality.md +342 -262
- package/agents/herald/personality.md +249 -247
- package/agents/loki/personality.md +108 -0
- package/agents/oracle/personality.md +284 -0
- package/agents/pixel/personality.md +140 -0
- package/agents/planning-hub/personality.md +473 -251
- package/agents/scribe/personality.md +253 -231
- package/agents/slag/personality.md +268 -0
- package/agents/temper/personality.md +270 -0
- package/bin/cli.js +372 -325
- package/bin/dashboard/api/agents.js +333 -0
- package/bin/dashboard/api/dispatch.js +507 -0
- package/bin/dashboard/api/tasks.js +416 -0
- package/bin/dashboard/public/assets/index-BpHfsx1r.js +2 -0
- package/bin/dashboard/public/assets/index-QODv4Zn9.css +1 -0
- package/bin/dashboard/public/index.html +14 -0
- package/bin/dashboard/server.js +645 -0
- package/bin/forge-daemon.sh +477 -775
- package/bin/forge-setup.sh +661 -532
- package/bin/forge-spawn.sh +164 -159
- package/bin/forge.cmd +83 -83
- package/bin/forge.sh +566 -393
- package/bin/lib/agents.sh +177 -177
- package/bin/lib/check-aliases.js +50 -0
- package/bin/lib/colors.sh +44 -44
- package/bin/lib/config.sh +347 -271
- package/bin/lib/constants.sh +241 -171
- package/bin/lib/daemon/budgets.sh +107 -0
- package/bin/lib/daemon/dependencies.sh +146 -0
- package/bin/lib/daemon/display.sh +128 -0
- package/bin/lib/daemon/notifications.sh +273 -0
- package/bin/lib/daemon/routing.sh +93 -0
- package/bin/lib/daemon/state.sh +163 -0
- package/bin/lib/daemon/sync.sh +103 -0
- package/bin/lib/database.sh +357 -224
- package/bin/lib/frontmatter.js +106 -0
- package/bin/lib/heimdall-setup.js +113 -0
- package/bin/lib/heimdall.js +265 -0
- package/bin/lib/json.sh +264 -0
- package/bin/lib/terminal.js +452 -0
- package/bin/lib/util.sh +126 -0
- package/bin/lib/vcs.js +349 -0
- package/config/agent-manifest.yaml +237 -230
- package/config/agents.json +207 -85
- package/config/task-template.md +159 -87
- package/config/task-types.yaml +111 -106
- package/config/templates/handoff-template.md +40 -0
- package/context/agent-overrides/README.md +41 -0
- package/context/architecture.md +42 -0
- package/context/modern-conventions.md +129 -129
- package/context/project-context-template.md +122 -122
- package/docs/agents.md +473 -0
- package/docs/architecture.md +194 -0
- package/docs/commands.md +451 -0
- package/docs/security.md +195 -144
- package/package.json +77 -48
- package/.claude/hooks/worker-loop.sh +0 -141
- package/.claude/settings.local.json +0 -29
- package/agents/forge-master/capabilities.md +0 -144
- package/agents/forge-master/context-template.md +0 -128
- package/agents/forge-master/personality.md +0 -138
- package/agents/sentinel/personality.md +0 -194
- package/context/forge-state.yaml +0 -19
- package/docs/TODO.md +0 -176
- package/docs/npm-publishing.md +0 -95
- package/tasks/review/task-001.md +0 -78
package/bin/lib/constants.sh
CHANGED
|
@@ -1,171 +1,241 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# Vibe Forge - Shared Constants
|
|
4
|
-
# Source this file in other scripts: source "$SCRIPT_DIR/lib/constants.sh"
|
|
5
|
-
#
|
|
6
|
-
# NOTE: Agent configuration should be loaded from config/agents.json via
|
|
7
|
-
# load_agents_from_json() in config.sh. The hardcoded values below are
|
|
8
|
-
# fallback defaults for when JSON loading is not possible (e.g., tests).
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
|
|
36
|
-
#
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
["
|
|
89
|
-
["
|
|
90
|
-
|
|
91
|
-
["
|
|
92
|
-
|
|
93
|
-
["
|
|
94
|
-
["
|
|
95
|
-
["
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
["
|
|
99
|
-
["
|
|
100
|
-
["
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
["
|
|
104
|
-
["
|
|
105
|
-
["
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
["
|
|
111
|
-
["
|
|
112
|
-
["
|
|
113
|
-
|
|
114
|
-
["scribe"]="
|
|
115
|
-
["
|
|
116
|
-
["
|
|
117
|
-
["
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
["
|
|
125
|
-
["
|
|
126
|
-
["
|
|
127
|
-
["
|
|
128
|
-
["
|
|
129
|
-
|
|
130
|
-
["aegis"]="
|
|
131
|
-
["
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
#
|
|
135
|
-
|
|
136
|
-
["
|
|
137
|
-
["
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
["
|
|
141
|
-
["
|
|
142
|
-
["
|
|
143
|
-
["
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
["
|
|
150
|
-
["
|
|
151
|
-
|
|
152
|
-
["
|
|
153
|
-
["
|
|
154
|
-
["
|
|
155
|
-
["
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
["anvil"]="
|
|
163
|
-
["furnace"]="
|
|
164
|
-
["crucible"]="
|
|
165
|
-
["
|
|
166
|
-
["
|
|
167
|
-
["
|
|
168
|
-
["
|
|
169
|
-
["
|
|
170
|
-
["
|
|
171
|
-
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Vibe Forge - Shared Constants
|
|
4
|
+
# Source this file in other scripts: source "$SCRIPT_DIR/lib/constants.sh"
|
|
5
|
+
#
|
|
6
|
+
# NOTE: Agent configuration should be loaded from config/agents.json via
|
|
7
|
+
# load_agents_from_json() in config.sh. The hardcoded values below are
|
|
8
|
+
# fallback defaults for when JSON loading is not possible (e.g., tests).
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
# =============================================================================
|
|
12
|
+
# Exit Codes
|
|
13
|
+
# =============================================================================
|
|
14
|
+
# Standardized exit codes for consistent error handling across scripts
|
|
15
|
+
# Based on BSD sysexits.h conventions with project-specific additions
|
|
16
|
+
|
|
17
|
+
EXIT_SUCCESS=0 # Successful execution
|
|
18
|
+
EXIT_GENERAL_ERROR=1 # Generic/unspecified error
|
|
19
|
+
EXIT_CONFIG_ERROR=2 # Configuration file missing/invalid
|
|
20
|
+
EXIT_DEPENDENCY_MISSING=3 # Required dependency not found (Claude Code, Git Bash, etc.)
|
|
21
|
+
EXIT_INVALID_ARGUMENT=4 # Invalid argument (unknown agent, unknown command)
|
|
22
|
+
EXIT_RUNTIME_ERROR=5 # Runtime error (daemon conflict, spawn failed, etc.)
|
|
23
|
+
|
|
24
|
+
# Timing
|
|
25
|
+
POLL_INTERVAL=2 # seconds between daemon task checks
|
|
26
|
+
NOTIFICATION_DEBOUNCE=1 # seconds to avoid notification spam
|
|
27
|
+
|
|
28
|
+
# Daemon Configuration
|
|
29
|
+
MAX_LOG_SIZE=1048576 # 1MB - log rotation threshold
|
|
30
|
+
MAX_NOTIFY_ENTRIES=1000 # Maximum notification log entries before trimming
|
|
31
|
+
STALE_STATUS_THRESHOLD=300 # 5 minutes - when worker status is considered stale
|
|
32
|
+
MAINTENANCE_INTERVAL=100 # Daemon iterations between maintenance runs
|
|
33
|
+
STALE_CLEANUP_MINUTES=30 # Remove agent status older than this (minutes)
|
|
34
|
+
HISTORY_PRUNE_DAYS=7 # Remove status history older than this (days)
|
|
35
|
+
|
|
36
|
+
# Directory structure (relative to FORGE_ROOT)
|
|
37
|
+
CONFIG_DIR=".forge"
|
|
38
|
+
TASKS_DIR="tasks"
|
|
39
|
+
TASKS_PENDING="$TASKS_DIR/pending"
|
|
40
|
+
TASKS_IN_PROGRESS="$TASKS_DIR/in-progress"
|
|
41
|
+
TASKS_COMPLETED="$TASKS_DIR/completed"
|
|
42
|
+
TASKS_REVIEW="$TASKS_DIR/review"
|
|
43
|
+
TASKS_APPROVED="$TASKS_DIR/approved"
|
|
44
|
+
TASKS_NEEDS_CHANGES="$TASKS_DIR/needs-changes"
|
|
45
|
+
TASKS_MERGED="$TASKS_DIR/merged"
|
|
46
|
+
TASKS_ATTENTION="$TASKS_DIR/attention"
|
|
47
|
+
CONTEXT_DIR="context"
|
|
48
|
+
AGENT_STATUS_DIR="$CONTEXT_DIR/agent-status"
|
|
49
|
+
AGENTS_DIR="agents"
|
|
50
|
+
|
|
51
|
+
# Config files
|
|
52
|
+
AGENTS_CONFIG="config/agents.json"
|
|
53
|
+
|
|
54
|
+
# =============================================================================
|
|
55
|
+
# Agent Configuration (Fallback Defaults)
|
|
56
|
+
# =============================================================================
|
|
57
|
+
# These values are overwritten when load_agents_from_json() is called.
|
|
58
|
+
# They exist as fallback for contexts where JSON loading isn't available.
|
|
59
|
+
|
|
60
|
+
# Flag to track if agents were loaded from JSON
|
|
61
|
+
AGENTS_LOADED="${AGENTS_LOADED:-false}"
|
|
62
|
+
|
|
63
|
+
# Valid agent names (whitelist for security)
|
|
64
|
+
# These are the ONLY valid canonical agent names
|
|
65
|
+
# NOTE: Must stay in sync with config/agents.json (which is the source of truth)
|
|
66
|
+
# Last sync: 2026-04-03 (added loki, renamed sentinel→temper)
|
|
67
|
+
VALID_AGENTS=(
|
|
68
|
+
"hub"
|
|
69
|
+
"temper"
|
|
70
|
+
"anvil"
|
|
71
|
+
"furnace"
|
|
72
|
+
"crucible"
|
|
73
|
+
"scribe"
|
|
74
|
+
"herald"
|
|
75
|
+
"ember"
|
|
76
|
+
"aegis"
|
|
77
|
+
"architect"
|
|
78
|
+
"pixel"
|
|
79
|
+
"oracle"
|
|
80
|
+
"loki"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Agent aliases map (for resolve_agent)
|
|
84
|
+
# Format: alias=canonical
|
|
85
|
+
# NOTE: Must stay in sync with config/agents.json (which is the source of truth)
|
|
86
|
+
declare -A AGENT_ALIASES=(
|
|
87
|
+
# Hub aliases (Chief Orchestrator)
|
|
88
|
+
["hub"]="hub"
|
|
89
|
+
["planning"]="hub"
|
|
90
|
+
["master"]="hub"
|
|
91
|
+
["forge-master"]="hub"
|
|
92
|
+
# Temper aliases (Code Reviewer)
|
|
93
|
+
["temper"]="temper"
|
|
94
|
+
["review"]="temper"
|
|
95
|
+
["reviewer"]="temper"
|
|
96
|
+
["cr"]="temper"
|
|
97
|
+
# Anvil aliases (Frontend)
|
|
98
|
+
["anvil"]="anvil"
|
|
99
|
+
["frontend"]="anvil"
|
|
100
|
+
["ui"]="anvil"
|
|
101
|
+
["fe"]="anvil"
|
|
102
|
+
# Furnace aliases (Backend)
|
|
103
|
+
["furnace"]="furnace"
|
|
104
|
+
["backend"]="furnace"
|
|
105
|
+
["api"]="furnace"
|
|
106
|
+
["be"]="furnace"
|
|
107
|
+
# Crucible aliases (Testing)
|
|
108
|
+
["crucible"]="crucible"
|
|
109
|
+
["test"]="crucible"
|
|
110
|
+
["testing"]="crucible"
|
|
111
|
+
["qa"]="crucible"
|
|
112
|
+
["tester"]="crucible"
|
|
113
|
+
# Scribe aliases (Documentation)
|
|
114
|
+
["scribe"]="scribe"
|
|
115
|
+
["docs"]="scribe"
|
|
116
|
+
["documentation"]="scribe"
|
|
117
|
+
["doc"]="scribe"
|
|
118
|
+
# Herald aliases (Release)
|
|
119
|
+
["herald"]="herald"
|
|
120
|
+
["release"]="herald"
|
|
121
|
+
["deploy"]="herald"
|
|
122
|
+
["deployment"]="herald"
|
|
123
|
+
# Ember aliases (DevOps)
|
|
124
|
+
["ember"]="ember"
|
|
125
|
+
["devops"]="ember"
|
|
126
|
+
["ops"]="ember"
|
|
127
|
+
["infra"]="ember"
|
|
128
|
+
["infrastructure"]="ember"
|
|
129
|
+
# Aegis aliases (Security)
|
|
130
|
+
["aegis"]="aegis"
|
|
131
|
+
["security"]="aegis"
|
|
132
|
+
["sec"]="aegis"
|
|
133
|
+
["appsec"]="aegis"
|
|
134
|
+
# Architect aliases (System Design)
|
|
135
|
+
["architect"]="architect"
|
|
136
|
+
["arch"]="architect"
|
|
137
|
+
["sage"]="architect"
|
|
138
|
+
# Pixel aliases (UX Design)
|
|
139
|
+
# NOTE: "design" intentionally removed — ambiguous with architect's technical design domain
|
|
140
|
+
["pixel"]="pixel"
|
|
141
|
+
["ux"]="pixel"
|
|
142
|
+
["ui-design"]="pixel"
|
|
143
|
+
["user-experience"]="pixel"
|
|
144
|
+
# Oracle aliases (Product / Requirements)
|
|
145
|
+
["oracle"]="oracle"
|
|
146
|
+
["product"]="oracle"
|
|
147
|
+
["po"]="oracle"
|
|
148
|
+
["requirements"]="oracle"
|
|
149
|
+
["req"]="oracle"
|
|
150
|
+
["analyst"]="oracle"
|
|
151
|
+
# Loki aliases (Lateral Thinker)
|
|
152
|
+
["loki"]="loki"
|
|
153
|
+
["trickster"]="loki"
|
|
154
|
+
["contrarian"]="loki"
|
|
155
|
+
["brainstorm"]="loki"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Agent display names
|
|
159
|
+
declare -A AGENT_DISPLAY_NAMES=(
|
|
160
|
+
["hub"]="Planning Hub"
|
|
161
|
+
["temper"]="Temper"
|
|
162
|
+
["anvil"]="Anvil"
|
|
163
|
+
["furnace"]="Furnace"
|
|
164
|
+
["crucible"]="Crucible"
|
|
165
|
+
["scribe"]="Scribe"
|
|
166
|
+
["herald"]="Herald"
|
|
167
|
+
["ember"]="Ember"
|
|
168
|
+
["aegis"]="Aegis"
|
|
169
|
+
["architect"]="Architect"
|
|
170
|
+
["pixel"]="Pixel"
|
|
171
|
+
["oracle"]="Oracle"
|
|
172
|
+
["loki"]="Loki"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Agent roles
|
|
176
|
+
declare -A AGENT_ROLES=(
|
|
177
|
+
["hub"]="Chief Orchestrator"
|
|
178
|
+
["temper"]="Code Reviewer"
|
|
179
|
+
["anvil"]="Frontend Developer"
|
|
180
|
+
["furnace"]="Backend Developer"
|
|
181
|
+
["crucible"]="Tester / QA"
|
|
182
|
+
["scribe"]="Documentation Specialist"
|
|
183
|
+
["herald"]="Release Manager"
|
|
184
|
+
["ember"]="DevOps Engineer"
|
|
185
|
+
["aegis"]="Security Specialist"
|
|
186
|
+
["architect"]="System Architect"
|
|
187
|
+
["pixel"]="UX Designer"
|
|
188
|
+
["oracle"]="Product Owner / Requirements Analyst"
|
|
189
|
+
["loki"]="Lateral Thinker"
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# Agent personality files (relative to FORGE_ROOT)
|
|
193
|
+
declare -A AGENT_PERSONALITY_FILES=(
|
|
194
|
+
["hub"]="agents/planning-hub/personality.md"
|
|
195
|
+
["temper"]="agents/temper/personality.md"
|
|
196
|
+
["anvil"]="agents/anvil/personality.md"
|
|
197
|
+
["furnace"]="agents/furnace/personality.md"
|
|
198
|
+
["crucible"]="agents/crucible/personality.md"
|
|
199
|
+
["scribe"]="agents/scribe/personality.md"
|
|
200
|
+
["herald"]="agents/herald/personality.md"
|
|
201
|
+
["ember"]="agents/ember/personality.md"
|
|
202
|
+
["aegis"]="agents/aegis/personality.md"
|
|
203
|
+
["architect"]="agents/architect/personality.md"
|
|
204
|
+
["pixel"]="agents/pixel/personality.md"
|
|
205
|
+
["oracle"]="agents/oracle/personality.md"
|
|
206
|
+
["loki"]="agents/loki/personality.md"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Agent icons
|
|
210
|
+
declare -A AGENT_ICONS=(
|
|
211
|
+
["hub"]="⚒️"
|
|
212
|
+
["temper"]="⚖️"
|
|
213
|
+
["anvil"]="🔨"
|
|
214
|
+
["furnace"]="🔥"
|
|
215
|
+
["crucible"]="🧪"
|
|
216
|
+
["scribe"]="📜"
|
|
217
|
+
["herald"]="📯"
|
|
218
|
+
["ember"]="⚙️"
|
|
219
|
+
["aegis"]="🔒"
|
|
220
|
+
["architect"]="🏛️"
|
|
221
|
+
["pixel"]="🎨"
|
|
222
|
+
["oracle"]="🔮"
|
|
223
|
+
["loki"]="🎭"
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Agent tab colors for Windows Terminal (hex format)
|
|
227
|
+
declare -A AGENT_TAB_COLORS=(
|
|
228
|
+
["hub"]="#FF6B35"
|
|
229
|
+
["temper"]="#8B5CF6"
|
|
230
|
+
["anvil"]="#3B82F6"
|
|
231
|
+
["furnace"]="#EF4444"
|
|
232
|
+
["crucible"]="#10B981"
|
|
233
|
+
["scribe"]="#F59E0B"
|
|
234
|
+
["herald"]="#EC4899"
|
|
235
|
+
["ember"]="#F97316"
|
|
236
|
+
["aegis"]="#06B6D4"
|
|
237
|
+
["architect"]="#6366F1"
|
|
238
|
+
["pixel"]="#D946EF"
|
|
239
|
+
["oracle"]="#FBBF24"
|
|
240
|
+
["loki"]="#7C3AED"
|
|
241
|
+
)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# bin/lib/daemon/budgets.sh
|
|
4
|
+
#
|
|
5
|
+
# Token budget warnings (T2-F1)
|
|
6
|
+
#
|
|
7
|
+
# Monitors agent session duration as a proxy for token consumption.
|
|
8
|
+
# Claude Code sessions don't expose token counts externally, so we
|
|
9
|
+
# estimate from how long an agent has been continuously "working" on
|
|
10
|
+
# a single task. Agents working beyond the warning threshold get an
|
|
11
|
+
# attention file so the user knows to check on them.
|
|
12
|
+
#
|
|
13
|
+
# Dependencies: database.sh, constants.sh
|
|
14
|
+
# Globals required: FORGE_ROOT, FORGE_DB, TASKS_ATTENTION, LOG_FILE
|
|
15
|
+
|
|
16
|
+
# Prevent double-sourcing
|
|
17
|
+
[[ -n "${_DAEMON_BUDGETS_LOADED:-}" ]] && return 0
|
|
18
|
+
_DAEMON_BUDGETS_LOADED=1
|
|
19
|
+
|
|
20
|
+
# Thresholds (seconds)
|
|
21
|
+
TOKEN_WARN_DURATION=${TOKEN_WARN_DURATION:-5400} # 90 minutes
|
|
22
|
+
TOKEN_URGENT_DURATION=${TOKEN_URGENT_DURATION:-7200} # 2 hours
|
|
23
|
+
|
|
24
|
+
# Track which agents we've already warned about (reset when they go idle)
|
|
25
|
+
declare -A _budget_warned 2>/dev/null || true
|
|
26
|
+
|
|
27
|
+
check_token_budgets() {
|
|
28
|
+
local now_epoch
|
|
29
|
+
now_epoch=$(date +%s)
|
|
30
|
+
|
|
31
|
+
# Query agents currently in "working" status
|
|
32
|
+
local rows
|
|
33
|
+
rows=$(sqlite3 "$FORGE_DB" \
|
|
34
|
+
"SELECT agent, task, updated_at FROM agent_status WHERE status='working';" 2>/dev/null) || return 0
|
|
35
|
+
|
|
36
|
+
[[ -z "$rows" ]] && return 0
|
|
37
|
+
|
|
38
|
+
while IFS='|' read -r agent task updated; do
|
|
39
|
+
[[ -z "$agent" || -z "$updated" ]] && continue
|
|
40
|
+
|
|
41
|
+
# Calculate how long agent has been working
|
|
42
|
+
local updated_epoch duration
|
|
43
|
+
updated_epoch=$(date -d "$updated" +%s 2>/dev/null || echo "0")
|
|
44
|
+
[[ "$updated_epoch" -eq 0 ]] && continue
|
|
45
|
+
duration=$((now_epoch - updated_epoch))
|
|
46
|
+
|
|
47
|
+
# Skip if we already warned for this agent+task combo
|
|
48
|
+
local warn_key="${agent}:${task}"
|
|
49
|
+
[[ "${_budget_warned[$warn_key]:-}" == "1" ]] && continue
|
|
50
|
+
|
|
51
|
+
local urgency=""
|
|
52
|
+
if [[ "$duration" -ge "$TOKEN_URGENT_DURATION" ]]; then
|
|
53
|
+
urgency="urgent"
|
|
54
|
+
elif [[ "$duration" -ge "$TOKEN_WARN_DURATION" ]]; then
|
|
55
|
+
urgency="normal"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
[[ -z "$urgency" ]] && continue
|
|
59
|
+
|
|
60
|
+
# Create attention file
|
|
61
|
+
local attention_dir="$FORGE_ROOT/$TASKS_ATTENTION"
|
|
62
|
+
mkdir -p "$attention_dir"
|
|
63
|
+
|
|
64
|
+
local duration_min=$((duration / 60))
|
|
65
|
+
local attention_file="$attention_dir/token-budget-${agent}.md"
|
|
66
|
+
|
|
67
|
+
# Don't overwrite if attention file already exists
|
|
68
|
+
if [[ -f "$attention_file" ]]; then
|
|
69
|
+
_budget_warned[$warn_key]=1
|
|
70
|
+
continue
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
cat > "$attention_file" << BUDGET
|
|
74
|
+
---
|
|
75
|
+
agent: $agent
|
|
76
|
+
created: $(date -Iseconds)
|
|
77
|
+
type: budget-warning
|
|
78
|
+
urgency: $urgency
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Issue
|
|
82
|
+
|
|
83
|
+
Token budget warning: **$agent** has been working on \`$task\` for ${duration_min} minutes.
|
|
84
|
+
|
|
85
|
+
Long-running sessions risk context degradation. Consider:
|
|
86
|
+
- Check if the agent is stuck or looping
|
|
87
|
+
- Use \`/compact-context\` in the agent's session
|
|
88
|
+
- Split remaining work into a new task for a fresh session
|
|
89
|
+
BUDGET
|
|
90
|
+
|
|
91
|
+
_budget_warned[$warn_key]=1
|
|
92
|
+
echo "[$(date -Iseconds)] TOKEN BUDGET: $agent working ${duration_min}m on $task ($urgency)" >> "$LOG_FILE"
|
|
93
|
+
done <<< "$rows"
|
|
94
|
+
|
|
95
|
+
# Clear warnings for agents that are no longer working
|
|
96
|
+
for key in "${!_budget_warned[@]}"; do
|
|
97
|
+
local agent_part="${key%%:*}"
|
|
98
|
+
local still_working
|
|
99
|
+
still_working=$(sqlite3 "$FORGE_DB" \
|
|
100
|
+
"SELECT COUNT(*) FROM agent_status WHERE agent='$agent_part' AND status='working';" 2>/dev/null || echo "0")
|
|
101
|
+
if [[ "$still_working" -eq 0 ]]; then
|
|
102
|
+
unset "_budget_warned[$key]"
|
|
103
|
+
# Clean up attention file if agent finished
|
|
104
|
+
rm -f "$FORGE_ROOT/$TASKS_ATTENTION/token-budget-${agent_part}.md" 2>/dev/null
|
|
105
|
+
fi
|
|
106
|
+
done
|
|
107
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# bin/lib/daemon/dependencies.sh
|
|
4
|
+
#
|
|
5
|
+
# Task dependency resolution (T2-H2)
|
|
6
|
+
#
|
|
7
|
+
# Checks blocked_by fields in pending task frontmatter. Tasks whose
|
|
8
|
+
# blockers are all resolved (in completed/ or merged/) are eligible
|
|
9
|
+
# for dispatch. Tasks with unresolved blockers are surfaced in the
|
|
10
|
+
# state file so Planning Hub and the dashboard can show them.
|
|
11
|
+
#
|
|
12
|
+
# Dependencies: frontmatter.js, constants.sh
|
|
13
|
+
# Globals required: FORGE_ROOT, TASKS_PENDING, TASKS_COMPLETED,
|
|
14
|
+
# TASKS_MERGED, LOG_FILE
|
|
15
|
+
|
|
16
|
+
# Prevent double-sourcing
|
|
17
|
+
[[ -n "${_DAEMON_DEPENDENCIES_LOADED:-}" ]] && return 0
|
|
18
|
+
_DAEMON_DEPENDENCIES_LOADED=1
|
|
19
|
+
|
|
20
|
+
# Node.js frontmatter helper
|
|
21
|
+
FRONTMATTER_JS="${FRONTMATTER_JS:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/frontmatter.js}"
|
|
22
|
+
|
|
23
|
+
# Count of currently blocked tasks (used by state.sh)
|
|
24
|
+
BLOCKED_TASK_COUNT=0
|
|
25
|
+
|
|
26
|
+
# Check if a task ID exists in completed or merged directories
|
|
27
|
+
_is_task_resolved() {
|
|
28
|
+
local task_id="$1"
|
|
29
|
+
local completed_dir="$FORGE_ROOT/$TASKS_COMPLETED"
|
|
30
|
+
local merged_dir="$FORGE_ROOT/$TASKS_MERGED"
|
|
31
|
+
|
|
32
|
+
# Check completed/
|
|
33
|
+
for f in "$completed_dir"/*.md; do
|
|
34
|
+
[[ -f "$f" ]] || continue
|
|
35
|
+
if grep -q "^id: *${task_id}$" "$f" 2>/dev/null; then
|
|
36
|
+
return 0
|
|
37
|
+
fi
|
|
38
|
+
done
|
|
39
|
+
|
|
40
|
+
# Check merged/
|
|
41
|
+
for f in "$merged_dir"/*.md; do
|
|
42
|
+
[[ -f "$f" ]] || continue
|
|
43
|
+
if grep -q "^id: *${task_id}$" "$f" 2>/dev/null; then
|
|
44
|
+
return 0
|
|
45
|
+
fi
|
|
46
|
+
done
|
|
47
|
+
|
|
48
|
+
return 1
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Check all pending tasks for unresolved blockers.
|
|
52
|
+
# Sets BLOCKED_TASK_COUNT and outputs YAML for blocked tasks.
|
|
53
|
+
check_task_dependencies() {
|
|
54
|
+
BLOCKED_TASK_COUNT=0
|
|
55
|
+
local pending_dir="$FORGE_ROOT/$TASKS_PENDING"
|
|
56
|
+
|
|
57
|
+
[[ -d "$pending_dir" ]] || return 0
|
|
58
|
+
|
|
59
|
+
for task_file in "$pending_dir"/*.md; do
|
|
60
|
+
[[ -f "$task_file" && ! -L "$task_file" ]] || continue
|
|
61
|
+
|
|
62
|
+
# Extract blocked_by field (comma or space separated list of task IDs)
|
|
63
|
+
local blocked_by
|
|
64
|
+
blocked_by=$(node "$FRONTMATTER_JS" "$task_file" "blocked_by" 2>/dev/null | sed -n 's/^blocked_by=//p')
|
|
65
|
+
|
|
66
|
+
[[ -z "$blocked_by" ]] && continue
|
|
67
|
+
|
|
68
|
+
# Parse the list (handle comma-separated, space-separated, or YAML array)
|
|
69
|
+
local blockers
|
|
70
|
+
blockers=$(echo "$blocked_by" | tr ',[]' ' ' | tr -s ' ')
|
|
71
|
+
|
|
72
|
+
local unresolved=""
|
|
73
|
+
local all_resolved=true
|
|
74
|
+
|
|
75
|
+
for blocker_id in $blockers; do
|
|
76
|
+
# Skip empty tokens
|
|
77
|
+
[[ -z "$blocker_id" ]] && continue
|
|
78
|
+
|
|
79
|
+
if ! _is_task_resolved "$blocker_id"; then
|
|
80
|
+
all_resolved=false
|
|
81
|
+
if [[ -n "$unresolved" ]]; then
|
|
82
|
+
unresolved="$unresolved, $blocker_id"
|
|
83
|
+
else
|
|
84
|
+
unresolved="$blocker_id"
|
|
85
|
+
fi
|
|
86
|
+
fi
|
|
87
|
+
done
|
|
88
|
+
|
|
89
|
+
if [[ "$all_resolved" == "false" ]]; then
|
|
90
|
+
((BLOCKED_TASK_COUNT++)) || true
|
|
91
|
+
local task_id
|
|
92
|
+
task_id=$(node "$FRONTMATTER_JS" "$task_file" "id" 2>/dev/null | sed -n 's/^id=//p')
|
|
93
|
+
task_id="${task_id:-$(basename "$task_file" .md)}"
|
|
94
|
+
echo "[$(date -Iseconds)] BLOCKED: $task_id waiting on: $unresolved" >> "$LOG_FILE"
|
|
95
|
+
fi
|
|
96
|
+
done
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Build YAML section for blocked tasks (called by state.sh update_state)
|
|
100
|
+
build_blocked_tasks() {
|
|
101
|
+
local pending_dir="$FORGE_ROOT/$TASKS_PENDING"
|
|
102
|
+
|
|
103
|
+
[[ -d "$pending_dir" ]] || return 0
|
|
104
|
+
|
|
105
|
+
local has_blocked=false
|
|
106
|
+
|
|
107
|
+
for task_file in "$pending_dir"/*.md; do
|
|
108
|
+
[[ -f "$task_file" && ! -L "$task_file" ]] || continue
|
|
109
|
+
|
|
110
|
+
local blocked_by
|
|
111
|
+
blocked_by=$(node "$FRONTMATTER_JS" "$task_file" "blocked_by" 2>/dev/null | sed -n 's/^blocked_by=//p')
|
|
112
|
+
|
|
113
|
+
[[ -z "$blocked_by" ]] && continue
|
|
114
|
+
|
|
115
|
+
local blockers unresolved_list=""
|
|
116
|
+
blockers=$(echo "$blocked_by" | tr ',[]' ' ' | tr -s ' ')
|
|
117
|
+
|
|
118
|
+
for blocker_id in $blockers; do
|
|
119
|
+
[[ -z "$blocker_id" ]] && continue
|
|
120
|
+
if ! _is_task_resolved "$blocker_id"; then
|
|
121
|
+
if [[ -n "$unresolved_list" ]]; then
|
|
122
|
+
unresolved_list="$unresolved_list, $blocker_id"
|
|
123
|
+
else
|
|
124
|
+
unresolved_list="$blocker_id"
|
|
125
|
+
fi
|
|
126
|
+
fi
|
|
127
|
+
done
|
|
128
|
+
|
|
129
|
+
if [[ -n "$unresolved_list" ]]; then
|
|
130
|
+
if [[ "$has_blocked" == "false" ]]; then
|
|
131
|
+
echo "blocked_tasks:"
|
|
132
|
+
has_blocked=true
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
local task_id title
|
|
136
|
+
task_id=$(node "$FRONTMATTER_JS" "$task_file" "id" 2>/dev/null | sed -n 's/^id=//p')
|
|
137
|
+
title=$(node "$FRONTMATTER_JS" "$task_file" "title" 2>/dev/null | sed -n 's/^title=//p')
|
|
138
|
+
task_id="${task_id:-$(basename "$task_file" .md)}"
|
|
139
|
+
title="${title:-Untitled}"
|
|
140
|
+
|
|
141
|
+
printf ' - id: %s\n' "$task_id"
|
|
142
|
+
printf ' title: "%s"\n' "$title"
|
|
143
|
+
printf ' waiting_on: "%s"\n' "$unresolved_list"
|
|
144
|
+
fi
|
|
145
|
+
done
|
|
146
|
+
}
|