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.
Files changed (62) hide show
  1. package/.claude/commands/ai-consultants:consult.md +94 -0
  2. package/.claude/commands/ai-consultants:debate.md +103 -0
  3. package/.claude/commands/ai-consultants:help.md +30 -0
  4. package/CLAUDE.md +839 -0
  5. package/LICENSE +21 -0
  6. package/README.md +758 -0
  7. package/SKILL.md +156 -0
  8. package/bin/ai-consultants +96 -0
  9. package/docs/COST_RATES.md +172 -0
  10. package/docs/JSON_SCHEMA.md +305 -0
  11. package/docs/SETUP.md +879 -0
  12. package/docs/SMART_ROUTING.md +219 -0
  13. package/docs/cost_rates.json +143 -0
  14. package/package.json +47 -0
  15. package/references/configuration.md +141 -0
  16. package/references/details.md +60 -0
  17. package/scripts/build_context.sh +562 -0
  18. package/scripts/classify_question.sh +148 -0
  19. package/scripts/config.sh +753 -0
  20. package/scripts/configure.sh +1093 -0
  21. package/scripts/consult_all.sh +934 -0
  22. package/scripts/debate_round.sh +421 -0
  23. package/scripts/doctor.sh +642 -0
  24. package/scripts/followup.sh +197 -0
  25. package/scripts/install.sh +319 -0
  26. package/scripts/lib/api.sh +640 -0
  27. package/scripts/lib/api_query.sh +228 -0
  28. package/scripts/lib/cache.sh +338 -0
  29. package/scripts/lib/chunking.sh +880 -0
  30. package/scripts/lib/code_optimizer.sh +671 -0
  31. package/scripts/lib/common.sh +1088 -0
  32. package/scripts/lib/costs.sh +674 -0
  33. package/scripts/lib/personas.sh +372 -0
  34. package/scripts/lib/progress.sh +251 -0
  35. package/scripts/lib/reflection.sh +447 -0
  36. package/scripts/lib/routing.sh +566 -0
  37. package/scripts/lib/schema.json +165 -0
  38. package/scripts/lib/session.sh +327 -0
  39. package/scripts/lib/symbol_map.sh +445 -0
  40. package/scripts/lib/voting.sh +495 -0
  41. package/scripts/peer_review.sh +460 -0
  42. package/scripts/preflight_check.sh +452 -0
  43. package/scripts/query_aider.sh +83 -0
  44. package/scripts/query_amp.sh +67 -0
  45. package/scripts/query_claude.sh +99 -0
  46. package/scripts/query_codex.sh +92 -0
  47. package/scripts/query_cursor.sh +72 -0
  48. package/scripts/query_deepseek.sh +17 -0
  49. package/scripts/query_gemini.sh +85 -0
  50. package/scripts/query_glm.sh +17 -0
  51. package/scripts/query_grok.sh +17 -0
  52. package/scripts/query_kilo.sh +140 -0
  53. package/scripts/query_kimi.sh +67 -0
  54. package/scripts/query_minimax.sh +17 -0
  55. package/scripts/query_mistral.sh +97 -0
  56. package/scripts/query_ollama.sh +328 -0
  57. package/scripts/query_qwen3.sh +93 -0
  58. package/scripts/setup_wizard.sh +367 -0
  59. package/scripts/skillport-install.sh +196 -0
  60. package/scripts/synthesize.sh +332 -0
  61. package/templates/consultation_report.md +323 -0
  62. package/templates/synthesis_prompt.md +120 -0
