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,495 @@
1
+ #!/bin/bash
2
+ # voting.sh - Confidence-weighted voting system for AI Consultants
3
+ #
4
+ # Implements weighted voting algorithms to calculate consensus
5
+ # and recommendations based on consultant confidence scores.
6
+
7
+ # =============================================================================
8
+ # CONFIDENCE-WEIGHTED VOTING
9
+ # =============================================================================
10
+
11
+ # Calculate the weighted average of confidence scores
12
+ # Usage: calculate_weighted_average <json_responses_dir>
13
+ calculate_weighted_average() {
14
+ local responses_dir="$1"
15
+ local total_weight=0
16
+ local weighted_sum=0
17
+
18
+ for f in "$responses_dir"/*.json; do
19
+ if [[ -f "$f" && -s "$f" ]]; then
20
+ local confidence=$(jq -r '.confidence.score // 5' "$f" 2>/dev/null)
21
+ weighted_sum=$((weighted_sum + confidence))
22
+ total_weight=$((total_weight + 1))
23
+ fi
24
+ done
25
+
26
+ if [[ $total_weight -gt 0 ]]; then
27
+ echo $((weighted_sum / total_weight))
28
+ else
29
+ echo 5
30
+ fi
31
+ }
32
+
33
+ # Strip common suffixes for basic stemming (portable, no external tools)
34
+ # "sessions" → "session", "caching" → "cach", "authentication" → "authenticat"
35
+ # Usage: stem=$(_stem_word "sessions")
36
+ _stem_word() {
37
+ local w="$1"
38
+ # Longest suffixes first; only strip -ing generically (not consonant+ing
39
+ # variants like *ting/*ring which cause "testing"→"tes", "string"→"st")
40
+ case "$w" in
41
+ *ation) echo "${w%ation}" ;;
42
+ *tion) echo "${w%tion}" ;;
43
+ *sion) echo "${w%sion}" ;;
44
+ *ment) echo "${w%ment}" ;;
45
+ *ness) echo "${w%ness}" ;;
46
+ *ing) echo "${w%ing}" ;;
47
+ *ies) echo "${w%ies}y" ;;
48
+ *ses) echo "${w%es}" ;;
49
+ *es) echo "${w%es}" ;;
50
+ *s) echo "${w%s}" ;;
51
+ *ed) echo "${w%ed}" ;;
52
+ *ly) echo "${w%ly}" ;;
53
+ *) echo "$w" ;;
54
+ esac
55
+ }
56
+
57
+ # Extract keywords from an approach string (portable, Bash 3.2 compatible)
58
+ # Lowercases, removes stop words, applies basic stemming, outputs sorted unique keywords
59
+ # Usage: keywords=$(_extract_keywords "JWT-based authentication")
60
+ _extract_keywords() {
61
+ echo "$1" | tr -cs '[:alnum:]' '\n' | tr '[:upper:]' '[:lower:]' | \
62
+ while IFS= read -r word; do
63
+ case "$word" in
64
+ the|a|an|of|for|to|in|on|with|and|or|is|are|was|were|be|been|being|based|using|use|"") ;;
65
+ *)
66
+ # Apply basic stemming to improve matching
67
+ if [[ ${#word} -gt 3 ]]; then
68
+ _stem_word "$word"
69
+ else
70
+ echo "$word"
71
+ fi
72
+ ;;
73
+ esac
74
+ done | sort -u | tr '\n' ' '
75
+ }
76
+
77
+ # Calculate Jaccard similarity between two keyword strings (integer 0-100)
78
+ # Inputs are already sorted+unique from _extract_keywords, so no re-sorting needed.
79
+ # Uses process substitution instead of temp files to avoid O(n^2) filesystem ops.
80
+ # Usage: sim=$(_jaccard_similarity "jwt auth tokens" "jwt token authentication")
81
+ _jaccard_similarity() {
82
+ local set_a="$1"
83
+ local set_b="$2"
84
+
85
+ local intersection union_count
86
+ intersection=$(comm -12 <(echo "$set_a" | tr ' ' '\n' | grep -v '^$') \
87
+ <(echo "$set_b" | tr ' ' '\n' | grep -v '^$') | wc -l | tr -d ' ')
88
+ union_count=$(echo "$set_a $set_b" | tr ' ' '\n' | sort -u | grep -v '^$' | wc -l | tr -d ' ')
89
+
90
+ if [[ $union_count -gt 0 ]]; then
91
+ echo $((intersection * 100 / union_count))
92
+ else
93
+ echo 0
94
+ fi
95
+ }
96
+
97
+ # Calculate consensus score based on keyword overlap between approaches
98
+ # Uses Jaccard similarity instead of exact string matching so that
99
+ # "JWT tokens", "JWT-based auth", and "JSON Web Tokens" are recognized as similar.
100
+ # Usage: calculate_consensus_score <json_responses_dir>
101
+ calculate_consensus_score() {
102
+ local responses_dir="$1"
103
+ local approaches=()
104
+ local keyword_sets=()
105
+
106
+ # Collect all approaches and extract keywords, skipping "unknown" approaches
107
+ for f in "$responses_dir"/*.json; do
108
+ if [[ -f "$f" && -s "$f" ]]; then
109
+ local approach
110
+ approach=$(jq -r '.response.approach // "unknown"' "$f" 2>/dev/null)
111
+ # Skip unknown/empty approaches — they add noise without signal
112
+ local approach_lower
113
+ approach_lower=$(echo "$approach" | tr '[:upper:]' '[:lower:]')
114
+ case "$approach_lower" in
115
+ unknown|""|n/a|none|"not available") continue ;;
116
+ esac
117
+ approaches+=("$approach")
118
+ keyword_sets+=("$(_extract_keywords "$approach")")
119
+ fi
120
+ done
121
+
122
+ local total=${#approaches[@]}
123
+ if [[ $total -le 1 ]]; then
124
+ [[ $total -eq 1 ]] && echo 100 || echo 0
125
+ return
126
+ fi
127
+
128
+ # Calculate pairwise Jaccard similarity
129
+ local similar_pairs=0
130
+ local total_pairs=0
131
+ local threshold=20 # Jaccard >= 0.20 means approaches are similar (lowered from 30 to account for verbose names)
132
+
133
+ local i j
134
+ for ((i=0; i<total; i++)); do
135
+ for ((j=i+1; j<total; j++)); do
136
+ total_pairs=$((total_pairs + 1))
137
+ local sim
138
+ sim=$(_jaccard_similarity "${keyword_sets[$i]}" "${keyword_sets[$j]}")
139
+ if [[ $sim -ge $threshold ]]; then
140
+ similar_pairs=$((similar_pairs + 1))
141
+ fi
142
+ done
143
+ done
144
+
145
+ # Score = percentage of pairs that are similar
146
+ if [[ $total_pairs -gt 0 ]]; then
147
+ echo $((similar_pairs * 100 / total_pairs))
148
+ else
149
+ echo 100
150
+ fi
151
+ }
152
+
153
+ # Determine the consensus level from a score
154
+ # Usage: get_consensus_level <score>
155
+ get_consensus_level() {
156
+ local score=$1
157
+
158
+ if [[ $score -ge 100 ]]; then
159
+ echo "unanimous"
160
+ elif [[ $score -ge 75 ]]; then
161
+ echo "high"
162
+ elif [[ $score -ge 50 ]]; then
163
+ echo "medium"
164
+ elif [[ $score -ge 25 ]]; then
165
+ echo "low"
166
+ else
167
+ echo "none"
168
+ fi
169
+ }
170
+
171
+ # Calculate the confidence-weighted recommendation
172
+ # Usage: calculate_weighted_recommendation <json_responses_dir>
173
+ # Output: JSON with the recommendation
174
+ calculate_weighted_recommendation() {
175
+ local responses_dir="$1"
176
+
177
+ # Clear any previous map state
178
+ map_clear "APPROACH_WEIGHTS"
179
+ map_clear "APPROACH_SUPPORTERS"
180
+
181
+ # Collect weights for each approach
182
+ for f in "$responses_dir"/*.json; do
183
+ if [[ -f "$f" && -s "$f" ]]; then
184
+ local consultant approach confidence current_weight current_supporters
185
+ consultant=$(jq -r '.consultant // "unknown"' "$f" 2>/dev/null)
186
+ approach=$(jq -r '.response.approach // "unknown"' "$f" 2>/dev/null)
187
+ confidence=$(jq -r '.confidence.score // 5' "$f" 2>/dev/null)
188
+
189
+ # Add weight
190
+ current_weight=$(map_get "APPROACH_WEIGHTS" "$approach")
191
+ map_set "APPROACH_WEIGHTS" "$approach" "$((${current_weight:-0} + confidence))"
192
+
193
+ # Add supporter
194
+ current_supporters=$(map_get "APPROACH_SUPPORTERS" "$approach")
195
+ if [[ -z "$current_supporters" ]]; then
196
+ map_set "APPROACH_SUPPORTERS" "$approach" "$consultant"
197
+ else
198
+ map_set "APPROACH_SUPPORTERS" "$approach" "${current_supporters},$consultant"
199
+ fi
200
+ fi
201
+ done
202
+
203
+ # Find the approach with the highest weight using map_keys
204
+ local best_approach=""
205
+ local best_weight=0
206
+ local best_supporters=""
207
+ local approach weight
208
+
209
+ for approach in $(map_keys "APPROACH_WEIGHTS"); do
210
+ weight=$(map_get "APPROACH_WEIGHTS" "$approach")
211
+ weight="${weight:-0}"
212
+ if [[ $weight -gt $best_weight ]]; then
213
+ best_weight=$weight
214
+ best_approach="$approach"
215
+ best_supporters=$(map_get "APPROACH_SUPPORTERS" "$approach")
216
+ fi
217
+ done
218
+
219
+ # Output JSON
220
+ jq -n \
221
+ --arg approach "$best_approach" \
222
+ --argjson weight "$best_weight" \
223
+ --arg supporters "$best_supporters" \
224
+ '{
225
+ recommended_approach: $approach,
226
+ total_weight: $weight,
227
+ supporters: ($supporters | split(","))
228
+ }'
229
+ }
230
+
231
+ # Identify dissenters (those who do not support the winning approach)
232
+ # Usage: get_dissenters <json_responses_dir> <winning_approach>
233
+ get_dissenters() {
234
+ local responses_dir="$1"
235
+ local winning_approach="$2"
236
+ local dissenters=()
237
+
238
+ for f in "$responses_dir"/*.json; do
239
+ if [[ -f "$f" && -s "$f" ]]; then
240
+ local consultant=$(jq -r '.consultant // "unknown"' "$f" 2>/dev/null)
241
+ local approach=$(jq -r '.response.approach // "unknown"' "$f" 2>/dev/null)
242
+
243
+ if [[ "$approach" != "$winning_approach" ]]; then
244
+ dissenters+=("$consultant")
245
+ fi
246
+ fi
247
+ done
248
+
249
+ printf '%s\n' ${dissenters[@]+"${dissenters[@]}"}
250
+ }
251
+
252
+ # Calculate confidence-weighted final score
253
+ # Usage: calculate_final_score <json_responses_dir> <winning_approach>
254
+ calculate_final_score() {
255
+ local responses_dir="$1"
256
+ local winning_approach="$2"
257
+
258
+ local weighted_sum=0
259
+ local total_confidence=0
260
+
261
+ for f in "$responses_dir"/*.json; do
262
+ if [[ -f "$f" && -s "$f" ]]; then
263
+ local approach=$(jq -r '.response.approach // "unknown"' "$f" 2>/dev/null)
264
+ local confidence=$(jq -r '.confidence.score // 5' "$f" 2>/dev/null)
265
+
266
+ total_confidence=$((total_confidence + confidence))
267
+
268
+ # Full weight if supports, half if neutral, zero if dissents
269
+ if [[ "$approach" == "$winning_approach" ]]; then
270
+ weighted_sum=$((weighted_sum + confidence * 10)) # Full support
271
+ else
272
+ weighted_sum=$((weighted_sum + confidence * 2)) # Dissent but consider
273
+ fi
274
+ fi
275
+ done
276
+
277
+ if [[ $total_confidence -gt 0 ]]; then
278
+ # Normalize to 1-10 scale
279
+ local score=$((weighted_sum / total_confidence))
280
+ if [[ $score -gt 10 ]]; then
281
+ score=10
282
+ fi
283
+ echo $score
284
+ else
285
+ echo 5
286
+ fi
287
+ }
288
+
289
+ # =============================================================================
290
+ # MAJORITY VOTING (simple)
291
+ # =============================================================================
292
+
293
+ # Simple majority vote (unweighted)
294
+ # Usage: simple_majority_vote <json_responses_dir>
295
+ simple_majority_vote() {
296
+ local responses_dir="$1"
297
+
298
+ # Clear any previous map state
299
+ map_clear "MAJORITY_VOTES"
300
+
301
+ for f in "$responses_dir"/*.json; do
302
+ if [[ -f "$f" && -s "$f" ]]; then
303
+ local approach current_votes
304
+ approach=$(jq -r '.response.approach // "unknown"' "$f" 2>/dev/null)
305
+ current_votes=$(map_get "MAJORITY_VOTES" "$approach")
306
+ map_set "MAJORITY_VOTES" "$approach" "$((${current_votes:-0} + 1))"
307
+ fi
308
+ done
309
+
310
+ # Find approach with most votes using map_keys
311
+ local winner=""
312
+ local max_votes=0
313
+ local approach votes
314
+
315
+ for approach in $(map_keys "MAJORITY_VOTES"); do
316
+ votes=$(map_get "MAJORITY_VOTES" "$approach")
317
+ votes="${votes:-0}"
318
+ if [[ $votes -gt $max_votes ]]; then
319
+ max_votes=$votes
320
+ winner="$approach"
321
+ fi
322
+ done
323
+
324
+ echo "$winner"
325
+ }
326
+
327
+ # =============================================================================
328
+ # CONFIDENCE INTERVALS (v2.2)
329
+ # =============================================================================
330
+
331
+ # Calculate standard deviation of confidence scores
332
+ # Usage: calculate_confidence_stddev <json_responses_dir>
333
+ # Returns: Standard deviation value (integer, scaled by 10 for precision)
334
+ calculate_confidence_stddev() {
335
+ local responses_dir="$1"
336
+ local scores=()
337
+ local sum=0
338
+ local count=0
339
+
340
+ # Collect all confidence scores
341
+ for f in "$responses_dir"/*.json; do
342
+ if [[ -f "$f" && -s "$f" ]]; then
343
+ local score=$(jq -r '.confidence.score // 5' "$f" 2>/dev/null)
344
+ scores+=("$score")
345
+ sum=$((sum + score))
346
+ count=$((count + 1))
347
+ fi
348
+ done
349
+
350
+ if [[ $count -lt 2 ]]; then
351
+ echo 0
352
+ return
353
+ fi
354
+
355
+ # Calculate mean (scaled by 10 for precision)
356
+ local mean_scaled=$((sum * 10 / count))
357
+
358
+ # Calculate variance
359
+ local variance_sum=0
360
+ for score in "${scores[@]}"; do
361
+ local diff=$((score * 10 - mean_scaled))
362
+ variance_sum=$((variance_sum + diff * diff))
363
+ done
364
+ local variance=$((variance_sum / count))
365
+
366
+ # Approximate square root using Newton's method (integer math)
367
+ # For small numbers, this gives reasonable results
368
+ if [[ $variance -eq 0 ]]; then
369
+ echo 0
370
+ return
371
+ fi
372
+
373
+ local sqrt=$variance
374
+ local prev=0
375
+ for _ in {1..10}; do
376
+ prev=$sqrt
377
+ # Guard against division by zero
378
+ [[ $sqrt -eq 0 ]] && sqrt=1
379
+ sqrt=$(( (sqrt + variance / sqrt) / 2 ))
380
+ [[ $sqrt -eq $prev ]] && break
381
+ done
382
+
383
+ # Return scaled by 10 (so 15 means 1.5)
384
+ echo $sqrt
385
+ }
386
+
387
+ # Calculate confidence interval
388
+ # Usage: calculate_confidence_interval <json_responses_dir>
389
+ # Output: JSON with mean, stddev, low, high bounds
390
+ calculate_confidence_interval() {
391
+ local responses_dir="$1"
392
+
393
+ local mean=$(calculate_weighted_average "$responses_dir")
394
+ local stddev_scaled=$(calculate_confidence_stddev "$responses_dir")
395
+
396
+ # Convert scaled stddev back to decimal format for display
397
+ local stddev_int=$((stddev_scaled / 10))
398
+ local stddev_dec=$((stddev_scaled % 10))
399
+
400
+ # Calculate bounds (mean ± stddev)
401
+ local low=$((mean * 10 - stddev_scaled))
402
+ local high=$((mean * 10 + stddev_scaled))
403
+
404
+ # Clamp to valid range [1, 10]
405
+ [[ $low -lt 10 ]] && low=10
406
+ [[ $high -gt 100 ]] && high=100
407
+
408
+ local low_int=$((low / 10))
409
+ local low_dec=$((low % 10))
410
+ local high_int=$((high / 10))
411
+ local high_dec=$((high % 10))
412
+
413
+ jq -n \
414
+ --argjson mean "$mean" \
415
+ --arg stddev "${stddev_int}.${stddev_dec}" \
416
+ --arg low "${low_int}.${low_dec}" \
417
+ --arg high "${high_int}.${high_dec}" \
418
+ --argjson variance_high "$([ $stddev_scaled -gt 20 ] && echo "true" || echo "false")" \
419
+ '{
420
+ mean: $mean,
421
+ stddev: ($stddev | tonumber),
422
+ interval: {
423
+ low: ($low | tonumber),
424
+ high: ($high | tonumber)
425
+ },
426
+ display: "\($mean) ± \($stddev)",
427
+ high_variance: $variance_high
428
+ }'
429
+ }
430
+
431
+ # Check if confidence scores have high variance (uncertainty indicator)
432
+ # Usage: has_high_confidence_variance <json_responses_dir>
433
+ # Returns: 0 (true) if variance is high, 1 (false) otherwise
434
+ has_high_confidence_variance() {
435
+ local responses_dir="$1"
436
+ local stddev_scaled=$(calculate_confidence_stddev "$responses_dir")
437
+
438
+ # Consider variance "high" if stddev > 2.0 (scaled value > 20)
439
+ [[ $stddev_scaled -gt 20 ]]
440
+ }
441
+
442
+ # Get confidence range as formatted string
443
+ # Usage: format_confidence_range <json_responses_dir>
444
+ # Output: "7 ± 1.5" or "7 (±1.5, high variance!)"
445
+ format_confidence_range() {
446
+ local responses_dir="$1"
447
+ local interval_json=$(calculate_confidence_interval "$responses_dir")
448
+
449
+ local display=$(echo "$interval_json" | jq -r '.display')
450
+ local high_variance=$(echo "$interval_json" | jq -r '.high_variance')
451
+
452
+ if [[ "$high_variance" == "true" ]]; then
453
+ echo "$display (high variance - uncertainty detected)"
454
+ else
455
+ echo "$display"
456
+ fi
457
+ }
458
+
459
+ # =============================================================================
460
+ # UTILITY
461
+ # =============================================================================
462
+
463
+ # Generate complete voting report
464
+ # Usage: generate_voting_report <json_responses_dir>
465
+ generate_voting_report() {
466
+ local responses_dir="$1"
467
+
468
+ local consensus_score=$(calculate_consensus_score "$responses_dir")
469
+ local consensus_level=$(get_consensus_level "$consensus_score")
470
+ local avg_confidence=$(calculate_weighted_average "$responses_dir")
471
+ local confidence_interval=$(calculate_confidence_interval "$responses_dir")
472
+ local recommendation=$(calculate_weighted_recommendation "$responses_dir")
473
+ local winning_approach=$(echo "$recommendation" | jq -r '.recommended_approach')
474
+ local final_score=$(calculate_final_score "$responses_dir" "$winning_approach")
475
+
476
+ jq -n \
477
+ --argjson consensus_score "$consensus_score" \
478
+ --arg consensus_level "$consensus_level" \
479
+ --argjson avg_confidence "$avg_confidence" \
480
+ --argjson confidence_interval "$confidence_interval" \
481
+ --argjson recommendation "$recommendation" \
482
+ --argjson final_score "$final_score" \
483
+ '{
484
+ voting_report: {
485
+ consensus: {
486
+ score: $consensus_score,
487
+ level: $consensus_level
488
+ },
489
+ average_confidence: $avg_confidence,
490
+ confidence_interval: $confidence_interval,
491
+ recommendation: $recommendation,
492
+ final_weighted_score: $final_score
493
+ }
494
+ }'
495
+ }