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,460 @@
1
+ #!/bin/bash
2
+ # peer_review.sh - Anonymous Peer Review for AI Consultants
3
+ #
4
+ # Implements blind peer review where consultants evaluate each other's
5
+ # responses without knowing the source, reducing bias and identifying
6
+ # the strongest arguments.
7
+ #
8
+ # Process:
9
+ # 1. Anonymize all responses (remove consultant names/identifiers)
10
+ # 2. Each consultant ranks and critiques top 2-3 responses
11
+ # 3. Aggregate peer scores to identify strongest arguments
12
+ # 4. De-anonymize in final report with peer scores
13
+ #
14
+ # Usage: ./peer_review.sh <responses_dir> <output_dir>
15
+ #
16
+ # Input: Directory containing consultant JSON responses
17
+ # Output: Directory with peer review results and aggregated scores
18
+
19
+ set -euo pipefail
20
+
21
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
22
+ source "$SCRIPT_DIR/lib/common.sh"
23
+
24
+ # =============================================================================
25
+ # CONFIGURATION
26
+ # =============================================================================
27
+
28
+ # Number of responses each consultant reviews (2-3 recommended)
29
+ REVIEWS_PER_CONSULTANT="${REVIEWS_PER_CONSULTANT:-3}"
30
+
31
+ # Minimum consultants needed for peer review
32
+ MIN_CONSULTANTS_FOR_REVIEW=3
33
+
34
+ # =============================================================================
35
+ # ANONYMIZATION
36
+ # =============================================================================
37
+
38
+ # Create anonymous ID from consultant name
39
+ # Uses a simple hash-like approach for deterministic but hidden IDs
40
+ generate_anonymous_id() {
41
+ local consultant="$1"
42
+ local index="$2"
43
+ echo "Response_$(printf '%c' $((65 + index)))" # A, B, C, D, etc.
44
+ }
45
+
46
+ # Anonymize a response by removing identifying information
47
+ # Usage: anonymize_response <response_file> <anonymous_id>
48
+ anonymize_response() {
49
+ local response_file="$1"
50
+ local anon_id="$2"
51
+
52
+ # Remove consultant name and persona, keep the substance
53
+ jq --arg anon_id "$anon_id" '
54
+ del(.consultant, .persona, .model) |
55
+ . + {anonymous_id: $anon_id}
56
+ ' "$response_file" 2>/dev/null
57
+ }
58
+
59
+ # Create mapping file for de-anonymization later
60
+ # Usage: create_mapping <responses_dir> <output_file>
61
+ create_mapping() {
62
+ local responses_dir="$1"
63
+ local output_file="$2"
64
+
65
+ local mapping="{}"
66
+ local index=0
67
+
68
+ for f in "$responses_dir"/*.json; do
69
+ if [[ -f "$f" && -s "$f" ]]; then
70
+ local consultant
71
+ consultant=$(jq -r '.consultant // "unknown"' "$f" 2>/dev/null)
72
+ local anon_id
73
+ anon_id=$(generate_anonymous_id "$consultant" "$index")
74
+
75
+ mapping=$(echo "$mapping" | jq \
76
+ --arg anon "$anon_id" \
77
+ --arg real "$consultant" \
78
+ --arg file "$(basename "$f")" \
79
+ '. + {($anon): {consultant: $real, file: $file}}')
80
+
81
+ ((index++))
82
+ fi
83
+ done
84
+
85
+ echo "$mapping" > "$output_file"
86
+ }
87
+
88
+ # =============================================================================
89
+ # PEER REVIEW PROMPTS
90
+ # =============================================================================
91
+
92
+ # Generate the peer review prompt
93
+ generate_review_prompt() {
94
+ local anonymous_responses="$1"
95
+
96
+ cat << EOF
97
+ You are an expert peer reviewer evaluating anonymous responses to a coding question.
98
+ Review each response objectively without knowing who wrote it.
99
+
100
+ ## Anonymous Responses to Review
101
+
102
+ $anonymous_responses
103
+
104
+ ## Your Task
105
+
106
+ For each response, provide:
107
+ 1. **Quality Score** (1-10): Overall quality of the response
108
+ 2. **Strengths**: What the response does well (2-3 points)
109
+ 3. **Weaknesses**: What could be improved (2-3 points)
110
+ 4. **Ranking**: Rank all responses from best to worst
111
+
112
+ Respond ONLY with valid JSON:
113
+ {
114
+ "reviews": [
115
+ {
116
+ "response_id": "Response_A",
117
+ "quality_score": <1-10>,
118
+ "strengths": ["<strength1>", "<strength2>"],
119
+ "weaknesses": ["<weakness1>", "<weakness2>"],
120
+ "key_insight": "<most valuable insight from this response>"
121
+ }
122
+ ],
123
+ "ranking": ["Response_A", "Response_B", ...],
124
+ "best_overall": "<response_id with best overall answer>",
125
+ "reasoning": "<why you ranked them this way>"
126
+ }
127
+ EOF
128
+ }
129
+
130
+ # =============================================================================
131
+ # PEER REVIEW EXECUTION
132
+ # =============================================================================
133
+
134
+ # Run peer review for a single consultant
135
+ # Usage: run_consultant_review <consultant> <anonymous_file> <output_file>
136
+ run_consultant_review() {
137
+ local consultant="$1"
138
+ local anonymous_file="$2"
139
+ local output_file="$3"
140
+
141
+ local prompt
142
+ prompt=$(generate_review_prompt "$(cat "$anonymous_file")")
143
+
144
+ local timeout_var="${consultant^^}_TIMEOUT_SECONDS"
145
+ local timeout="${!timeout_var:-180}"
146
+
147
+ log_debug "Running peer review by $consultant..."
148
+
149
+ case "$consultant" in
150
+ Gemini) echo "$prompt" | run_with_timeout "$timeout" "$GEMINI_CMD" -p - -m "$GEMINI_MODEL" ;;
151
+ Codex) run_with_timeout "$timeout" "$CODEX_CMD" exec --skip-git-repo-check "$prompt" ;;
152
+ Mistral) run_with_timeout "$timeout" "$MISTRAL_CMD" --prompt "$prompt" --auto-approve ;;
153
+ Kilo) run_with_timeout "$timeout" "$KILO_CMD" --auto --json "$prompt" ;;
154
+ Cursor) run_with_timeout "$timeout" "$CURSOR_CMD" "$prompt" ;;
155
+ *)
156
+ echo '{"error": "Unknown consultant for peer review"}' > "$output_file"
157
+ return 1
158
+ ;;
159
+ esac > "$output_file" 2>/dev/null
160
+ }
161
+
162
+ # Extract JSON from response (may have text wrapper)
163
+ extract_json_from_response() {
164
+ local input="$1"
165
+
166
+ # Try to extract JSON
167
+ if echo "$input" | jq -e '.' > /dev/null 2>&1; then
168
+ echo "$input"
169
+ return 0
170
+ fi
171
+
172
+ # Try to extract from markdown code block
173
+ local extracted
174
+ extracted=$(echo "$input" | sed -n '/```json/,/```/p' | sed '1d;$d')
175
+ if [[ -n "$extracted" ]] && echo "$extracted" | jq -e '.' > /dev/null 2>&1; then
176
+ echo "$extracted"
177
+ return 0
178
+ fi
179
+
180
+ # Try to find first { to last }
181
+ extracted=$(echo "$input" | sed -n '/{/,/}/p')
182
+ if [[ -n "$extracted" ]] && echo "$extracted" | jq -e '.' > /dev/null 2>&1; then
183
+ echo "$extracted"
184
+ return 0
185
+ fi
186
+
187
+ echo '{"error": "Could not extract JSON from response"}'
188
+ return 1
189
+ }
190
+
191
+ # =============================================================================
192
+ # AGGREGATION
193
+ # =============================================================================
194
+
195
+ # Aggregate peer review scores
196
+ # Usage: aggregate_peer_scores <reviews_dir> <mapping_file> <output_file>
197
+ aggregate_peer_scores() {
198
+ local reviews_dir="$1"
199
+ local mapping_file="$2"
200
+ local output_file="$3"
201
+
202
+ local mapping
203
+ mapping=$(cat "$mapping_file")
204
+
205
+ # Initialize score tracking
206
+ local aggregated='{}'
207
+
208
+ # Process each review file
209
+ for review_file in "$reviews_dir"/review_*.json; do
210
+ if [[ ! -f "$review_file" ]]; then
211
+ continue
212
+ fi
213
+
214
+ local reviewer
215
+ reviewer=$(basename "$review_file" .json | sed 's/review_//')
216
+
217
+ local review_content
218
+ review_content=$(cat "$review_file")
219
+
220
+ # Extract reviews array
221
+ local reviews
222
+ reviews=$(echo "$review_content" | jq -r '.reviews // []' 2>/dev/null)
223
+
224
+ # Process each individual review (using process substitution to avoid subshell)
225
+ while read -r review; do
226
+ [[ -z "$review" ]] && continue
227
+ local response_id score
228
+ response_id=$(echo "$review" | jq -r '.response_id // ""')
229
+ score=$(echo "$review" | jq -r '.quality_score // 5')
230
+
231
+ if [[ -n "$response_id" ]]; then
232
+ # Update aggregated scores
233
+ aggregated=$(echo "$aggregated" | jq \
234
+ --arg id "$response_id" \
235
+ --argjson score "$score" \
236
+ --arg reviewer "$reviewer" \
237
+ '
238
+ if .[$id] then
239
+ .[$id].scores += [$score] |
240
+ .[$id].reviewers += [$reviewer] |
241
+ .[$id].total += $score |
242
+ .[$id].count += 1
243
+ else
244
+ .[$id] = {scores: [$score], reviewers: [$reviewer], total: $score, count: 1}
245
+ end
246
+ ')
247
+ fi
248
+ done < <(echo "$reviews" | jq -c '.[]' 2>/dev/null)
249
+ done
250
+
251
+ # Calculate averages and de-anonymize
252
+ local final_results='[]'
253
+
254
+ for anon_id in $(echo "$aggregated" | jq -r 'keys[]' 2>/dev/null); do
255
+ local data
256
+ data=$(echo "$aggregated" | jq --arg id "$anon_id" '.[$id]')
257
+
258
+ local total count avg_score
259
+ total=$(echo "$data" | jq -r '.total // 0')
260
+ count=$(echo "$data" | jq -r '.count // 1')
261
+ avg_score=$((total / count))
262
+
263
+ # De-anonymize
264
+ local real_consultant
265
+ real_consultant=$(echo "$mapping" | jq -r --arg id "$anon_id" '.[$id].consultant // "unknown"')
266
+
267
+ final_results=$(echo "$final_results" | jq \
268
+ --arg anon "$anon_id" \
269
+ --arg real "$real_consultant" \
270
+ --argjson avg "$avg_score" \
271
+ --argjson count "$count" \
272
+ --argjson data "$data" \
273
+ '. + [{
274
+ anonymous_id: $anon,
275
+ consultant: $real,
276
+ average_peer_score: $avg,
277
+ review_count: $count,
278
+ individual_scores: $data.scores
279
+ }]')
280
+ done
281
+
282
+ # Sort by average score descending
283
+ echo "$final_results" | jq 'sort_by(-.average_peer_score)' > "$output_file"
284
+ }
285
+
286
+ # =============================================================================
287
+ # MAIN
288
+ # =============================================================================
289
+
290
+ main() {
291
+ local responses_dir="${1:-}"
292
+ local output_dir="${2:-}"
293
+
294
+ # Validate inputs
295
+ if [[ -z "$responses_dir" || ! -d "$responses_dir" ]]; then
296
+ log_error "Usage: $0 <responses_dir> <output_dir>"
297
+ exit 1
298
+ fi
299
+
300
+ # Count available responses
301
+ local response_count=0
302
+ for f in "$responses_dir"/*.json; do
303
+ if [[ -f "$f" && -s "$f" && "$(basename "$f")" != "voting.json" && "$(basename "$f")" != "synthesis.json" ]]; then
304
+ ((response_count++))
305
+ fi
306
+ done
307
+
308
+ if [[ $response_count -lt $MIN_CONSULTANTS_FOR_REVIEW ]]; then
309
+ log_warn "Peer review requires at least $MIN_CONSULTANTS_FOR_REVIEW responses (found $response_count)"
310
+ log_info "Skipping peer review"
311
+ exit 0
312
+ fi
313
+
314
+ # Setup output directory
315
+ output_dir="${output_dir:-$responses_dir/peer_review}"
316
+ mkdir -p "$output_dir"
317
+
318
+ log_info "Starting anonymous peer review..."
319
+ log_info "Responses to review: $response_count"
320
+
321
+ # Step 1: Create anonymized versions and mapping
322
+ log_info "Step 1: Anonymizing responses..."
323
+
324
+ local mapping_file="$output_dir/mapping.json"
325
+ local anonymous_dir="$output_dir/anonymous"
326
+ mkdir -p "$anonymous_dir"
327
+
328
+ create_mapping "$responses_dir" "$mapping_file"
329
+
330
+ local index=0
331
+ local anonymous_content=""
332
+
333
+ for f in "$responses_dir"/*.json; do
334
+ if [[ -f "$f" && -s "$f" && "$(basename "$f")" != "voting.json" && "$(basename "$f")" != "synthesis.json" ]]; then
335
+ local consultant
336
+ consultant=$(jq -r '.consultant // "unknown"' "$f" 2>/dev/null)
337
+ local anon_id
338
+ anon_id=$(generate_anonymous_id "$consultant" "$index")
339
+
340
+ # Create anonymized file
341
+ anonymize_response "$f" "$anon_id" > "$anonymous_dir/${anon_id}.json"
342
+
343
+ # Build combined anonymous content for review
344
+ local summary approach
345
+ summary=$(jq -r '.response.summary // "No summary"' "$f" 2>/dev/null)
346
+ approach=$(jq -r '.response.approach // "Unknown"' "$f" 2>/dev/null)
347
+
348
+ anonymous_content+="
349
+ ### $anon_id
350
+ **Approach**: $approach
351
+ **Summary**: $summary
352
+ ---
353
+ "
354
+ ((index++))
355
+ fi
356
+ done
357
+
358
+ # Save combined anonymous responses
359
+ echo "$anonymous_content" > "$output_dir/anonymous_responses.md"
360
+ log_success "Created $index anonymous responses"
361
+
362
+ # Step 2: Run peer reviews (excluding self-review)
363
+ log_info "Step 2: Running peer reviews..."
364
+
365
+ local reviews_dir="$output_dir/reviews"
366
+ mkdir -p "$reviews_dir"
367
+
368
+ # Determine which consultants can do reviews
369
+ local reviewers=()
370
+ [[ "$ENABLE_GEMINI" == "true" ]] && command -v "$GEMINI_CMD" &>/dev/null && reviewers+=("Gemini")
371
+ [[ "$ENABLE_CODEX" == "true" ]] && command -v "$CODEX_CMD" &>/dev/null && reviewers+=("Codex")
372
+ [[ "$ENABLE_MISTRAL" == "true" ]] && command -v "$MISTRAL_CMD" &>/dev/null && reviewers+=("Mistral")
373
+ [[ "$ENABLE_KILO" == "true" ]] && command -v "$KILO_CMD" &>/dev/null && reviewers+=("Kilo")
374
+ [[ "$ENABLE_CURSOR" == "true" ]] && command -v "$CURSOR_CMD" &>/dev/null && reviewers+=("Cursor")
375
+
376
+ if [[ ${#reviewers[@]} -lt 2 ]]; then
377
+ log_warn "Need at least 2 reviewers for peer review"
378
+ exit 0
379
+ fi
380
+
381
+ # Run reviews in parallel
382
+ local pids=()
383
+ for reviewer in "${reviewers[@]}"; do
384
+ local review_output="$reviews_dir/review_${reviewer}.json"
385
+ run_consultant_review "$reviewer" "$output_dir/anonymous_responses.md" "$review_output" &
386
+ pids+=($!)
387
+ log_debug "Started review by $reviewer (PID: ${pids[-1]})"
388
+ done
389
+
390
+ # Wait for all reviews
391
+ local success_count=0
392
+ for i in "${!pids[@]}"; do
393
+ if wait "${pids[$i]}" 2>/dev/null; then
394
+ local reviewer="${reviewers[$i]}"
395
+ local review_file="$reviews_dir/review_${reviewer}.json"
396
+
397
+ if [[ -s "$review_file" ]]; then
398
+ # Try to extract and validate JSON
399
+ local extracted
400
+ extracted=$(extract_json_from_response "$(cat "$review_file")")
401
+ echo "$extracted" > "$review_file"
402
+
403
+ if echo "$extracted" | jq -e '.reviews' > /dev/null 2>&1; then
404
+ log_success " $reviewer: review completed"
405
+ ((success_count++))
406
+ else
407
+ log_warn " $reviewer: invalid review format"
408
+ fi
409
+ fi
410
+ else
411
+ log_warn " ${reviewers[$i]}: review failed"
412
+ fi
413
+ done
414
+
415
+ if [[ $success_count -lt 2 ]]; then
416
+ log_error "Not enough successful reviews for aggregation"
417
+ exit 1
418
+ fi
419
+
420
+ # Step 3: Aggregate scores
421
+ log_info "Step 3: Aggregating peer scores..."
422
+
423
+ local aggregated_file="$output_dir/aggregated_scores.json"
424
+ aggregate_peer_scores "$reviews_dir" "$mapping_file" "$aggregated_file"
425
+
426
+ log_success "Peer review complete"
427
+
428
+ # Step 4: Generate summary report
429
+ log_info "Step 4: Generating summary..."
430
+
431
+ local summary_file="$output_dir/peer_review_summary.json"
432
+ jq -n \
433
+ --argjson scores "$(cat "$aggregated_file")" \
434
+ --argjson mapping "$(cat "$mapping_file")" \
435
+ --argjson review_count "$success_count" \
436
+ --argjson response_count "$response_count" \
437
+ '{
438
+ peer_review: {
439
+ total_responses: $response_count,
440
+ total_reviews: $review_count,
441
+ results: $scores,
442
+ best_rated: ($scores | first),
443
+ mapping: $mapping
444
+ }
445
+ }' > "$summary_file"
446
+
447
+ # Print results
448
+ if [[ -s "$aggregated_file" ]]; then
449
+ echo ""
450
+ log_info "Peer Review Results (ranked by peer score):"
451
+ echo ""
452
+ jq -r '.[] | " \(.consultant): \(.average_peer_score)/10 (reviewed by \(.review_count) peers)"' "$aggregated_file"
453
+ echo ""
454
+ fi
455
+
456
+ log_info "Output directory: $output_dir"
457
+ echo "$output_dir"
458
+ }
459
+
460
+ main "$@"