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,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
|
+
|