ai-consultants 2.10.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/ai-consultants:consult.md +94 -0
- package/.claude/commands/ai-consultants:debate.md +103 -0
- package/.claude/commands/ai-consultants:help.md +30 -0
- package/CLAUDE.md +839 -0
- package/LICENSE +21 -0
- package/README.md +758 -0
- package/SKILL.md +156 -0
- package/bin/ai-consultants +96 -0
- package/docs/COST_RATES.md +172 -0
- package/docs/JSON_SCHEMA.md +305 -0
- package/docs/SETUP.md +879 -0
- package/docs/SMART_ROUTING.md +219 -0
- package/docs/cost_rates.json +143 -0
- package/package.json +47 -0
- package/references/configuration.md +141 -0
- package/references/details.md +60 -0
- package/scripts/build_context.sh +562 -0
- package/scripts/classify_question.sh +148 -0
- package/scripts/config.sh +753 -0
- package/scripts/configure.sh +1093 -0
- package/scripts/consult_all.sh +934 -0
- package/scripts/debate_round.sh +421 -0
- package/scripts/doctor.sh +642 -0
- package/scripts/followup.sh +197 -0
- package/scripts/install.sh +319 -0
- package/scripts/lib/api.sh +640 -0
- package/scripts/lib/api_query.sh +228 -0
- package/scripts/lib/cache.sh +338 -0
- package/scripts/lib/chunking.sh +880 -0
- package/scripts/lib/code_optimizer.sh +671 -0
- package/scripts/lib/common.sh +1088 -0
- package/scripts/lib/costs.sh +674 -0
- package/scripts/lib/personas.sh +372 -0
- package/scripts/lib/progress.sh +251 -0
- package/scripts/lib/reflection.sh +447 -0
- package/scripts/lib/routing.sh +566 -0
- package/scripts/lib/schema.json +165 -0
- package/scripts/lib/session.sh +327 -0
- package/scripts/lib/symbol_map.sh +445 -0
- package/scripts/lib/voting.sh +495 -0
- package/scripts/peer_review.sh +460 -0
- package/scripts/preflight_check.sh +452 -0
- package/scripts/query_aider.sh +83 -0
- package/scripts/query_amp.sh +67 -0
- package/scripts/query_claude.sh +99 -0
- package/scripts/query_codex.sh +92 -0
- package/scripts/query_cursor.sh +72 -0
- package/scripts/query_deepseek.sh +17 -0
- package/scripts/query_gemini.sh +85 -0
- package/scripts/query_glm.sh +17 -0
- package/scripts/query_grok.sh +17 -0
- package/scripts/query_kilo.sh +140 -0
- package/scripts/query_kimi.sh +67 -0
- package/scripts/query_minimax.sh +17 -0
- package/scripts/query_mistral.sh +97 -0
- package/scripts/query_ollama.sh +328 -0
- package/scripts/query_qwen3.sh +93 -0
- package/scripts/setup_wizard.sh +367 -0
- package/scripts/skillport-install.sh +196 -0
- package/scripts/synthesize.sh +332 -0
- package/templates/consultation_report.md +323 -0
- package/templates/synthesis_prompt.md +120 -0
|
@@ -0,0 +1,934 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# consult_all.sh - AI Consultants - Main Orchestrator
|
|
3
|
+
#
|
|
4
|
+
# Complete workflow for multi-model AI consultation with:
|
|
5
|
+
# - Specialized personas for each consultant
|
|
6
|
+
# - Confidence scoring and weighted voting
|
|
7
|
+
# - Auto-synthesis of responses with multiple strategies
|
|
8
|
+
# - Optional Multi-Agent Debate (MAD)
|
|
9
|
+
# - Smart routing based on question category
|
|
10
|
+
# - Configuration presets for quick setup
|
|
11
|
+
# - Session management for follow-up
|
|
12
|
+
# - Cost tracking
|
|
13
|
+
# - Panic button mode for uncertainty detection
|
|
14
|
+
#
|
|
15
|
+
# Usage: ./consult_all.sh [options] "Your question" [file1] [file2] ...
|
|
16
|
+
#
|
|
17
|
+
# Options:
|
|
18
|
+
# --preset <name> Use a configuration preset (minimal, balanced, thorough, high-stakes, local, security, cost-capped)
|
|
19
|
+
# --strategy <name> Synthesis strategy (majority, risk_averse, security_first, cost_capped, compare_only)
|
|
20
|
+
# --list-presets List available presets
|
|
21
|
+
# --list-strategies List available synthesis strategies
|
|
22
|
+
# --help Show this help message
|
|
23
|
+
#
|
|
24
|
+
# Environment variables:
|
|
25
|
+
# ENABLE_SYNTHESIS=true Enable automatic synthesis
|
|
26
|
+
# ENABLE_DEBATE=true Enable multi-round debate
|
|
27
|
+
# DEBATE_ROUNDS=2 Number of debate rounds
|
|
28
|
+
# ENABLE_SMART_ROUTING=true Select consultants based on question
|
|
29
|
+
# ENABLE_PANIC_MODE=auto Panic mode (auto, always, never)
|
|
30
|
+
|
|
31
|
+
set -euo pipefail
|
|
32
|
+
|
|
33
|
+
# --- Initial Setup ---
|
|
34
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
35
|
+
source "$SCRIPT_DIR/lib/common.sh"
|
|
36
|
+
source "$SCRIPT_DIR/lib/session.sh"
|
|
37
|
+
source "$SCRIPT_DIR/lib/progress.sh"
|
|
38
|
+
source "$SCRIPT_DIR/lib/costs.sh"
|
|
39
|
+
source "$SCRIPT_DIR/lib/voting.sh"
|
|
40
|
+
source "$SCRIPT_DIR/lib/routing.sh"
|
|
41
|
+
source "$SCRIPT_DIR/lib/cache.sh"
|
|
42
|
+
|
|
43
|
+
# --- Custom API Agent Discovery ---
|
|
44
|
+
# Discovers custom API agents from environment variables
|
|
45
|
+
# Convention: ENABLE_AGENTNAME=true with AGENTNAME_API_URL set
|
|
46
|
+
_discover_custom_api_agents() {
|
|
47
|
+
while IFS='=' read -r var value; do
|
|
48
|
+
[[ "$var" != ENABLE_* ]] && continue
|
|
49
|
+
[[ "$value" != "true" ]] && continue
|
|
50
|
+
|
|
51
|
+
local agent_upper="${var#ENABLE_}"
|
|
52
|
+
|
|
53
|
+
# Skip known agents (uses helper from common.sh)
|
|
54
|
+
is_known_agent "$agent_upper" && continue
|
|
55
|
+
|
|
56
|
+
# Check if it has an API URL configured (indicates it's an API agent)
|
|
57
|
+
local url_var="${agent_upper}_API_URL"
|
|
58
|
+
[[ -z "${!url_var:-}" ]] && continue
|
|
59
|
+
|
|
60
|
+
# Add custom API agent with proper case
|
|
61
|
+
local agent_name
|
|
62
|
+
agent_name=$(to_title "$agent_upper")
|
|
63
|
+
SELECTED_CONSULTANTS+=("$agent_name")
|
|
64
|
+
log_debug "Discovered custom API agent: $agent_name"
|
|
65
|
+
done < <(env)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# --- Show usage help ---
|
|
69
|
+
show_help() {
|
|
70
|
+
echo "AI Consultants v${AI_CONSULTANTS_VERSION:-2.10.0} - Multi-Model AI Consultation"
|
|
71
|
+
cat << 'EOF'
|
|
72
|
+
|
|
73
|
+
Usage: ./consult_all.sh [options] "Your question" [file1] [file2] ...
|
|
74
|
+
|
|
75
|
+
Options:
|
|
76
|
+
--preset <name> Use a configuration preset:
|
|
77
|
+
minimal - 2 models, fast and cheap
|
|
78
|
+
balanced - 4 models, good coverage
|
|
79
|
+
thorough - 5 models, comprehensive
|
|
80
|
+
high-stakes - All models + debate
|
|
81
|
+
max_quality - All + premium models + debate + reflection
|
|
82
|
+
medium - 4 models + standard models + light debate
|
|
83
|
+
fast - 2 models + economy models, no debate
|
|
84
|
+
local - Ollama only, full privacy
|
|
85
|
+
security - Security-focused + debate
|
|
86
|
+
cost-capped - Budget-conscious options
|
|
87
|
+
|
|
88
|
+
--strategy <name> Synthesis strategy:
|
|
89
|
+
majority - Simple voting, most common wins
|
|
90
|
+
risk_averse - Weight conservative responses higher
|
|
91
|
+
security_first - Prioritize security-focused insights
|
|
92
|
+
cost_capped - Prefer cheaper consultant opinions
|
|
93
|
+
compare_only - No recommendation, just comparison
|
|
94
|
+
|
|
95
|
+
--list-presets List all available presets
|
|
96
|
+
--list-strategies List all synthesis strategies
|
|
97
|
+
--help, -h Show this help message
|
|
98
|
+
|
|
99
|
+
Examples:
|
|
100
|
+
./consult_all.sh "How to optimize this SQL query?"
|
|
101
|
+
./consult_all.sh --preset minimal "Quick question about Python lists"
|
|
102
|
+
./consult_all.sh --preset high-stakes --strategy risk_averse "Critical architecture decision"
|
|
103
|
+
./consult_all.sh "Review this code" src/main.py src/utils.py
|
|
104
|
+
|
|
105
|
+
Environment Variables:
|
|
106
|
+
ENABLE_SYNTHESIS=true Enable automatic synthesis
|
|
107
|
+
ENABLE_DEBATE=true Enable multi-round debate
|
|
108
|
+
DEBATE_ROUNDS=2 Number of debate rounds
|
|
109
|
+
ENABLE_SMART_ROUTING=true Select consultants based on question category
|
|
110
|
+
ENABLE_PANIC_MODE=auto Panic mode: auto, always, never
|
|
111
|
+
MAX_SESSION_COST=1.00 Maximum budget per session ($)
|
|
112
|
+
|
|
113
|
+
For more information, run: ./doctor.sh
|
|
114
|
+
EOF
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# --- List synthesis strategies ---
|
|
118
|
+
list_strategies() {
|
|
119
|
+
cat << 'EOF'
|
|
120
|
+
Available synthesis strategies:
|
|
121
|
+
|
|
122
|
+
majority Simple voting - most common answer wins (default)
|
|
123
|
+
risk_averse Weight conservative responses higher, prefer safety
|
|
124
|
+
security_first Prioritize security-focused consultants and insights
|
|
125
|
+
cost_capped Prefer opinions from cheaper consultants within budget
|
|
126
|
+
compare_only No recommendation, just structured comparison table
|
|
127
|
+
|
|
128
|
+
Usage: ./consult_all.sh --strategy <name> "Your question"
|
|
129
|
+
EOF
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
# --- Argument Parsing ---
|
|
133
|
+
PRESET=""
|
|
134
|
+
SYNTHESIS_STRATEGY=""
|
|
135
|
+
QUERY=""
|
|
136
|
+
FILES=()
|
|
137
|
+
|
|
138
|
+
while [[ $# -gt 0 ]]; do
|
|
139
|
+
case "$1" in
|
|
140
|
+
--preset)
|
|
141
|
+
if [[ -n "${2:-}" ]]; then
|
|
142
|
+
PRESET="$2"
|
|
143
|
+
shift 2
|
|
144
|
+
else
|
|
145
|
+
log_error "--preset requires a value"
|
|
146
|
+
exit 1
|
|
147
|
+
fi
|
|
148
|
+
;;
|
|
149
|
+
--strategy)
|
|
150
|
+
if [[ -n "${2:-}" ]]; then
|
|
151
|
+
SYNTHESIS_STRATEGY="$2"
|
|
152
|
+
shift 2
|
|
153
|
+
else
|
|
154
|
+
log_error "--strategy requires a value"
|
|
155
|
+
exit 1
|
|
156
|
+
fi
|
|
157
|
+
;;
|
|
158
|
+
--list-presets)
|
|
159
|
+
list_presets
|
|
160
|
+
exit 0
|
|
161
|
+
;;
|
|
162
|
+
--list-strategies)
|
|
163
|
+
list_strategies
|
|
164
|
+
exit 0
|
|
165
|
+
;;
|
|
166
|
+
--help|-h)
|
|
167
|
+
show_help
|
|
168
|
+
exit 0
|
|
169
|
+
;;
|
|
170
|
+
-*)
|
|
171
|
+
log_error "Unknown option: $1"
|
|
172
|
+
show_help
|
|
173
|
+
exit 1
|
|
174
|
+
;;
|
|
175
|
+
*)
|
|
176
|
+
if [[ -z "$QUERY" ]]; then
|
|
177
|
+
QUERY="$1"
|
|
178
|
+
else
|
|
179
|
+
FILES+=("$1")
|
|
180
|
+
fi
|
|
181
|
+
shift
|
|
182
|
+
;;
|
|
183
|
+
esac
|
|
184
|
+
done
|
|
185
|
+
|
|
186
|
+
# --- Apply Preset (CLI flag or default) ---
|
|
187
|
+
# Use CLI flag if provided, otherwise fall back to DEFAULT_PRESET
|
|
188
|
+
PRESET="${PRESET:-${DEFAULT_PRESET:-}}"
|
|
189
|
+
if [[ -n "$PRESET" ]]; then
|
|
190
|
+
if ! apply_preset "$PRESET"; then
|
|
191
|
+
exit 1
|
|
192
|
+
fi
|
|
193
|
+
log_info "Applied preset: $PRESET"
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
# Set synthesis strategy: CLI flag > environment > DEFAULT_STRATEGY > fallback
|
|
197
|
+
SYNTHESIS_STRATEGY="${SYNTHESIS_STRATEGY:-${DEFAULT_STRATEGY:-majority}}"
|
|
198
|
+
|
|
199
|
+
# Export synthesis strategy for use by synthesize.sh
|
|
200
|
+
export SYNTHESIS_STRATEGY
|
|
201
|
+
|
|
202
|
+
# --- Input Validation ---
|
|
203
|
+
if [[ -z "$QUERY" ]]; then
|
|
204
|
+
log_error "Usage: $0 [options] \"Your question\" [file1] [file2] ..."
|
|
205
|
+
log_info "Example: $0 \"How to optimize this function?\" src/utils.py"
|
|
206
|
+
log_info "Run with --help for more options"
|
|
207
|
+
exit 1
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
# --- Header ---
|
|
211
|
+
echo "" >&2
|
|
212
|
+
echo "╔══════════════════════════════════════════════════════════════╗" >&2
|
|
213
|
+
echo "║ AI Consultants v${AI_CONSULTANTS_VERSION} - Expert Panel ║" >&2
|
|
214
|
+
echo "╚══════════════════════════════════════════════════════════════╝" >&2
|
|
215
|
+
echo "" >&2
|
|
216
|
+
|
|
217
|
+
# --- Pre-flight Check (optional) ---
|
|
218
|
+
if [[ "$ENABLE_PREFLIGHT" == "true" ]]; then
|
|
219
|
+
log_info "Running pre-flight check..."
|
|
220
|
+
preflight_args=()
|
|
221
|
+
[[ "$PREFLIGHT_QUICK" == "true" ]] && preflight_args+=("--quick")
|
|
222
|
+
if ! "$SCRIPT_DIR/preflight_check.sh" ${preflight_args[@]+"${preflight_args[@]}"} > /dev/null 2>&1; then
|
|
223
|
+
log_error "Pre-flight check failed. Run ./preflight_check.sh for details."
|
|
224
|
+
exit 1
|
|
225
|
+
fi
|
|
226
|
+
log_success "Pre-flight check passed"
|
|
227
|
+
echo "" >&2
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
# --- Question Classification ---
|
|
231
|
+
QUESTION_CATEGORY="GENERAL"
|
|
232
|
+
if [[ "$ENABLE_CLASSIFICATION" == "true" ]]; then
|
|
233
|
+
log_info "Classifying question..."
|
|
234
|
+
QUESTION_CATEGORY=$("$SCRIPT_DIR/classify_question.sh" "$QUERY" 2>/dev/null || echo "GENERAL")
|
|
235
|
+
log_info "Category: $QUESTION_CATEGORY"
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
# --- Create Output Directory (with secure permissions) ---
|
|
239
|
+
# Add random suffix to prevent predictable directory names (security improvement)
|
|
240
|
+
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
241
|
+
RANDOM_SUFFIX="${RANDOM}${$}"
|
|
242
|
+
OUTPUT_DIR="${DEFAULT_OUTPUT_DIR_BASE}/${TIMESTAMP}_${RANDOM_SUFFIX}"
|
|
243
|
+
# Create base directory with restricted permissions (owner only)
|
|
244
|
+
mkdir -p "$DEFAULT_OUTPUT_DIR_BASE"
|
|
245
|
+
chmod 700 "$DEFAULT_OUTPUT_DIR_BASE"
|
|
246
|
+
# Create session-specific directory
|
|
247
|
+
mkdir -p "$OUTPUT_DIR"
|
|
248
|
+
chmod 700 "$OUTPUT_DIR"
|
|
249
|
+
|
|
250
|
+
log_info "Output: $OUTPUT_DIR"
|
|
251
|
+
log_info "Question: ${QUERY:0:80}$([ ${#QUERY} -gt 80 ] && echo '...')"
|
|
252
|
+
if [[ ${#FILES[@]} -gt 0 ]]; then
|
|
253
|
+
log_info "Files: ${FILES[*]}"
|
|
254
|
+
fi
|
|
255
|
+
echo "" >&2
|
|
256
|
+
|
|
257
|
+
# --- Automatic Context Creation ---
|
|
258
|
+
log_info "Building automatic context..."
|
|
259
|
+
CONTEXT_FILE="$OUTPUT_DIR/context.md"
|
|
260
|
+
if [[ ${#FILES[@]} -gt 0 ]]; then
|
|
261
|
+
"$SCRIPT_DIR/build_context.sh" "$CONTEXT_FILE" "$QUERY" "${FILES[@]}" > /dev/null
|
|
262
|
+
else
|
|
263
|
+
"$SCRIPT_DIR/build_context.sh" "$CONTEXT_FILE" "$QUERY" > /dev/null
|
|
264
|
+
fi
|
|
265
|
+
log_success "Context created: $CONTEXT_FILE ($(wc -l < "$CONTEXT_FILE" | tr -d ' ') lines)"
|
|
266
|
+
|
|
267
|
+
# --- Cost Estimation (optional) ---
|
|
268
|
+
CONTEXT_SIZE=0
|
|
269
|
+
ESTIMATED_COST=0
|
|
270
|
+
if [[ "$ENABLE_COST_TRACKING" == "true" ]]; then
|
|
271
|
+
CONTEXT_SIZE=$(wc -c < "$CONTEXT_FILE" | tr -d ' ')
|
|
272
|
+
ESTIMATED_COST=$(estimate_consultation_cost 4 "$CONTEXT_SIZE")
|
|
273
|
+
log_info "Estimated cost: $(format_cost "$ESTIMATED_COST")"
|
|
274
|
+
fi
|
|
275
|
+
|
|
276
|
+
# --- Budget Check: Before Round 1 ---
|
|
277
|
+
if is_budget_enabled; then
|
|
278
|
+
log_debug "Budget enforcement enabled: \$${MAX_SESSION_COST} limit"
|
|
279
|
+
if ! enforce_budget 0 "$ESTIMATED_COST" "initial consultation"; then
|
|
280
|
+
log_error "Consultation aborted: estimated cost exceeds budget"
|
|
281
|
+
exit 1
|
|
282
|
+
fi
|
|
283
|
+
fi
|
|
284
|
+
echo "" >&2
|
|
285
|
+
|
|
286
|
+
# --- Consultant Selection ---
|
|
287
|
+
declare -a SELECTED_CONSULTANTS=()
|
|
288
|
+
|
|
289
|
+
if [[ "$ENABLE_SMART_ROUTING" == "true" ]]; then
|
|
290
|
+
log_info "Smart routing for category $QUESTION_CATEGORY..."
|
|
291
|
+
while IFS= read -r c; do
|
|
292
|
+
[[ -n "$c" ]] && SELECTED_CONSULTANTS+=("$c")
|
|
293
|
+
done < <(select_consultants "$QUESTION_CATEGORY" "$MIN_AFFINITY")
|
|
294
|
+
log_info "Selected consultants: ${SELECTED_CONSULTANTS[*]}"
|
|
295
|
+
else
|
|
296
|
+
# Log self-exclusion status for debugging
|
|
297
|
+
log_self_exclusion_status
|
|
298
|
+
|
|
299
|
+
# All enabled consultants - use a compact loop
|
|
300
|
+
# Order matches ALL_CONSULTANTS in config.sh for consistency
|
|
301
|
+
_consultant_map="GEMINI:Gemini CODEX:Codex MISTRAL:Mistral KILO:Kilo CURSOR:Cursor AIDER:Aider AMP:Amp KIMI:Kimi CLAUDE:Claude QWEN3:Qwen3 GLM:GLM GROK:Grok DEEPSEEK:DeepSeek MINIMAX:MiniMax OLLAMA:Ollama"
|
|
302
|
+
for _entry in $_consultant_map; do
|
|
303
|
+
_flag="${_entry%%:*}"
|
|
304
|
+
_name="${_entry#*:}"
|
|
305
|
+
_enable_var="ENABLE_${_flag}"
|
|
306
|
+
|
|
307
|
+
# Skip self (invoking agent shouldn't query itself)
|
|
308
|
+
if should_skip_consultant "$_flag"; then
|
|
309
|
+
log_debug "Skipping $_name (self-exclusion: invoking agent)"
|
|
310
|
+
continue
|
|
311
|
+
fi
|
|
312
|
+
|
|
313
|
+
[[ "${!_enable_var:-false}" == "true" ]] && SELECTED_CONSULTANTS+=("$_name")
|
|
314
|
+
done
|
|
315
|
+
unset _consultant_map _entry _flag _name _enable_var
|
|
316
|
+
|
|
317
|
+
# Discover custom API agents from environment
|
|
318
|
+
_discover_custom_api_agents
|
|
319
|
+
fi
|
|
320
|
+
|
|
321
|
+
if [[ ${#SELECTED_CONSULTANTS[@]} -eq 0 ]]; then
|
|
322
|
+
log_error "No consultant enabled or selected"
|
|
323
|
+
exit 1
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
# Minimum 2 consultants required for meaningful comparison
|
|
327
|
+
if [[ ${#SELECTED_CONSULTANTS[@]} -lt 2 ]]; then
|
|
328
|
+
log_error "At least 2 consultants required for meaningful comparison."
|
|
329
|
+
log_error "Currently enabled: ${#SELECTED_CONSULTANTS[@]} (${SELECTED_CONSULTANTS[*]})"
|
|
330
|
+
echo "" >&2
|
|
331
|
+
log_info "Run ./scripts/setup_wizard.sh to configure additional consultants"
|
|
332
|
+
log_info "Or manually enable more:"
|
|
333
|
+
log_info " ENABLE_GEMINI=true ENABLE_CODEX=true ./scripts/consult_all.sh \"query\""
|
|
334
|
+
exit 1
|
|
335
|
+
fi
|
|
336
|
+
|
|
337
|
+
# --- Refined Cost Estimation (v2.4) ---
|
|
338
|
+
# Now that we know which consultants are selected, recalculate with accurate model rates
|
|
339
|
+
if [[ "$ENABLE_COST_TRACKING" == "true" ]]; then
|
|
340
|
+
CONSULTANT_LIST=$(IFS=','; echo "${SELECTED_CONSULTANTS[*]}")
|
|
341
|
+
ESTIMATED_COST=$(estimate_consultation_cost "${#SELECTED_CONSULTANTS[@]}" "$CONTEXT_SIZE" "$CONSULTANT_LIST")
|
|
342
|
+
log_debug "Refined estimate with ${#SELECTED_CONSULTANTS[@]} consultants: $(format_cost "$ESTIMATED_COST")"
|
|
343
|
+
|
|
344
|
+
# Re-check budget with refined estimate
|
|
345
|
+
if is_budget_enabled; then
|
|
346
|
+
if ! enforce_budget 0 "$ESTIMATED_COST" "refined consultation estimate"; then
|
|
347
|
+
log_error "Consultation aborted: refined estimate exceeds budget"
|
|
348
|
+
exit 1
|
|
349
|
+
fi
|
|
350
|
+
fi
|
|
351
|
+
fi
|
|
352
|
+
|
|
353
|
+
# --- Round 1: Parallel Consultation ---
|
|
354
|
+
log_info "Starting parallel consultation (Round 1)..."
|
|
355
|
+
echo "" >&2
|
|
356
|
+
|
|
357
|
+
declare -a PIDS=()
|
|
358
|
+
declare -a NAMES=()
|
|
359
|
+
declare -a OUTPUT_FILES=()
|
|
360
|
+
|
|
361
|
+
# Initialize progress for each consultant
|
|
362
|
+
for c in "${SELECTED_CONSULTANTS[@]}"; do
|
|
363
|
+
init_progress "$c"
|
|
364
|
+
done
|
|
365
|
+
|
|
366
|
+
# Track cache hits
|
|
367
|
+
declare -a CACHE_HITS=()
|
|
368
|
+
|
|
369
|
+
# Launch consultants in parallel (with cache check)
|
|
370
|
+
for consultant in "${SELECTED_CONSULTANTS[@]}"; do
|
|
371
|
+
consultant_lower=$(to_lower "$consultant")
|
|
372
|
+
local_output_file="$OUTPUT_DIR/${consultant_lower}.json"
|
|
373
|
+
OUTPUT_FILES+=("$local_output_file")
|
|
374
|
+
NAMES+=("$consultant")
|
|
375
|
+
|
|
376
|
+
# Check cache first (v2.3)
|
|
377
|
+
if is_cache_enabled; then
|
|
378
|
+
cached_response=$(check_cache "$QUERY" "$QUESTION_CATEGORY" "$consultant_lower" "$CONTEXT_FILE" 2>/dev/null || echo "")
|
|
379
|
+
if [[ -n "$cached_response" ]]; then
|
|
380
|
+
# Use cached response
|
|
381
|
+
echo "$cached_response" | mark_from_cache > "$local_output_file"
|
|
382
|
+
PIDS+=("cache") # Marker for cache hit
|
|
383
|
+
CACHE_HITS+=("$consultant")
|
|
384
|
+
update_progress "$consultant" 100 "cached"
|
|
385
|
+
log_debug "Cache hit for $consultant"
|
|
386
|
+
continue
|
|
387
|
+
fi
|
|
388
|
+
fi
|
|
389
|
+
|
|
390
|
+
# Convention: query script is query_<lowercase_name>.sh
|
|
391
|
+
query_script="$SCRIPT_DIR/query_${consultant_lower}.sh"
|
|
392
|
+
|
|
393
|
+
if [[ -x "$query_script" ]]; then
|
|
394
|
+
# Apply cost-aware model override if enabled (v2.3)
|
|
395
|
+
if [[ "${ENABLE_COST_AWARE_ROUTING:-false}" == "true" ]] && type get_cost_aware_model &>/dev/null; then
|
|
396
|
+
local_model=$(get_cost_aware_model "$consultant_lower" "${QUERY_COMPLEXITY:-5}" 2>/dev/null || echo "")
|
|
397
|
+
if [[ -n "$local_model" ]]; then
|
|
398
|
+
model_var="$(echo "${consultant_lower}" | tr '[:lower:]' '[:upper:]')_MODEL"
|
|
399
|
+
if [[ ! "$model_var" =~ ^[A-Z0-9_]+_MODEL$ ]]; then
|
|
400
|
+
log_warn "Invalid model variable name: $model_var, skipping cost-aware override"
|
|
401
|
+
continue
|
|
402
|
+
fi
|
|
403
|
+
log_debug "Cost-aware: $consultant using model $local_model"
|
|
404
|
+
(
|
|
405
|
+
apply_launch_stagger
|
|
406
|
+
export "$model_var=$local_model"
|
|
407
|
+
"$query_script" "" "$CONTEXT_FILE" "$local_output_file"
|
|
408
|
+
) > /dev/null 2>&1 &
|
|
409
|
+
PIDS+=($!)
|
|
410
|
+
update_progress "$consultant" 10 "running"
|
|
411
|
+
continue
|
|
412
|
+
fi
|
|
413
|
+
fi
|
|
414
|
+
# Standard launch (inherits current environment models)
|
|
415
|
+
( apply_launch_stagger; "$query_script" "" "$CONTEXT_FILE" "$local_output_file" ) > /dev/null 2>&1 &
|
|
416
|
+
else
|
|
417
|
+
# Fallback: custom API agent via generic API query
|
|
418
|
+
(
|
|
419
|
+
apply_launch_stagger
|
|
420
|
+
source "$SCRIPT_DIR/lib/api_query.sh"
|
|
421
|
+
run_api_consultant "$consultant" "" "$CONTEXT_FILE" "$local_output_file"
|
|
422
|
+
) > /dev/null 2>&1 &
|
|
423
|
+
fi
|
|
424
|
+
|
|
425
|
+
PIDS+=($!)
|
|
426
|
+
update_progress "$consultant" 10 "running"
|
|
427
|
+
done
|
|
428
|
+
|
|
429
|
+
# Log cache status
|
|
430
|
+
if [[ ${#CACHE_HITS[@]} -gt 0 ]]; then
|
|
431
|
+
log_info "Cache hits: ${CACHE_HITS[*]}"
|
|
432
|
+
fi
|
|
433
|
+
|
|
434
|
+
# --- Wait and Collect Results ---
|
|
435
|
+
log_info "Waiting for responses from ${#PIDS[@]} consultants..."
|
|
436
|
+
|
|
437
|
+
declare -a RESULTS=()
|
|
438
|
+
SUCCESS_COUNT=0
|
|
439
|
+
|
|
440
|
+
for i in "${!PIDS[@]}"; do
|
|
441
|
+
pid="${PIDS[$i]}"
|
|
442
|
+
name="${NAMES[$i]}"
|
|
443
|
+
output_file="${OUTPUT_FILES[$i]}"
|
|
444
|
+
name_lower=$(to_lower "$name")
|
|
445
|
+
|
|
446
|
+
# Handle cache hits (pid is "cache" marker)
|
|
447
|
+
if [[ "$pid" == "cache" ]]; then
|
|
448
|
+
if [[ -s "$output_file" ]]; then
|
|
449
|
+
log_success " $name: OK (cached)"
|
|
450
|
+
RESULTS+=("$name:OK")
|
|
451
|
+
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
|
|
452
|
+
else
|
|
453
|
+
log_warn " $name: Cache miss"
|
|
454
|
+
RESULTS+=("$name:EMPTY")
|
|
455
|
+
fi
|
|
456
|
+
continue
|
|
457
|
+
fi
|
|
458
|
+
|
|
459
|
+
if wait "$pid" 2>/dev/null; then
|
|
460
|
+
if [[ -s "$output_file" ]]; then
|
|
461
|
+
log_success " $name: OK ($(wc -c < "$output_file" | tr -d ' ') bytes)"
|
|
462
|
+
RESULTS+=("$name:OK")
|
|
463
|
+
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
|
|
464
|
+
update_progress "$name" 100 "success"
|
|
465
|
+
|
|
466
|
+
# Store in cache (v2.3)
|
|
467
|
+
if is_cache_enabled; then
|
|
468
|
+
response_content=$(cat "$output_file")
|
|
469
|
+
store_cache "$QUERY" "$QUESTION_CATEGORY" "$name_lower" "$response_content" "$CONTEXT_FILE" 2>/dev/null || true
|
|
470
|
+
fi
|
|
471
|
+
else
|
|
472
|
+
log_warn " $name: Empty response"
|
|
473
|
+
RESULTS+=("$name:EMPTY")
|
|
474
|
+
update_progress "$name" 100 "failed"
|
|
475
|
+
fi
|
|
476
|
+
else
|
|
477
|
+
log_error " $name: Failed"
|
|
478
|
+
RESULTS+=("$name:FAILED")
|
|
479
|
+
update_progress "$name" 100 "failed"
|
|
480
|
+
fi
|
|
481
|
+
done
|
|
482
|
+
|
|
483
|
+
echo "" >&2
|
|
484
|
+
|
|
485
|
+
# --- Budget Check: After Round 1 ---
|
|
486
|
+
CURRENT_COST=0
|
|
487
|
+
if [[ "$ENABLE_COST_TRACKING" == "true" && $SUCCESS_COUNT -gt 0 ]]; then
|
|
488
|
+
CURRENT_COST=$(calculate_session_cost "$OUTPUT_DIR")
|
|
489
|
+
|
|
490
|
+
# Check warning threshold
|
|
491
|
+
if check_warning_threshold "$CURRENT_COST"; then
|
|
492
|
+
log_warn "Cost warning: $(format_budget_status "$CURRENT_COST")"
|
|
493
|
+
fi
|
|
494
|
+
|
|
495
|
+
# Budget enforcement check
|
|
496
|
+
if is_budget_enabled; then
|
|
497
|
+
log_debug "Current cost after Round 1: $(format_cost "$CURRENT_COST")"
|
|
498
|
+
fi
|
|
499
|
+
fi
|
|
500
|
+
|
|
501
|
+
# --- Fallback Escalation: Re-query low-confidence with premium models ---
|
|
502
|
+
if is_escalation_enabled && [[ $SUCCESS_COUNT -gt 0 ]]; then
|
|
503
|
+
ESCALATED_COUNT=0
|
|
504
|
+
for consultant in "${SELECTED_CONSULTANTS[@]}"; do
|
|
505
|
+
consultant_lower=$(to_lower "$consultant")
|
|
506
|
+
response_file="$OUTPUT_DIR/${consultant_lower}.json"
|
|
507
|
+
|
|
508
|
+
if needs_escalation "$response_file"; then
|
|
509
|
+
premium_model=$(get_premium_model "$consultant_lower")
|
|
510
|
+
if [[ -n "$premium_model" ]]; then
|
|
511
|
+
original_confidence=$(jq -r '.confidence.score // 5' "$response_file" 2>/dev/null)
|
|
512
|
+
[[ "$original_confidence" =~ ^[0-9]+$ ]] || original_confidence=5
|
|
513
|
+
log_info "Escalating $consultant (confidence: $original_confidence) → premium model: $premium_model"
|
|
514
|
+
|
|
515
|
+
query_script="$SCRIPT_DIR/query_${consultant_lower}.sh"
|
|
516
|
+
if [[ -x "$query_script" ]]; then
|
|
517
|
+
escalation_file="$OUTPUT_DIR/${consultant_lower}_escalated.json"
|
|
518
|
+
model_var="$(echo "${consultant_lower}" | tr '[:lower:]' '[:upper:]')_MODEL"
|
|
519
|
+
if [[ ! "$model_var" =~ ^[A-Z0-9_]+_MODEL$ ]]; then
|
|
520
|
+
log_warn "Invalid model variable name: $model_var, skipping escalation for $consultant"
|
|
521
|
+
continue
|
|
522
|
+
fi
|
|
523
|
+
(
|
|
524
|
+
export "$model_var=$premium_model"
|
|
525
|
+
"$query_script" "" "$CONTEXT_FILE" "$escalation_file"
|
|
526
|
+
) > /dev/null 2>&1
|
|
527
|
+
|
|
528
|
+
# Replace original if escalated response has higher confidence
|
|
529
|
+
if [[ -f "$escalation_file" && -s "$escalation_file" ]]; then
|
|
530
|
+
new_confidence=$(jq -r '.confidence.score // 0' "$escalation_file" 2>/dev/null)
|
|
531
|
+
[[ "$new_confidence" =~ ^[0-9]+$ ]] || new_confidence=0
|
|
532
|
+
if [[ "$new_confidence" -gt "$original_confidence" ]]; then
|
|
533
|
+
cp "$escalation_file" "$response_file"
|
|
534
|
+
log_success " $consultant escalated: confidence $original_confidence → $new_confidence"
|
|
535
|
+
ESCALATED_COUNT=$((ESCALATED_COUNT + 1))
|
|
536
|
+
else
|
|
537
|
+
log_debug " $consultant escalation did not improve confidence"
|
|
538
|
+
fi
|
|
539
|
+
fi
|
|
540
|
+
fi
|
|
541
|
+
fi
|
|
542
|
+
fi
|
|
543
|
+
done
|
|
544
|
+
if [[ $ESCALATED_COUNT -gt 0 ]]; then
|
|
545
|
+
log_info "Escalated $ESCALATED_COUNT consultant(s) to premium models"
|
|
546
|
+
fi
|
|
547
|
+
fi
|
|
548
|
+
|
|
549
|
+
# --- Panic Mode Detection (v2.2) ---
|
|
550
|
+
PANIC_TRIGGERED=false
|
|
551
|
+
if [[ "$ENABLE_PANIC_MODE" != "never" && $SUCCESS_COUNT -gt 1 ]]; then
|
|
552
|
+
if should_trigger_panic "$OUTPUT_DIR"; then
|
|
553
|
+
PANIC_TRIGGERED=true
|
|
554
|
+
PANIC_DIAGNOSIS=$(get_panic_diagnosis "$OUTPUT_DIR")
|
|
555
|
+
|
|
556
|
+
log_warn "PANIC MODE TRIGGERED - Uncertainty detected!"
|
|
557
|
+
log_warn " Diagnosis: $(echo "$PANIC_DIAGNOSIS" | jq -r '.triggers | join(", ")')"
|
|
558
|
+
|
|
559
|
+
# Save diagnosis
|
|
560
|
+
echo "$PANIC_DIAGNOSIS" > "$OUTPUT_DIR/panic_diagnosis.json"
|
|
561
|
+
|
|
562
|
+
# Actions: Enable debate if not already enabled, switch to risk_averse strategy
|
|
563
|
+
if [[ "$ENABLE_DEBATE" != "true" ]]; then
|
|
564
|
+
log_info " Action: Enabling multi-agent debate"
|
|
565
|
+
ENABLE_DEBATE=true
|
|
566
|
+
DEBATE_ROUNDS="${PANIC_EXTRA_DEBATE_ROUNDS:-1}"
|
|
567
|
+
DEBATE_ROUNDS=$((DEBATE_ROUNDS + 1)) # At least one extra round
|
|
568
|
+
else
|
|
569
|
+
# Add extra debate rounds
|
|
570
|
+
EXTRA_ROUNDS="${PANIC_EXTRA_DEBATE_ROUNDS:-1}"
|
|
571
|
+
DEBATE_ROUNDS=$((DEBATE_ROUNDS + EXTRA_ROUNDS))
|
|
572
|
+
log_info " Action: Adding $EXTRA_ROUNDS extra debate round(s) (total: $DEBATE_ROUNDS)"
|
|
573
|
+
fi
|
|
574
|
+
|
|
575
|
+
# Switch to risk_averse synthesis strategy
|
|
576
|
+
if [[ "$SYNTHESIS_STRATEGY" == "majority" ]]; then
|
|
577
|
+
log_info " Action: Switching to risk_averse synthesis strategy"
|
|
578
|
+
SYNTHESIS_STRATEGY="risk_averse"
|
|
579
|
+
export SYNTHESIS_STRATEGY
|
|
580
|
+
fi
|
|
581
|
+
|
|
582
|
+
echo "" >&2
|
|
583
|
+
fi
|
|
584
|
+
fi
|
|
585
|
+
|
|
586
|
+
# --- Round 2+: Multi-Agent Debate (optional) ---
|
|
587
|
+
if [[ "$ENABLE_DEBATE" == "true" && $DEBATE_ROUNDS -gt 1 && $SUCCESS_COUNT -gt 1 ]]; then
|
|
588
|
+
# Budget Check: Before Debate
|
|
589
|
+
if is_budget_enabled; then
|
|
590
|
+
DEBATE_ESTIMATE=$(estimate_phase_cost "debate" "$SUCCESS_COUNT" "$CONTEXT_SIZE")
|
|
591
|
+
DEBATE_ESTIMATE=$(echo "scale=6; $DEBATE_ESTIMATE * $DEBATE_ROUNDS" | bc)
|
|
592
|
+
if ! enforce_budget "$CURRENT_COST" "$DEBATE_ESTIMATE" "debate rounds"; then
|
|
593
|
+
log_warn "Skipping debate due to budget constraints"
|
|
594
|
+
ENABLE_DEBATE=false
|
|
595
|
+
fi
|
|
596
|
+
fi
|
|
597
|
+
|
|
598
|
+
# Proceed with debate if still enabled after budget check
|
|
599
|
+
if [[ "$ENABLE_DEBATE" == "true" ]]; then
|
|
600
|
+
log_info "Starting Multi-Agent Debate (${DEBATE_ROUNDS} total rounds)..."
|
|
601
|
+
|
|
602
|
+
for ((round=2; round<=DEBATE_ROUNDS; round++)); do
|
|
603
|
+
log_info " Round $round..."
|
|
604
|
+
# v2.3: Pass category for mandatory debate check (SECURITY/ARCHITECTURE always debate)
|
|
605
|
+
DEBATE_OUTPUT=$("$SCRIPT_DIR/debate_round.sh" "$OUTPUT_DIR" "$round" "$OUTPUT_DIR/round_$round" "$QUESTION_CATEGORY")
|
|
606
|
+
|
|
607
|
+
# Update responses with debate results
|
|
608
|
+
if [[ -d "$OUTPUT_DIR/round_$round" ]]; then
|
|
609
|
+
for f in "$OUTPUT_DIR/round_$round"/*.json; do
|
|
610
|
+
if [[ -f "$f" && "$f" != *"summary"* ]]; then
|
|
611
|
+
consultant=$(basename "$f" .json)
|
|
612
|
+
# Merge debate data into main response
|
|
613
|
+
if [[ -f "$OUTPUT_DIR/${consultant}.json" ]]; then
|
|
614
|
+
jq -s '.[0] * {debate: .[1].debate}' \
|
|
615
|
+
"$OUTPUT_DIR/${consultant}.json" "$f" \
|
|
616
|
+
> "$OUTPUT_DIR/${consultant}.json.tmp" && \
|
|
617
|
+
mv "$OUTPUT_DIR/${consultant}.json.tmp" "$OUTPUT_DIR/${consultant}.json"
|
|
618
|
+
fi
|
|
619
|
+
fi
|
|
620
|
+
done
|
|
621
|
+
fi
|
|
622
|
+
done
|
|
623
|
+
echo "" >&2
|
|
624
|
+
fi
|
|
625
|
+
fi
|
|
626
|
+
|
|
627
|
+
# --- Voting and Consensus ---
|
|
628
|
+
log_info "Calculating consensus and voting..."
|
|
629
|
+
VOTING_REPORT=$(generate_voting_report "$OUTPUT_DIR")
|
|
630
|
+
echo "$VOTING_REPORT" > "$OUTPUT_DIR/voting.json"
|
|
631
|
+
|
|
632
|
+
CONSENSUS_SCORE=$(echo "$VOTING_REPORT" | jq -r '.voting_report.consensus.score // 50')
|
|
633
|
+
CONSENSUS_LEVEL=$(echo "$VOTING_REPORT" | jq -r '.voting_report.consensus.level // "unknown"')
|
|
634
|
+
log_info " Consensus: ${CONSENSUS_SCORE}% ($CONSENSUS_LEVEL)"
|
|
635
|
+
echo "" >&2
|
|
636
|
+
|
|
637
|
+
# --- Quality Monitoring (v2.3) ---
|
|
638
|
+
# Log optimization metrics for quality tracking
|
|
639
|
+
if [[ "${LOG_LEVEL:-INFO}" == "DEBUG" ]]; then
|
|
640
|
+
log_debug "=== Token Optimization Metrics ==="
|
|
641
|
+
log_debug " Cache enabled: ${ENABLE_SEMANTIC_CACHE:-true}"
|
|
642
|
+
log_debug " Cache hits: ${#CACHE_HITS[@]}"
|
|
643
|
+
log_debug " Response limits enabled: ${ENABLE_RESPONSE_LIMITS:-false}"
|
|
644
|
+
log_debug " Cost-aware routing: ${ENABLE_COST_AWARE_ROUTING:-false}"
|
|
645
|
+
log_debug " Debate optimization: ${ENABLE_DEBATE_OPTIMIZATION:-false}"
|
|
646
|
+
fi
|
|
647
|
+
|
|
648
|
+
# Save optimization metrics to output directory
|
|
649
|
+
jq -n \
|
|
650
|
+
--argjson cache_enabled "${ENABLE_SEMANTIC_CACHE:-true}" \
|
|
651
|
+
--argjson cache_hits "${#CACHE_HITS[@]}" \
|
|
652
|
+
--argjson response_limits "${ENABLE_RESPONSE_LIMITS:-false}" \
|
|
653
|
+
--argjson cost_aware "${ENABLE_COST_AWARE_ROUTING:-false}" \
|
|
654
|
+
--argjson debate_opt "${ENABLE_DEBATE_OPTIMIZATION:-false}" \
|
|
655
|
+
--argjson compact "${ENABLE_COMPACT_REPORT:-true}" \
|
|
656
|
+
--argjson consensus "$CONSENSUS_SCORE" \
|
|
657
|
+
--arg consensus_level "$CONSENSUS_LEVEL" \
|
|
658
|
+
--argjson success_count "$SUCCESS_COUNT" \
|
|
659
|
+
--argjson total "${#PIDS[@]}" \
|
|
660
|
+
--arg category "$QUESTION_CATEGORY" \
|
|
661
|
+
'{
|
|
662
|
+
optimization_settings: {
|
|
663
|
+
cache_enabled: $cache_enabled,
|
|
664
|
+
cache_hits: $cache_hits,
|
|
665
|
+
response_limits_enabled: $response_limits,
|
|
666
|
+
cost_aware_routing: $cost_aware,
|
|
667
|
+
debate_optimization: $debate_opt,
|
|
668
|
+
compact_report: $compact
|
|
669
|
+
},
|
|
670
|
+
quality_metrics: {
|
|
671
|
+
consensus_score: $consensus,
|
|
672
|
+
consensus_level: $consensus_level,
|
|
673
|
+
successful_responses: $success_count,
|
|
674
|
+
total_consultants: $total,
|
|
675
|
+
category: $category
|
|
676
|
+
},
|
|
677
|
+
timestamp: (now | todate)
|
|
678
|
+
}' > "$OUTPUT_DIR/optimization_metrics.json"
|
|
679
|
+
|
|
680
|
+
# --- Auto-Synthesis (optional) ---
|
|
681
|
+
SYNTHESIS_FILE=""
|
|
682
|
+
if [[ "$ENABLE_SYNTHESIS" == "true" && $SUCCESS_COUNT -gt 0 ]]; then
|
|
683
|
+
# Budget Check: Before Synthesis
|
|
684
|
+
if is_budget_enabled; then
|
|
685
|
+
# Update current cost after debate
|
|
686
|
+
if [[ "$ENABLE_COST_TRACKING" == "true" ]]; then
|
|
687
|
+
CURRENT_COST=$(calculate_session_cost "$OUTPUT_DIR")
|
|
688
|
+
fi
|
|
689
|
+
SYNTHESIS_ESTIMATE=$(estimate_phase_cost "synthesis" 1 "$CONTEXT_SIZE")
|
|
690
|
+
if ! enforce_budget "$CURRENT_COST" "$SYNTHESIS_ESTIMATE" "synthesis"; then
|
|
691
|
+
log_warn "Skipping synthesis due to budget constraints"
|
|
692
|
+
ENABLE_SYNTHESIS=false
|
|
693
|
+
fi
|
|
694
|
+
fi
|
|
695
|
+
|
|
696
|
+
# Proceed with synthesis if still enabled after budget check
|
|
697
|
+
if [[ "$ENABLE_SYNTHESIS" == "true" ]]; then
|
|
698
|
+
log_info "Generating automatic synthesis..."
|
|
699
|
+
SYNTHESIS_FILE="$OUTPUT_DIR/synthesis.json"
|
|
700
|
+
|
|
701
|
+
if "$SCRIPT_DIR/synthesize.sh" "$OUTPUT_DIR" "$SYNTHESIS_FILE" "$QUERY" > /dev/null 2>&1; then
|
|
702
|
+
log_success "Synthesis generated: $SYNTHESIS_FILE"
|
|
703
|
+
else
|
|
704
|
+
log_warn "Synthesis failed, using fallback"
|
|
705
|
+
fi
|
|
706
|
+
echo "" >&2
|
|
707
|
+
fi
|
|
708
|
+
fi
|
|
709
|
+
|
|
710
|
+
# --- Anonymous Peer Review (v2.2, optional) ---
|
|
711
|
+
if [[ "${ENABLE_PEER_REVIEW:-false}" == "true" ]]; then
|
|
712
|
+
MIN_RESPONSES="${PEER_REVIEW_MIN_RESPONSES:-3}"
|
|
713
|
+
if [[ $SUCCESS_COUNT -ge $MIN_RESPONSES ]]; then
|
|
714
|
+
log_info "Running anonymous peer review..."
|
|
715
|
+
PEER_REVIEW_DIR="$OUTPUT_DIR/peer_review"
|
|
716
|
+
mkdir -p "$PEER_REVIEW_DIR"
|
|
717
|
+
|
|
718
|
+
if "$SCRIPT_DIR/peer_review.sh" "$OUTPUT_DIR" "$PEER_REVIEW_DIR" > /dev/null 2>&1; then
|
|
719
|
+
log_success "Peer review completed: $PEER_REVIEW_DIR"
|
|
720
|
+
else
|
|
721
|
+
log_warn "Peer review failed or unavailable"
|
|
722
|
+
fi
|
|
723
|
+
else
|
|
724
|
+
log_info "Skipping peer review: need ${MIN_RESPONSES}+ responses (got $SUCCESS_COUNT)"
|
|
725
|
+
fi
|
|
726
|
+
echo "" >&2
|
|
727
|
+
fi
|
|
728
|
+
|
|
729
|
+
# --- Generate Combined Report ---
|
|
730
|
+
log_info "Generating combined report..."
|
|
731
|
+
|
|
732
|
+
REPORT_FILE="$OUTPUT_DIR/report.md"
|
|
733
|
+
|
|
734
|
+
{
|
|
735
|
+
echo "# AI Consultation Report v2.0"
|
|
736
|
+
echo ""
|
|
737
|
+
echo "**Date**: $(date '+%Y-%m-%d %H:%M:%S')"
|
|
738
|
+
echo "**Directory**: $(pwd)"
|
|
739
|
+
echo "**Version**: AI Consultants v${AI_CONSULTANTS_VERSION}"
|
|
740
|
+
echo "**Category**: $QUESTION_CATEGORY"
|
|
741
|
+
echo "**Successes**: $SUCCESS_COUNT/${#PIDS[@]} consultants"
|
|
742
|
+
echo "**Consensus**: ${CONSENSUS_SCORE}% ($CONSENSUS_LEVEL)"
|
|
743
|
+
echo ""
|
|
744
|
+
echo "---"
|
|
745
|
+
echo ""
|
|
746
|
+
|
|
747
|
+
# Question
|
|
748
|
+
echo "## Question"
|
|
749
|
+
echo ""
|
|
750
|
+
echo "$QUERY"
|
|
751
|
+
echo ""
|
|
752
|
+
|
|
753
|
+
# Included files
|
|
754
|
+
if [[ ${#FILES[@]} -gt 0 ]]; then
|
|
755
|
+
echo "## Analyzed Files"
|
|
756
|
+
echo ""
|
|
757
|
+
for f in "${FILES[@]}"; do
|
|
758
|
+
echo "- \`$f\`"
|
|
759
|
+
done
|
|
760
|
+
echo ""
|
|
761
|
+
fi
|
|
762
|
+
|
|
763
|
+
# Quick Summary Table (v2.3)
|
|
764
|
+
echo "## Quick Summary"
|
|
765
|
+
echo ""
|
|
766
|
+
echo "| Consultant | Confidence | Approach |"
|
|
767
|
+
echo "|------------|------------|----------|"
|
|
768
|
+
for i in "${!NAMES[@]}"; do
|
|
769
|
+
name="${NAMES[$i]}"
|
|
770
|
+
output_file="${OUTPUT_FILES[$i]}"
|
|
771
|
+
result="${RESULTS[$i]}"
|
|
772
|
+
if [[ "$result" == *":OK" ]] && [[ -s "$output_file" ]]; then
|
|
773
|
+
conf=$(jq -r '.confidence.score // "?"' "$output_file" 2>/dev/null)
|
|
774
|
+
appr=$(jq -r '.response.approach // "N/A"' "$output_file" 2>/dev/null | head -c 30)
|
|
775
|
+
echo "| $name | $conf/10 | $appr |"
|
|
776
|
+
else
|
|
777
|
+
echo "| $name | - | (no response) |"
|
|
778
|
+
fi
|
|
779
|
+
done
|
|
780
|
+
echo ""
|
|
781
|
+
|
|
782
|
+
echo "---"
|
|
783
|
+
echo ""
|
|
784
|
+
|
|
785
|
+
# Synthesis (if available)
|
|
786
|
+
if [[ -n "$SYNTHESIS_FILE" && -f "$SYNTHESIS_FILE" ]]; then
|
|
787
|
+
echo "## Automatic Synthesis"
|
|
788
|
+
echo ""
|
|
789
|
+
|
|
790
|
+
recommendation=$(jq -r '.weighted_recommendation.summary // "N/A"' "$SYNTHESIS_FILE" 2>/dev/null)
|
|
791
|
+
approach=$(jq -r '.weighted_recommendation.approach // "N/A"' "$SYNTHESIS_FILE" 2>/dev/null)
|
|
792
|
+
|
|
793
|
+
echo "**Recommended Approach**: $approach"
|
|
794
|
+
echo ""
|
|
795
|
+
echo "$recommendation"
|
|
796
|
+
echo ""
|
|
797
|
+
|
|
798
|
+
# Comparison table (dynamic: reads actual consultant names from synthesis JSON)
|
|
799
|
+
echo "### Consultant Comparison"
|
|
800
|
+
echo ""
|
|
801
|
+
|
|
802
|
+
jq -r '
|
|
803
|
+
(.comparison_table[0] // {} | keys | map(select(. != "aspect"))) as $cs |
|
|
804
|
+
if ($cs | length) > 0 then
|
|
805
|
+
(["Aspect"] + $cs | map("| \(.) ") | join("")) + "|",
|
|
806
|
+
(["-"] + ($cs | map("-")) | map("|---") | join("")) + "|",
|
|
807
|
+
(.comparison_table[]? | . as $row |
|
|
808
|
+
([.aspect] + [$cs[] as $c | ($row[$c] // "N/A")] |
|
|
809
|
+
map("| \(.) ") | join("")) + "|")
|
|
810
|
+
else
|
|
811
|
+
"| (comparison data unavailable) |"
|
|
812
|
+
end
|
|
813
|
+
' "$SYNTHESIS_FILE" 2>/dev/null || echo "| (comparison data unavailable) |"
|
|
814
|
+
|
|
815
|
+
echo ""
|
|
816
|
+
|
|
817
|
+
# Risks
|
|
818
|
+
echo "### Risk Assessment"
|
|
819
|
+
echo ""
|
|
820
|
+
overall_risk=$(jq -r '.risk_assessment.overall_risk // "unknown"' "$SYNTHESIS_FILE" 2>/dev/null)
|
|
821
|
+
echo "**Overall Risk**: $overall_risk"
|
|
822
|
+
echo ""
|
|
823
|
+
|
|
824
|
+
jq -r '.risk_assessment.risks[]? | "- **\(.description)** (\(.severity)): \(.mitigation)"' \
|
|
825
|
+
"$SYNTHESIS_FILE" 2>/dev/null || echo "- No significant risks identified"
|
|
826
|
+
|
|
827
|
+
echo ""
|
|
828
|
+
echo "---"
|
|
829
|
+
echo ""
|
|
830
|
+
fi
|
|
831
|
+
|
|
832
|
+
echo "## Individual Responses"
|
|
833
|
+
echo ""
|
|
834
|
+
|
|
835
|
+
# Response from each consultant
|
|
836
|
+
for i in "${!NAMES[@]}"; do
|
|
837
|
+
name="${NAMES[$i]}"
|
|
838
|
+
output_file="${OUTPUT_FILES[$i]}"
|
|
839
|
+
result="${RESULTS[$i]}"
|
|
840
|
+
|
|
841
|
+
echo "### $name"
|
|
842
|
+
echo ""
|
|
843
|
+
|
|
844
|
+
if [[ "$result" == *":OK" ]] && [[ -s "$output_file" ]]; then
|
|
845
|
+
persona=$(jq -r '.persona // "N/A"' "$output_file" 2>/dev/null)
|
|
846
|
+
confidence=$(jq -r '.confidence.score // "N/A"' "$output_file" 2>/dev/null)
|
|
847
|
+
approach=$(jq -r '.response.approach // "N/A"' "$output_file" 2>/dev/null)
|
|
848
|
+
summary=$(jq -r '.response.summary // "No summary"' "$output_file" 2>/dev/null)
|
|
849
|
+
|
|
850
|
+
echo "**Persona**: $persona | **Confidence**: $confidence/10 | **Approach**: $approach"
|
|
851
|
+
echo ""
|
|
852
|
+
echo "$summary"
|
|
853
|
+
echo ""
|
|
854
|
+
|
|
855
|
+
# Include full JSON only if compact report is disabled (v2.3)
|
|
856
|
+
if [[ "${ENABLE_COMPACT_REPORT:-true}" != "true" ]]; then
|
|
857
|
+
max_lines="${REPORT_MAX_JSON_LINES:-50}"
|
|
858
|
+
echo "<details>"
|
|
859
|
+
echo "<summary>Full response</summary>"
|
|
860
|
+
echo ""
|
|
861
|
+
echo '```json'
|
|
862
|
+
jq '.' "$output_file" 2>/dev/null | head -"$max_lines"
|
|
863
|
+
if [[ $(wc -l < "$output_file") -gt $max_lines ]]; then
|
|
864
|
+
echo ""
|
|
865
|
+
echo "[... truncated, see ${output_file} for full response ...]"
|
|
866
|
+
fi
|
|
867
|
+
echo '```'
|
|
868
|
+
echo "</details>"
|
|
869
|
+
echo ""
|
|
870
|
+
fi
|
|
871
|
+
elif [[ "$result" == *":EMPTY" ]]; then
|
|
872
|
+
echo "*Empty response*"
|
|
873
|
+
else
|
|
874
|
+
echo "*Consultant did not respond*"
|
|
875
|
+
fi
|
|
876
|
+
echo ""
|
|
877
|
+
done
|
|
878
|
+
|
|
879
|
+
echo "---"
|
|
880
|
+
echo ""
|
|
881
|
+
echo "## Output Files"
|
|
882
|
+
echo ""
|
|
883
|
+
echo "- Context: \`$CONTEXT_FILE\`"
|
|
884
|
+
echo "- Voting: \`$OUTPUT_DIR/voting.json\`"
|
|
885
|
+
[[ -n "$SYNTHESIS_FILE" ]] && echo "- Synthesis: \`$SYNTHESIS_FILE\`"
|
|
886
|
+
for i in "${!NAMES[@]}"; do
|
|
887
|
+
echo "- ${NAMES[$i]}: \`${OUTPUT_FILES[$i]}\`"
|
|
888
|
+
done
|
|
889
|
+
|
|
890
|
+
} > "$REPORT_FILE"
|
|
891
|
+
|
|
892
|
+
log_success "Report generated: $REPORT_FILE"
|
|
893
|
+
echo "" >&2
|
|
894
|
+
|
|
895
|
+
# --- Session Management ---
|
|
896
|
+
SESSION_ID=$(save_session "$(generate_session_id)" "$QUERY" "$OUTPUT_DIR" "$QUESTION_CATEGORY")
|
|
897
|
+
log_info "Session saved: $SESSION_ID"
|
|
898
|
+
|
|
899
|
+
# --- Cost Tracking ---
|
|
900
|
+
if [[ "$ENABLE_COST_TRACKING" == "true" ]]; then
|
|
901
|
+
ACTUAL_COST=$(calculate_session_cost "$OUTPUT_DIR")
|
|
902
|
+
track_session_cost "$SESSION_ID" "$ACTUAL_COST"
|
|
903
|
+
log_info "Session cost: $(format_cost "$ACTUAL_COST")"
|
|
904
|
+
fi
|
|
905
|
+
|
|
906
|
+
# --- Final Summary ---
|
|
907
|
+
echo "" >&2
|
|
908
|
+
echo "════════════════════════════════════════════════════════════════" >&2
|
|
909
|
+
log_success "Consultation completed!"
|
|
910
|
+
echo "" >&2
|
|
911
|
+
log_info "Results:"
|
|
912
|
+
for result in "${RESULTS[@]}"; do
|
|
913
|
+
name="${result%%:*}"
|
|
914
|
+
status="${result##*:}"
|
|
915
|
+
case "$status" in
|
|
916
|
+
OK) log_success " - $name: Success" ;;
|
|
917
|
+
EMPTY) log_warn " - $name: Empty response" ;;
|
|
918
|
+
FAILED) log_error " - $name: Failed" ;;
|
|
919
|
+
esac
|
|
920
|
+
done
|
|
921
|
+
echo "" >&2
|
|
922
|
+
log_info "Consensus: ${CONSENSUS_SCORE}% ($CONSENSUS_LEVEL)"
|
|
923
|
+
echo "" >&2
|
|
924
|
+
log_info "To view the report:"
|
|
925
|
+
log_info " cat $REPORT_FILE"
|
|
926
|
+
echo "" >&2
|
|
927
|
+
log_info "For follow-up:"
|
|
928
|
+
log_info " ./followup.sh \"Your follow-up question\""
|
|
929
|
+
echo "" >&2
|
|
930
|
+
log_info "Output directory: $OUTPUT_DIR"
|
|
931
|
+
echo "════════════════════════════════════════════════════════════════" >&2
|
|
932
|
+
|
|
933
|
+
# Output path for calling scripts
|
|
934
|
+
echo "$OUTPUT_DIR"
|