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,671 @@
1
+ #!/bin/bash
2
+ # code_optimizer.sh - AST-based code extraction to reduce token usage
3
+ # Part of AI Consultants v2.1
4
+
5
+ # Source common utilities
6
+ _SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ source "$_SCRIPT_DIR/common.sh" 2>/dev/null || true
8
+
9
+ # Logging stubs if common.sh unavailable
10
+ if ! command -v log_debug &>/dev/null; then
11
+ log_debug() { :; }
12
+ log_info() { echo "[INFO] $1" >&2; }
13
+ log_warn() { echo "[WARN] $1" >&2; }
14
+ log_error() { echo "[ERROR] $1" >&2; }
15
+ fi
16
+
17
+ # =============================================================================
18
+ # CONFIGURATION
19
+ # =============================================================================
20
+
21
+ # Enable/disable AST-based extraction (default: true)
22
+ ENABLE_AST_EXTRACTION="${ENABLE_AST_EXTRACTION:-true}"
23
+
24
+ # Maximum lines to include in body summary
25
+ MAX_BODY_SUMMARY_LINES="${MAX_BODY_SUMMARY_LINES:-3}"
26
+
27
+ # Include docstrings in extraction
28
+ INCLUDE_DOCSTRINGS="${INCLUDE_DOCSTRINGS:-true}"
29
+
30
+ # Include critical comments (TODO, FIXME, HACK, NOTE)
31
+ INCLUDE_CRITICAL_COMMENTS="${INCLUDE_CRITICAL_COMMENTS:-true}"
32
+
33
+ # =============================================================================
34
+ # LANGUAGE DETECTION
35
+ # =============================================================================
36
+
37
+ # Detect programming language from file extension
38
+ # Usage: detect_language "/path/to/file.py"
39
+ # Returns: python, javascript, typescript, bash, go, or unknown
40
+ detect_language() {
41
+ local file_path="$1"
42
+ local extension=""
43
+
44
+ # Extract extension (handle files with multiple dots)
45
+ extension="${file_path##*.}"
46
+
47
+ # Convert to lowercase using tr (Bash 3.2 compatible)
48
+ extension=$(echo "$extension" | tr '[:upper:]' '[:lower:]')
49
+
50
+ case "$extension" in
51
+ py|pyw)
52
+ echo "python"
53
+ ;;
54
+ js|mjs|cjs)
55
+ echo "javascript"
56
+ ;;
57
+ ts|tsx|mts|cts)
58
+ echo "typescript"
59
+ ;;
60
+ jsx)
61
+ echo "javascript"
62
+ ;;
63
+ sh|bash|zsh)
64
+ echo "bash"
65
+ ;;
66
+ go)
67
+ echo "go"
68
+ ;;
69
+ rb)
70
+ echo "ruby"
71
+ ;;
72
+ rs)
73
+ echo "rust"
74
+ ;;
75
+ java)
76
+ echo "java"
77
+ ;;
78
+ c|h)
79
+ echo "c"
80
+ ;;
81
+ cpp|cc|cxx|hpp|hxx)
82
+ echo "cpp"
83
+ ;;
84
+ *)
85
+ echo "unknown"
86
+ ;;
87
+ esac
88
+ }
89
+
90
+ # =============================================================================
91
+ # PYTHON EXTRACTOR (AST-based)
92
+ # =============================================================================
93
+
94
+ # Extract code skeleton from Python file using ast module
95
+ # Usage: _extract_python "/path/to/file.py"
96
+ _extract_python() {
97
+ local file_path="$1"
98
+
99
+ # Check if python3 is available
100
+ if ! command -v python3 &>/dev/null; then
101
+ log_warn "python3 not available, falling back to generic extractor"
102
+ _extract_generic "$file_path"
103
+ return
104
+ fi
105
+
106
+ # Convert shell booleans to Python booleans
107
+ local py_include_docstrings="True"
108
+ local py_include_comments="True"
109
+ if [ "$INCLUDE_DOCSTRINGS" = "false" ]; then
110
+ py_include_docstrings="False"
111
+ fi
112
+ if [ "$INCLUDE_CRITICAL_COMMENTS" = "false" ]; then
113
+ py_include_comments="False"
114
+ fi
115
+
116
+ # SECURITY: Pass file path via environment variable to prevent command injection
117
+ CODE_OPTIMIZER_FILE="$file_path" \
118
+ CODE_OPTIMIZER_DOCSTRINGS="$py_include_docstrings" \
119
+ CODE_OPTIMIZER_COMMENTS="$py_include_comments" \
120
+ python3 - <<'PYTHON_SCRIPT' 2>/dev/null
121
+ import ast
122
+ import sys
123
+ import os
124
+
125
+ # Get parameters from environment (safe from injection)
126
+ file_path = os.environ.get('CODE_OPTIMIZER_FILE', '')
127
+ include_docstrings = os.environ.get('CODE_OPTIMIZER_DOCSTRINGS', 'True') == 'True'
128
+ include_comments = os.environ.get('CODE_OPTIMIZER_COMMENTS', 'True') == 'True'
129
+
130
+ def get_docstring(node):
131
+ '''Extract docstring from a node if present'''
132
+ try:
133
+ return ast.get_docstring(node) or ''
134
+ except:
135
+ return ''
136
+
137
+ def format_args(args):
138
+ '''Format function arguments'''
139
+ parts = []
140
+ for arg in args.args:
141
+ annotation = ''
142
+ if arg.annotation:
143
+ try:
144
+ annotation = ': ' + ast.unparse(arg.annotation)
145
+ except:
146
+ pass
147
+ parts.append(arg.arg + annotation)
148
+ if args.vararg:
149
+ parts.append('*' + args.vararg.arg)
150
+ if args.kwarg:
151
+ parts.append('**' + args.kwarg.arg)
152
+ return ', '.join(parts)
153
+
154
+ def get_return_annotation(node):
155
+ '''Get return type annotation'''
156
+ if node.returns:
157
+ try:
158
+ return ' -> ' + ast.unparse(node.returns)
159
+ except:
160
+ pass
161
+ return ''
162
+
163
+ def extract_critical_comments(source):
164
+ '''Extract TODO, FIXME, HACK, NOTE comments'''
165
+ comments = []
166
+ for i, line in enumerate(source.split('\n'), 1):
167
+ line_stripped = line.strip()
168
+ if line_stripped.startswith('#'):
169
+ upper = line_stripped.upper()
170
+ if any(marker in upper for marker in ['TODO', 'FIXME', 'HACK', 'NOTE', 'XXX', 'BUG']):
171
+ comments.append(f' # Line {i}: {line_stripped}')
172
+ return comments
173
+
174
+ try:
175
+ with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
176
+ source = f.read()
177
+
178
+ tree = ast.parse(source)
179
+
180
+ output = []
181
+ output.append(f'# FILE: {file_path}')
182
+ output.append('# LANGUAGE: python')
183
+ output.append('')
184
+
185
+ # Extract imports
186
+ imports = []
187
+ for node in ast.walk(tree):
188
+ if isinstance(node, ast.Import):
189
+ for alias in node.names:
190
+ imports.append(f'import {alias.name}')
191
+ elif isinstance(node, ast.ImportFrom):
192
+ module = node.module or ''
193
+ names = ', '.join(alias.name for alias in node.names)
194
+ imports.append(f'from {module} import {names}')
195
+
196
+ if imports:
197
+ output.append('# IMPORTS:')
198
+ for imp in imports[:20]:
199
+ output.append(imp)
200
+ if len(imports) > 20:
201
+ output.append(f'# ... and {len(imports) - 20} more imports')
202
+ output.append('')
203
+
204
+ # Extract classes and functions
205
+ for node in ast.iter_child_nodes(tree):
206
+ if isinstance(node, ast.ClassDef):
207
+ output.append(f'class {node.name}:')
208
+ docstring = get_docstring(node)
209
+ if docstring and include_docstrings:
210
+ first_line = docstring.split('\n')[0][:100]
211
+ output.append(f' """{ first_line }..."""')
212
+
213
+ methods = []
214
+ for item in node.body:
215
+ if isinstance(item, ast.FunctionDef) or isinstance(item, ast.AsyncFunctionDef):
216
+ async_prefix = 'async ' if isinstance(item, ast.AsyncFunctionDef) else ''
217
+ signature = f'{async_prefix}def {item.name}({format_args(item.args)}){get_return_annotation(item)}'
218
+ methods.append(signature)
219
+
220
+ if methods:
221
+ output.append(' # Methods:')
222
+ for m in methods:
223
+ output.append(f' {m}: ...')
224
+ output.append('')
225
+
226
+ elif isinstance(node, ast.FunctionDef) or isinstance(node, ast.AsyncFunctionDef):
227
+ async_prefix = 'async ' if isinstance(node, ast.AsyncFunctionDef) else ''
228
+ signature = f'{async_prefix}def {node.name}({format_args(node.args)}){get_return_annotation(node)}:'
229
+ output.append(signature)
230
+
231
+ docstring = get_docstring(node)
232
+ if docstring and include_docstrings:
233
+ first_line = docstring.split('\n')[0][:100]
234
+ output.append(f' """{ first_line }..."""')
235
+ output.append(' ...')
236
+ output.append('')
237
+
238
+ # Extract critical comments
239
+ if include_comments:
240
+ comments = extract_critical_comments(source)
241
+ if comments:
242
+ output.append('# CRITICAL COMMENTS:')
243
+ for c in comments[:10]:
244
+ output.append(c)
245
+ output.append('')
246
+
247
+ print('\n'.join(output))
248
+
249
+ except SyntaxError as e:
250
+ print(f'# FILE: {file_path}')
251
+ print('# LANGUAGE: python')
252
+ print(f'# ERROR: Syntax error at line {e.lineno}: {e.msg}')
253
+ print('# Falling back to basic extraction')
254
+ sys.exit(1)
255
+ except Exception as e:
256
+ print(f'# FILE: {file_path}')
257
+ print('# LANGUAGE: python')
258
+ print(f'# ERROR: {str(e)}')
259
+ sys.exit(1)
260
+ PYTHON_SCRIPT
261
+
262
+ local exit_code=$?
263
+ if [ $exit_code -ne 0 ]; then
264
+ log_debug "Python AST extraction failed, using generic extractor"
265
+ _extract_generic "$file_path"
266
+ fi
267
+ }
268
+
269
+ # =============================================================================
270
+ # JAVASCRIPT/TYPESCRIPT EXTRACTOR (Regex-based, ctags-style)
271
+ # =============================================================================
272
+
273
+ # Extract code skeleton from JavaScript/TypeScript file
274
+ # Usage: _extract_javascript "/path/to/file.js"
275
+ _extract_javascript() {
276
+ local file_path="$1"
277
+ local lang="$2"
278
+
279
+ if [ -z "$lang" ]; then
280
+ lang=$(detect_language "$file_path")
281
+ fi
282
+
283
+ echo "// FILE: $file_path"
284
+ echo "// LANGUAGE: $lang"
285
+ echo ""
286
+
287
+ # Extract imports/requires
288
+ echo "// IMPORTS:"
289
+ grep -E -- "^(import |const .* = require\(|export .* from )" "$file_path" 2>/dev/null | head -20
290
+ echo ""
291
+
292
+ # Extract interfaces (TypeScript)
293
+ if [ "$lang" = "typescript" ]; then
294
+ local interfaces
295
+ interfaces=$(grep -E -- "^(export )?(interface|type) [A-Z]" "$file_path" 2>/dev/null)
296
+ if [ -n "$interfaces" ]; then
297
+ echo "// INTERFACES/TYPES:"
298
+ echo "$interfaces" | while IFS= read -r line; do
299
+ # Get interface name and first few properties
300
+ echo "$line"
301
+ done
302
+ echo ""
303
+ fi
304
+ fi
305
+
306
+ # Extract class definitions
307
+ echo "// CLASSES:"
308
+ grep -n -- "^[[:space:]]*\(export \)\?class [A-Za-z]" "$file_path" 2>/dev/null | while IFS= read -r line; do
309
+ local line_num="${line%%:*}"
310
+ local class_line="${line#*:}"
311
+ echo "$class_line"
312
+
313
+ # Extract method signatures from the class (simplified)
314
+ # Look for methods in the next 100 lines after class declaration
315
+ local end_line=$((line_num + 100))
316
+ sed -n "${line_num},${end_line}p" -- "$file_path" 2>/dev/null | \
317
+ grep -E "^[[:space:]]+(async )?(public |private |protected )?(static )?[a-zA-Z_][a-zA-Z0-9_]*\(" | \
318
+ head -10 | while IFS= read -r method; do
319
+ echo " $method"
320
+ done
321
+ done
322
+ echo ""
323
+
324
+ # Extract function declarations
325
+ echo "// FUNCTIONS:"
326
+ grep -E -- "^(export )?(async )?(function [a-zA-Z_]|const [a-zA-Z_][a-zA-Z0-9_]* = (async )?\(|const [a-zA-Z_][a-zA-Z0-9_]* = (async )?function)" "$file_path" 2>/dev/null | head -30
327
+ echo ""
328
+
329
+ # Extract arrow function exports
330
+ grep -E -- "^export (const|let) [a-zA-Z_][a-zA-Z0-9_]* = " "$file_path" 2>/dev/null | head -20
331
+
332
+ # Extract critical comments
333
+ if [ "$INCLUDE_CRITICAL_COMMENTS" = "true" ]; then
334
+ local comments
335
+ comments=$(grep -n -E -- "(//|/\*).*(TODO|FIXME|HACK|NOTE|XXX|BUG)" "$file_path" 2>/dev/null | head -10)
336
+ if [ -n "$comments" ]; then
337
+ echo ""
338
+ echo "// CRITICAL COMMENTS:"
339
+ echo "$comments"
340
+ fi
341
+ fi
342
+ }
343
+
344
+ # =============================================================================
345
+ # BASH EXTRACTOR
346
+ # =============================================================================
347
+
348
+ # Extract code skeleton from Bash script
349
+ # Usage: _extract_bash "/path/to/script.sh"
350
+ _extract_bash() {
351
+ local file_path="$1"
352
+
353
+ echo "# FILE: $file_path"
354
+ echo "# LANGUAGE: bash"
355
+ echo ""
356
+
357
+ # Extract shebang
358
+ local shebang
359
+ shebang=$(head -1 -- "$file_path" 2>/dev/null)
360
+ if [ "${shebang:0:2}" = "#!" ]; then
361
+ echo "# SHEBANG: $shebang"
362
+ echo ""
363
+ fi
364
+
365
+ # Extract source/. includes (must start with source or . followed by space and path)
366
+ echo "# SOURCED FILES:"
367
+ grep -E -- "^[[:space:]]*(source [\"'\$]|\. [\"'\$])" "$file_path" 2>/dev/null | head -10
368
+ echo ""
369
+
370
+ # Extract global variable assignments (uppercase variables)
371
+ echo "# CONFIGURATION VARIABLES:"
372
+ grep -E -- "^[A-Z_][A-Z0-9_]*=" "$file_path" 2>/dev/null | head -20
373
+ echo ""
374
+
375
+ # Extract function definitions
376
+ echo "# FUNCTIONS:"
377
+ grep -n -E -- "^[a-zA-Z_][a-zA-Z0-9_]*\(\)[[:space:]]*\{?" "$file_path" 2>/dev/null | while IFS= read -r line; do
378
+ local _line_num="${line%%:*}"
379
+ local _func_line="${line#*:}"
380
+ local _func_name
381
+ _func_name=$(echo "$_func_line" | sed 's/().*//')
382
+
383
+ # Look for function comment (line before)
384
+ local _prev_line=$((_line_num - 1))
385
+ if [ "$_prev_line" -gt 0 ]; then
386
+ local _comment
387
+ _comment=$(sed -n "${_prev_line}p" -- "$file_path" 2>/dev/null)
388
+ if [ "${_comment:0:1}" = "#" ]; then
389
+ echo "$_comment"
390
+ fi
391
+ fi
392
+ echo "${_func_name}()"
393
+ done
394
+ echo ""
395
+
396
+ # Also check for function keyword style
397
+ grep -E -- "^function [a-zA-Z_]" "$file_path" 2>/dev/null | while IFS= read -r line; do
398
+ local _func_name
399
+ _func_name=$(echo "$line" | sed 's/function //; s/[({].*//')
400
+ echo "${_func_name}()"
401
+ done
402
+
403
+ # Extract critical comments
404
+ if [ "$INCLUDE_CRITICAL_COMMENTS" = "true" ]; then
405
+ local comments
406
+ comments=$(grep -n -E -- "^[[:space:]]*#.*(TODO|FIXME|HACK|NOTE|XXX|BUG)" "$file_path" 2>/dev/null | head -10)
407
+ if [ -n "$comments" ]; then
408
+ echo ""
409
+ echo "# CRITICAL COMMENTS:"
410
+ echo "$comments"
411
+ fi
412
+ fi
413
+ }
414
+
415
+ # =============================================================================
416
+ # GO EXTRACTOR
417
+ # =============================================================================
418
+
419
+ # Extract code skeleton from Go file
420
+ # Usage: _extract_go "/path/to/file.go"
421
+ _extract_go() {
422
+ local file_path="$1"
423
+
424
+ echo "// FILE: $file_path"
425
+ echo "// LANGUAGE: go"
426
+ echo ""
427
+
428
+ # Extract package declaration
429
+ echo "// PACKAGE:"
430
+ grep -E -- "^package " "$file_path" 2>/dev/null | head -1
431
+ echo ""
432
+
433
+ # Extract imports
434
+ echo "// IMPORTS:"
435
+ # Handle both single and multi-line imports
436
+ local in_import_block=false
437
+ while IFS= read -r line; do
438
+ if echo "$line" | grep -qE "^import \("; then
439
+ in_import_block=true
440
+ continue
441
+ fi
442
+ if [ "$in_import_block" = true ]; then
443
+ if echo "$line" | grep -qE "^\)"; then
444
+ in_import_block=false
445
+ continue
446
+ fi
447
+ echo "$line"
448
+ fi
449
+ if echo "$line" | grep -qE "^import \""; then
450
+ echo "$line"
451
+ fi
452
+ done < "$file_path"
453
+ echo ""
454
+
455
+ # Extract type definitions (struct, interface)
456
+ echo "// TYPES:"
457
+ grep -n -E -- "^type [A-Z][a-zA-Z0-9_]* (struct|interface)" "$file_path" 2>/dev/null | while IFS= read -r line; do
458
+ local line_num="${line%%:*}"
459
+ local type_line="${line#*:}"
460
+ echo "$type_line"
461
+
462
+ # Get struct/interface fields (simplified)
463
+ local end_line=$((line_num + 20))
464
+ sed -n "$((line_num + 1)),${end_line}p" -- "$file_path" 2>/dev/null | while IFS= read -r field_line; do
465
+ if echo "$field_line" | grep -qE "^\}"; then
466
+ break
467
+ fi
468
+ # Print field if it starts with a capital letter (exported)
469
+ if echo "$field_line" | grep -qE "^[[:space:]]+[A-Z]"; then
470
+ echo " $field_line"
471
+ fi
472
+ done
473
+ done
474
+ echo ""
475
+
476
+ # Extract function and method definitions
477
+ echo "// FUNCTIONS:"
478
+ grep -E -- "^func " "$file_path" 2>/dev/null | while IFS= read -r line; do
479
+ # Extract just the signature (up to opening brace)
480
+ local signature
481
+ signature=$(echo "$line" | sed 's/ {$//')
482
+ echo "$signature"
483
+ done
484
+ echo ""
485
+
486
+ # Extract critical comments
487
+ if [ "$INCLUDE_CRITICAL_COMMENTS" = "true" ]; then
488
+ local comments
489
+ comments=$(grep -n -E -- "//.*( TODO| FIXME| HACK| NOTE| XXX| BUG)" "$file_path" 2>/dev/null | head -10)
490
+ if [ -n "$comments" ]; then
491
+ echo ""
492
+ echo "// CRITICAL COMMENTS:"
493
+ echo "$comments"
494
+ fi
495
+ fi
496
+ }
497
+
498
+ # =============================================================================
499
+ # GENERIC EXTRACTOR (Fallback)
500
+ # =============================================================================
501
+
502
+ # Generic fallback extractor using common keywords
503
+ # Usage: _extract_generic "/path/to/file"
504
+ _extract_generic() {
505
+ local file_path="$1"
506
+ local lang
507
+ lang=$(detect_language "$file_path")
508
+
509
+ echo "# FILE: $file_path"
510
+ echo "# LANGUAGE: $lang (generic extraction)"
511
+ echo ""
512
+
513
+ # Extract lines with common keywords
514
+ echo "# DEFINITIONS:"
515
+ grep -n -E -- "^[[:space:]]*(public |private |protected )?(static )?(async )?(function |def |class |interface |type |struct |enum |const |let |var |import |from |require|export |module )" "$file_path" 2>/dev/null | head -50
516
+ echo ""
517
+
518
+ # Extract critical comments
519
+ if [ "$INCLUDE_CRITICAL_COMMENTS" = "true" ]; then
520
+ local comments
521
+ comments=$(grep -n -E -- "(#|//|/\*).*(TODO|FIXME|HACK|NOTE|XXX|BUG)" "$file_path" 2>/dev/null | head -10)
522
+ if [ -n "$comments" ]; then
523
+ echo "# CRITICAL COMMENTS:"
524
+ echo "$comments"
525
+ fi
526
+ fi
527
+ }
528
+
529
+ # =============================================================================
530
+ # MAIN OPTIMIZATION FUNCTION
531
+ # =============================================================================
532
+
533
+ # Optimize a code file by extracting semantic skeleton
534
+ # Usage: optimize_code_file "/path/to/file.py"
535
+ # Returns: Optimized code skeleton on stdout
536
+ optimize_code_file() {
537
+ local file_path="$1"
538
+
539
+ # Validate input
540
+ if [ -z "$file_path" ]; then
541
+ log_error "No file path provided to optimize_code_file"
542
+ return 1
543
+ fi
544
+
545
+ if [ ! -f "$file_path" ]; then
546
+ log_error "File not found: $file_path"
547
+ return 1
548
+ fi
549
+
550
+ # Check if extraction is enabled
551
+ if [ "$ENABLE_AST_EXTRACTION" != "true" ]; then
552
+ log_debug "AST extraction disabled, returning raw file"
553
+ cat -- "$file_path"
554
+ return 0
555
+ fi
556
+
557
+ # Detect language
558
+ local lang
559
+ lang=$(detect_language "$file_path")
560
+ log_debug "Detected language: $lang for file: $file_path"
561
+
562
+ # Route to appropriate extractor
563
+ case "$lang" in
564
+ python)
565
+ _extract_python "$file_path"
566
+ ;;
567
+ javascript)
568
+ _extract_javascript "$file_path" "javascript"
569
+ ;;
570
+ typescript)
571
+ _extract_javascript "$file_path" "typescript"
572
+ ;;
573
+ bash)
574
+ _extract_bash "$file_path"
575
+ ;;
576
+ go)
577
+ _extract_go "$file_path"
578
+ ;;
579
+ *)
580
+ log_debug "Using generic extractor for: $lang"
581
+ _extract_generic "$file_path"
582
+ ;;
583
+ esac
584
+ }
585
+
586
+ # =============================================================================
587
+ # BATCH OPTIMIZATION
588
+ # =============================================================================
589
+
590
+ # Optimize multiple files and combine output
591
+ # Usage: optimize_code_files file1.py file2.js ...
592
+ # Returns: Combined optimized skeletons
593
+ optimize_code_files() {
594
+ local files=("$@")
595
+ local total_files="${#files[@]}"
596
+ local processed=0
597
+
598
+ if [ "$total_files" -eq 0 ]; then
599
+ log_warn "No files provided to optimize_code_files"
600
+ return 1
601
+ fi
602
+
603
+ log_info "Optimizing $total_files code file(s)..."
604
+
605
+ for file_path in "${files[@]}"; do
606
+ if [ -f "$file_path" ]; then
607
+ echo "# =============================================="
608
+ optimize_code_file "$file_path"
609
+ echo ""
610
+ processed=$((processed + 1))
611
+ else
612
+ log_warn "Skipping non-existent file: $file_path"
613
+ fi
614
+ done
615
+
616
+ log_success "Optimized $processed of $total_files files"
617
+ }
618
+
619
+ # =============================================================================
620
+ # TOKEN ESTIMATION
621
+ # =============================================================================
622
+
623
+ # Estimate token savings from optimization
624
+ # Usage: estimate_token_savings "/path/to/file.py"
625
+ # Returns: JSON with original_tokens, optimized_tokens, savings_percent
626
+ estimate_token_savings() {
627
+ local file_path="$1"
628
+
629
+ if [ ! -f "$file_path" ]; then
630
+ log_error "File not found: $file_path"
631
+ return 1
632
+ fi
633
+
634
+ # Get original size
635
+ local original_chars
636
+ original_chars=$(wc -c < "$file_path" | tr -d ' ')
637
+ local original_tokens=$((original_chars / 4))
638
+
639
+ # Get optimized size
640
+ local optimized_content
641
+ optimized_content=$(optimize_code_file "$file_path")
642
+ local optimized_chars
643
+ optimized_chars=$(echo -n "$optimized_content" | wc -c | tr -d ' ')
644
+ local optimized_tokens=$((optimized_chars / 4))
645
+
646
+ # Calculate savings
647
+ local savings_percent=0
648
+ if [ "$original_tokens" -gt 0 ]; then
649
+ savings_percent=$(( (original_tokens - optimized_tokens) * 100 / original_tokens ))
650
+ fi
651
+
652
+ # Output JSON
653
+ cat <<EOF
654
+ {
655
+ "file": "$file_path",
656
+ "original_tokens": $original_tokens,
657
+ "optimized_tokens": $optimized_tokens,
658
+ "savings_tokens": $((original_tokens - optimized_tokens)),
659
+ "savings_percent": $savings_percent
660
+ }
661
+ EOF
662
+ }
663
+
664
+ # Check if a file should be optimized based on extension
665
+ # Usage: is_optimizable_file "/path/to/file.py"
666
+ is_optimizable_file() {
667
+ local lang
668
+ lang=$(detect_language "$1")
669
+ [ "$lang" != "unknown" ]
670
+ }
671
+