@@ -0,0 +1,421 @@
1
+ #!/bin/bash
2
+ # debate_round.sh - Multi-Agent Debate (MAD) implementation for AI Consultants
3
+ #
4
+ # Implements deliberation rounds where consultants can see and
5
+ # respond to each other's responses.
6
+ #
7
+ # Usage: ./debate_round.sh <responses_dir> <round_number> <output_dir>
8
+ #
9
+ # Round 1: Initial responses (handled by consult_all.sh)
10
+ # Round 2: Cross-critique - each consultant sees other responses
11
+ # Round 3: Final stance - final position after seeing critiques
12
+
13
+ set -euo pipefail
14
+
15
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
+ source "$SCRIPT_DIR/lib/common.sh"
17
+ source "$SCRIPT_DIR/lib/personas.sh"
18
+ source "$SCRIPT_DIR/config.sh" 2>/dev/null || log_warn "config.sh not loaded (using defaults)"
19
+
20
+ # =============================================================================
21
+ # DEBATE OPTIMIZATION (v2.3)
22
+ # =============================================================================
23
+
24
+ # Check if category requires mandatory debate (v2.3 Quality Review)
25
+ # Usage: is_mandatory_debate_category <category>
26
+ is_mandatory_debate_category() {
27
+ local category="${1:-GENERAL}"
28
+ case "$category" in
29
+ SECURITY|ARCHITECTURE)
30
+ return 0 # Mandatory debate
31
+ ;;
32
+ *)
33
+ return 1 # Not mandatory
34
+ ;;
35
+ esac
36
+ }
37
+
38
+ # Check if debate should be skipped based on confidence spread
39
+ # Usage: should_skip_debate <responses_dir> [category]
40
+ should_skip_debate() {
41
+ local responses_dir="$1"
42
+ local category="${2:-GENERAL}"
43
+
44
+ # SECURITY and ARCHITECTURE always require debate (v2.3 Quality Review)
45
+ if is_mandatory_debate_category "$category"; then
46
+ return 1 # Don't skip - mandatory categories always debate
47
+ fi
48
+
49
+ # Skip if debate optimization is not enabled
50
+ if [[ "${ENABLE_DEBATE_OPTIMIZATION:-false}" != "true" ]]; then
51
+ return 1 # Don't skip
52
+ fi
53
+
54
+ local threshold="${DEBATE_CONFIDENCE_SPREAD_THRESHOLD:-3}"
55
+ local min_confidence=10
56
+ local max_confidence=0
57
+ local count=0
58
+
59
+ for f in "$responses_dir"/*.json; do
60
+ if [[ -f "$f" && "$f" != *"summary"* && "$f" != *"round_"* ]]; then
61
+ local confidence
62
+ confidence=$(jq -r '.confidence.score // 5' "$f" 2>/dev/null)
63
+ if [[ "$confidence" =~ ^[0-9]+$ ]]; then
64
+ [[ $confidence -lt $min_confidence ]] && min_confidence=$confidence
65
+ [[ $confidence -gt $max_confidence ]] && max_confidence=$confidence
66
+ ((count++)) || true
67
+ fi
68
+ fi
69
+ done
70
+
71
+ if [[ $count -lt 2 ]]; then
72
+ return 1 # Not enough responses to evaluate
73
+ fi
74
+
75
+ local spread=$((max_confidence - min_confidence))
76
+ if [[ $spread -lt $threshold ]]; then
77
+ log_debug "Debate optimization: Skipping debate (confidence spread: $spread < threshold: $threshold)"
78
+ return 0 # Skip debate
79
+ fi
80
+
81
+ return 1 # Don't skip
82
+ }
83
+
84
+ # Extract compact summary for token-optimized debate
85
+ # Usage: extract_compact_summary <response_file>
86
+ extract_compact_summary() {
87
+ local response_file="$1"
88
+
89
+ if [[ "${DEBATE_USE_SUMMARIES:-true}" == "true" ]]; then
90
+ # Use only summary field (compact)
91
+ local consultant=$(jq -r '.consultant // "Unknown"' "$response_file" 2>/dev/null)
92
+ local summary=$(jq -r '.response.summary // "No summary"' "$response_file" 2>/dev/null | head -c 300)
93
+ local approach=$(jq -r '.response.approach // "unknown"' "$response_file" 2>/dev/null)
94
+ local confidence=$(jq -r '.confidence.score // 5' "$response_file" 2>/dev/null)
95
+
96
+ echo "**$consultant** ($approach, conf:$confidence): $summary"
97
+ else
98
+ # Use full extract_summary (original behavior)
99
+ extract_summary "$response_file"
100
+ fi
101
+ }
102
+
103
+ # --- Parameters ---
104
+ RESPONSES_DIR="${1:-}"
105
+ ROUND_NUMBER="${2:-2}"
106
+ OUTPUT_DIR="${3:-}"
107
+ QUESTION_CATEGORY="${4:-GENERAL}" # v2.3: Category for mandatory debate check
108
+
109
+ if [[ ! "$ROUND_NUMBER" =~ ^[0-9]+$ ]] || [[ "$ROUND_NUMBER" -lt 1 ]]; then
110
+ log_error "Invalid round number: $ROUND_NUMBER (must be a positive integer)"
111
+ exit 1
112
+ fi
113
+
114
+ if [[ -z "$RESPONSES_DIR" || ! -d "$RESPONSES_DIR" ]]; then
115
+ log_error "Usage: $0 <responses_dir> <round_number> [output_dir] [category]"
116
+ exit 1
117
+ fi
118
+
119
+ if [[ -z "$OUTPUT_DIR" ]]; then
120
+ OUTPUT_DIR="$RESPONSES_DIR/round_${ROUND_NUMBER}"
121
+ fi
122
+
123
+ mkdir -p "$OUTPUT_DIR"
124
+
125
+ log_info "=== Multi-Agent Debate - Round $ROUND_NUMBER ==="
126
+
127
+ # =============================================================================
128
+ # HELPER FUNCTIONS
129
+ # =============================================================================
130
+
131
+ # Extract summary from a response for the debate prompt
132
+ extract_summary() {
133
+ local response_file="$1"
134
+ local consultant=$(jq -r '.consultant // "Unknown"' "$response_file" 2>/dev/null)
135
+ local summary=$(jq -r '.response.summary // .response.detailed[:500] // "No summary"' "$response_file" 2>/dev/null)
136
+ local approach=$(jq -r '.response.approach // "unknown"' "$response_file" 2>/dev/null)
137
+ local confidence=$(jq -r '.confidence.score // 5' "$response_file" 2>/dev/null)
138
+
139
+ echo "**$consultant** (Approach: $approach, Confidence: $confidence/10):
140
+ $summary"
141
+ }
142
+
143
+ # Build prompt for debate round
144
+ build_debate_prompt() {
145
+ local consultant="$1"
146
+ local my_response_file="$2"
147
+ local responses_dir="$3"
148
+
149
+ local my_response=$(cat "$my_response_file" 2>/dev/null)
150
+
151
+ # Validate response JSON before using in debate prompt
152
+ if [[ -z "$my_response" ]] || ! echo "$my_response" | jq -e '.' > /dev/null; then
153
+ log_warn "Invalid or empty previous response for $consultant, using fallback"
154
+ my_response='{"response":{"summary":"No summary","approach":"unknown"},"confidence":{"score":5}}'
155
+ fi
156
+
157
+ local other_summaries=""
158
+
159
+ # Collect summaries from other consultants (using compact summaries when optimized)
160
+ for f in "$responses_dir"/*.json; do
161
+ if [[ -f "$f" && "$f" != *"summary"* && "$f" != *"round_"* ]]; then
162
+ local other_consultant=$(jq -r '.consultant // "Unknown"' "$f" 2>/dev/null)
163
+ if [[ "$other_consultant" != "$consultant" ]]; then
164
+ other_summaries+="
165
+ $(extract_compact_summary "$f")
166
+ ---
167
+ "
168
+ fi
169
+ fi
170
+ done
171
+
172
+ cat << EOF
173
+ # Round $ROUND_NUMBER - Cross-Critique
174
+
175
+ ## Your Previous Response (Round $((ROUND_NUMBER - 1)))
176
+ $(echo "$my_response" | jq -r '.response.summary // "No summary"' 2>/dev/null)
177
+
178
+ Approach: $(echo "$my_response" | jq -r '.response.approach // "unknown"' 2>/dev/null)
179
+ Confidence: $(echo "$my_response" | jq -r '.confidence.score // 5' 2>/dev/null)/10
180
+
181
+ ## Other Consultants' Responses
182
+ $other_summaries
183
+
184
+ ## Instructions
185
+
186
+ After seeing the opinions of other consultants, respond in JSON:
187
+
188
+ {
189
+ "debate": {
190
+ "round": $ROUND_NUMBER,
191
+ "position_changed": <true if you changed your mind, false otherwise>,
192
+ "original_stance": "<your original position>",
193
+ "updated_stance": "<your new position or confirmation of previous>",
194
+ "confidence_delta": <confidence change: -3 to +3>,
195
+ "critiques": [
196
+ {
197
+ "target": "<consultant name>",
198
+ "critique": "<your critique>",
199
+ "severity": "<minor|moderate|major>"
200
+ }
201
+ ],
202
+ "incorporated_from": [
203
+ {
204
+ "source": "<consultant name>",
205
+ "idea": "<what you incorporated>"
206
+ }
207
+ ],
208
+ "areas_of_agreement": ["<points you agree with others on>"],
209
+ "areas_of_disagreement": ["<points you disagree on>"]
210
+ },
211
+ "response": {
212
+ "summary": "<updated summary if position_changed, otherwise confirmation>",
213
+ "detailed": "<updated details>",
214
+ "approach": "<updated or confirmed approach>",
215
+ "pros": ["<updated pros>"],
216
+ "cons": ["<updated cons>"]
217
+ },
218
+ "confidence": {
219
+ "score": <new score 1-10>,
220
+ "reasoning": "<why this confidence level after debate>"
221
+ }
222
+ }
223
+
224
+ IMPORTANT: Respond ONLY with valid JSON. Be honest - if others have valid points, acknowledge them.
225
+ EOF
226
+ }
227
+
228
+ # =============================================================================
229
+ # MAIN DEBATE EXECUTION
230
+ # =============================================================================
231
+
232
+ run_debate_round() {
233
+ # Use shared function to get enabled consultants
234
+ local consultants_str
235
+ consultants_str=$(get_enabled_consultants)
236
+ # shellcheck disable=SC2206
237
+ local consultants=($consultants_str)
238
+
239
+ local pids=()
240
+ local output_files=()
241
+ local consultant_names=()
242
+
243
+ log_info "Starting debate round $ROUND_NUMBER for ${#consultants[@]} consultants..."
244
+
245
+ for consultant in "${consultants[@]}"; do
246
+ local consultant_lower
247
+ consultant_lower=$(to_lower "$consultant")
248
+ local response_file="$RESPONSES_DIR/${consultant_lower}.json"
249
+
250
+ # Skip if no previous round response
251
+ if [[ ! -f "$response_file" || ! -s "$response_file" ]]; then
252
+ log_warn "$consultant: no previous response, skipping"
253
+ continue
254
+ fi
255
+
256
+ local output_file="$OUTPUT_DIR/${consultant_lower}.json"
257
+ output_files+=("$output_file")
258
+
259
+ # Build debate prompt
260
+ local debate_prompt=$(build_debate_prompt "$consultant" "$response_file" "$RESPONSES_DIR")
261
+
262
+ # Execute query in background
263
+ (
264
+ local start_time
265
+ start_time=$(get_timestamp_ms)
266
+
267
+ # Route to appropriate query script based on consultant
268
+ case "$consultant" in
269
+ Gemini)
270
+ echo "$debate_prompt" | "$SCRIPT_DIR/query_gemini.sh" "" "" "$output_file" > /dev/null
271
+ ;;
272
+ Codex)
273
+ "$SCRIPT_DIR/query_codex.sh" "$debate_prompt" "" "$output_file" > /dev/null
274
+ ;;
275
+ Mistral)
276
+ "$SCRIPT_DIR/query_mistral.sh" "$debate_prompt" "" "$output_file" > /dev/null
277
+ ;;
278
+ Kilo)
279
+ "$SCRIPT_DIR/query_kilo.sh" "$debate_prompt" "" "$output_file" > /dev/null
280
+ ;;
281
+ Cursor)
282
+ "$SCRIPT_DIR/query_cursor.sh" "$debate_prompt" "" "$output_file" > /dev/null
283
+ ;;
284
+ Aider)
285
+ "$SCRIPT_DIR/query_aider.sh" "$debate_prompt" "" "$output_file" > /dev/null
286
+ ;;
287
+ Claude)
288
+ "$SCRIPT_DIR/query_claude.sh" "$debate_prompt" "" "$output_file" > /dev/null
289
+ ;;
290
+ Qwen3)
291
+ "$SCRIPT_DIR/query_qwen3.sh" "$debate_prompt" "" "$output_file" > /dev/null
292
+ ;;
293
+ GLM)
294
+ "$SCRIPT_DIR/query_glm.sh" "$debate_prompt" "" "$output_file" > /dev/null
295
+ ;;
296
+ Grok)
297
+ "$SCRIPT_DIR/query_grok.sh" "$debate_prompt" "" "$output_file" > /dev/null
298
+ ;;
299
+ DeepSeek)
300
+ "$SCRIPT_DIR/query_deepseek.sh" "$debate_prompt" "" "$output_file" > /dev/null
301
+ ;;
302
+ Ollama)
303
+ "$SCRIPT_DIR/query_ollama.sh" "$debate_prompt" "" "$output_file" > /dev/null
304
+ ;;
305
+ Amp)
306
+ "$SCRIPT_DIR/query_amp.sh" "$debate_prompt" "" "$output_file" > /dev/null
307
+ ;;
308
+ Kimi)
309
+ "$SCRIPT_DIR/query_kimi.sh" "$debate_prompt" "" "$output_file" > /dev/null
310
+ ;;
311
+ MiniMax)
312
+ "$SCRIPT_DIR/query_minimax.sh" "$debate_prompt" "" "$output_file" > /dev/null
313
+ ;;
314
+ *)
315
+ log_error "Unknown consultant in debate round: $consultant (no query script mapped)"
316
+ exit 1
317
+ ;;
318
+ esac
319
+
320
+ local end_time
321
+ end_time=$(get_timestamp_ms)
322
+ local latency=$((end_time - start_time))
323
+
324
+ # Add debate metadata to result
325
+ if [[ -f "$output_file" && -s "$output_file" ]]; then
326
+ local temp_file=$(mktemp)
327
+ if jq --argjson round "$ROUND_NUMBER" \
328
+ --argjson latency "$latency" \
329
+ '. + {debate_round: $round, debate_latency_ms: $latency}' \
330
+ "$output_file" > "$temp_file"; then
331
+ mv "$temp_file" "$output_file"
332
+ else
333
+ log_warn "Failed to add debate metadata for $consultant"
334
+ rm -f "$temp_file"
335
+ fi
336
+ fi
337
+ ) 2>"$OUTPUT_DIR/${consultant_lower}.err" &
338
+
339
+ pids+=($!)
340
+ consultant_names+=("$consultant")
341
+ done
342
+
343
+ # Wait for all jobs
344
+ local success_count=0
345
+ for i in "${!pids[@]}"; do
346
+ local cname="${consultant_names[$i]}"
347
+ local cname_lower
348
+ cname_lower=$(to_lower "$cname")
349
+ local err_file="$OUTPUT_DIR/${cname_lower}.err"
350
+ if wait "${pids[$i]}" 2>/dev/null; then
351
+ ((success_count++)) || true
352
+ else
353
+ log_warn "Debate round $ROUND_NUMBER: $cname failed"
354
+ if [[ -s "$err_file" ]]; then
355
+ log_debug " Error details: $(head -c 200 "$err_file")"
356
+ fi
357
+ fi
358
+ # Clean up empty error logs
359
+ [[ -f "$err_file" && ! -s "$err_file" ]] && rm -f "$err_file"
360
+ done
361
+
362
+ log_success "Debate round $ROUND_NUMBER completed: $success_count/${#pids[@]} responses"
363
+
364
+ # Generate round summary
365
+ generate_round_summary
366
+ }
367
+
368
+ # Generate debate round summary
369
+ generate_round_summary() {
370
+ local summary_file="$OUTPUT_DIR/round_summary.json"
371
+
372
+ local position_changes=0
373
+ local total_critiques=0
374
+ local consultants_responded=0
375
+
376
+ for f in "$OUTPUT_DIR"/*.json; do
377
+ if [[ -f "$f" && "$f" != *"summary"* ]]; then
378
+ ((consultants_responded++)) || true
379
+
380
+ local changed=$(jq -r '.debate.position_changed // false' "$f" 2>/dev/null)
381
+ if [[ "$changed" == "true" ]]; then
382
+ ((position_changes++)) || true
383
+ fi
384
+
385
+ local critiques=$(jq -r '.debate.critiques | length // 0' "$f" 2>/dev/null)
386
+ total_critiques=$((total_critiques + critiques))
387
+ fi
388
+ done
389
+
390
+ jq -n \
391
+ --argjson round "$ROUND_NUMBER" \
392
+ --argjson responded "$consultants_responded" \
393
+ --argjson changes "$position_changes" \
394
+ --argjson critiques "$total_critiques" \
395
+ --arg timestamp "$(date -Iseconds)" \
396
+ '{
397
+ round: $round,
398
+ timestamp: $timestamp,
399
+ consultants_responded: $responded,
400
+ position_changes: $changes,
401
+ total_critiques: $critiques,
402
+ stability: (if $changes == 0 then "stable" elif $changes <= 1 then "mostly_stable" else "volatile" end)
403
+ }' > "$summary_file"
404
+
405
+ log_info "Round summary saved: $summary_file"
406
+ }
407
+
408
+ # =============================================================================
409
+ # ENTRY POINT
410
+ # =============================================================================
411
+
412
+ # Check if debate should be skipped based on confidence spread (v2.3)
413
+ if [[ "$ROUND_NUMBER" == "2" ]] && should_skip_debate "$RESPONSES_DIR" "$QUESTION_CATEGORY"; then
414
+ log_info "Debate skipped: confidence spread below threshold (all consultants agree)"
415
+ echo '{"skipped": true, "reason": "confidence_spread_below_threshold"}' > "$OUTPUT_DIR/round_summary.json"
416
+ echo "$OUTPUT_DIR"
417
+ exit 0
418
+ fi
419
+
420
+ run_debate_round
421
+ echo "$OUTPUT_DIR"