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,445 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# symbol_map.sh - Symbol compression for AI Consultants v2.1
|
|
3
|
+
# Replaces long identifiers with short symbols (f1, v1, c1) to reduce tokens.
|
|
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
|
+
# Map function stubs if common.sh unavailable
|
|
18
|
+
if ! command -v map_set &>/dev/null; then
|
|
19
|
+
_SYMBOL_MAP_FILE="/tmp/symbol_map_$$"
|
|
20
|
+
map_clear() { rm -f "${_SYMBOL_MAP_FILE}_$1" 2>/dev/null; touch "${_SYMBOL_MAP_FILE}_$1"; }
|
|
21
|
+
map_set() { echo "$2=$3" >> "${_SYMBOL_MAP_FILE}_$1"; }
|
|
22
|
+
map_get() { grep "^$2=" "${_SYMBOL_MAP_FILE}_$1" 2>/dev/null | tail -1 | cut -d= -f2-; }
|
|
23
|
+
map_has() { grep -q "^$2=" "${_SYMBOL_MAP_FILE}_$1" 2>/dev/null; }
|
|
24
|
+
map_keys() { cut -d= -f1 "${_SYMBOL_MAP_FILE}_$1" 2>/dev/null | sort -u | tr '\n' ' '; }
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# =============================================================================
|
|
28
|
+
# CONFIGURATION
|
|
29
|
+
# =============================================================================
|
|
30
|
+
|
|
31
|
+
# Enable/disable symbol compression
|
|
32
|
+
ENABLE_SYMBOL_COMPRESSION="${ENABLE_SYMBOL_COMPRESSION:-true}"
|
|
33
|
+
|
|
34
|
+
# Minimum identifier length to compress (shorter ones are kept as-is)
|
|
35
|
+
MIN_IDENTIFIER_LENGTH="${MIN_IDENTIFIER_LENGTH:-8}"
|
|
36
|
+
|
|
37
|
+
# Preserve exported/public names (function exports, class exports)
|
|
38
|
+
PRESERVE_EXPORTS="${PRESERVE_EXPORTS:-true}"
|
|
39
|
+
|
|
40
|
+
# =============================================================================
|
|
41
|
+
# LANGUAGE KEYWORDS (preserved, not compressed)
|
|
42
|
+
# =============================================================================
|
|
43
|
+
|
|
44
|
+
# Common keywords across multiple languages
|
|
45
|
+
KEYWORDS_COMMON="if else elif fi then for while do done case esac in return break continue function"
|
|
46
|
+
|
|
47
|
+
# JavaScript/TypeScript keywords
|
|
48
|
+
KEYWORDS_JS="const let var function class extends implements interface type enum async await import export default from new this super static public private protected readonly typeof instanceof void null undefined true false try catch finally throw"
|
|
49
|
+
|
|
50
|
+
# Python keywords
|
|
51
|
+
KEYWORDS_PYTHON="def class return if elif else for while in not and or is None True False import from as try except finally raise with lambda pass break continue global nonlocal assert yield del"
|
|
52
|
+
|
|
53
|
+
# Go keywords
|
|
54
|
+
KEYWORDS_GO="func package import var const type struct interface map chan go defer return if else for range switch case default break continue fallthrough select"
|
|
55
|
+
|
|
56
|
+
# Java/C# keywords
|
|
57
|
+
KEYWORDS_JAVA="public private protected static final abstract class interface extends implements new this super return if else for while do switch case default break continue try catch finally throw throws void int long float double boolean char String null true false import package"
|
|
58
|
+
|
|
59
|
+
# Rust keywords
|
|
60
|
+
KEYWORDS_RUST="fn let mut const static struct enum impl trait pub mod use crate self super where async await move return if else for while loop match break continue"
|
|
61
|
+
|
|
62
|
+
# Bash keywords
|
|
63
|
+
KEYWORDS_BASH="if then else elif fi case esac for while until do done in function return exit local export readonly declare typeset source"
|
|
64
|
+
|
|
65
|
+
# All keywords combined (space-separated)
|
|
66
|
+
ALL_KEYWORDS="$KEYWORDS_COMMON $KEYWORDS_JS $KEYWORDS_PYTHON $KEYWORDS_GO $KEYWORDS_JAVA $KEYWORDS_RUST $KEYWORDS_BASH"
|
|
67
|
+
|
|
68
|
+
# =============================================================================
|
|
69
|
+
# BUILT-IN FUNCTIONS (preserved)
|
|
70
|
+
# =============================================================================
|
|
71
|
+
|
|
72
|
+
BUILTINS_JS="console log warn error info debug setTimeout setInterval clearTimeout clearInterval fetch Promise JSON Math Array Object String Number Date RegExp Map Set parseInt parseFloat isNaN isFinite encodeURI decodeURI require module exports"
|
|
73
|
+
|
|
74
|
+
BUILTINS_PYTHON="print len range type str int float list dict set tuple bool open input abs min max sum sorted reversed enumerate zip map filter any all isinstance issubclass hasattr getattr setattr delattr"
|
|
75
|
+
|
|
76
|
+
BUILTINS_GO="fmt Println Printf Sprintf Errorf make len cap append copy delete panic recover new close"
|
|
77
|
+
|
|
78
|
+
BUILTINS_BASH="echo printf read eval exec source exit return test true false cd pwd ls cat grep sed awk cut sort uniq wc head tail tr"
|
|
79
|
+
|
|
80
|
+
ALL_BUILTINS="$BUILTINS_JS $BUILTINS_PYTHON $BUILTINS_GO $BUILTINS_BASH"
|
|
81
|
+
|
|
82
|
+
# =============================================================================
|
|
83
|
+
# SYMBOL COUNTERS (use file-based counters for Bash 3.2 subshell compatibility)
|
|
84
|
+
# =============================================================================
|
|
85
|
+
|
|
86
|
+
# Temporary files (avoids subshell variable isolation issues)
|
|
87
|
+
_SYM_COUNTER_FILE="${TMPDIR:-/tmp}/.symbol_map_counters_$$"
|
|
88
|
+
_SYM_MAP_FILE="${TMPDIR:-/tmp}/.symbol_map_json_$$"
|
|
89
|
+
|
|
90
|
+
# Initialize counter file
|
|
91
|
+
_init_symbol_counters() {
|
|
92
|
+
cat > "$_SYM_COUNTER_FILE" << 'EOF'
|
|
93
|
+
_SYM_FUNC_COUNTER=0
|
|
94
|
+
_SYM_VAR_COUNTER=0
|
|
95
|
+
_SYM_CLASS_COUNTER=0
|
|
96
|
+
_SYM_METHOD_COUNTER=0
|
|
97
|
+
_SYM_TYPE_COUNTER=0
|
|
98
|
+
EOF
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# Reset all counters
|
|
102
|
+
_reset_symbol_counters() {
|
|
103
|
+
_init_symbol_counters
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# Load current counter values
|
|
107
|
+
_load_symbol_counters() {
|
|
108
|
+
if [[ -f "$_SYM_COUNTER_FILE" ]]; then
|
|
109
|
+
source "$_SYM_COUNTER_FILE"
|
|
110
|
+
else
|
|
111
|
+
_init_symbol_counters
|
|
112
|
+
source "$_SYM_COUNTER_FILE"
|
|
113
|
+
fi
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# Save counter values
|
|
117
|
+
_save_symbol_counters() {
|
|
118
|
+
cat > "$_SYM_COUNTER_FILE" << EOF
|
|
119
|
+
_SYM_FUNC_COUNTER=$_SYM_FUNC_COUNTER
|
|
120
|
+
_SYM_VAR_COUNTER=$_SYM_VAR_COUNTER
|
|
121
|
+
_SYM_CLASS_COUNTER=$_SYM_CLASS_COUNTER
|
|
122
|
+
_SYM_METHOD_COUNTER=$_SYM_METHOD_COUNTER
|
|
123
|
+
_SYM_TYPE_COUNTER=$_SYM_TYPE_COUNTER
|
|
124
|
+
EOF
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Cleanup temporary files on exit
|
|
128
|
+
_cleanup_symbol_counters() {
|
|
129
|
+
rm -f "$_SYM_COUNTER_FILE" 2>/dev/null
|
|
130
|
+
rm -f "$_SYM_MAP_FILE" 2>/dev/null
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# Save symbol map to file for cross-subshell access
|
|
134
|
+
_save_symbol_map() {
|
|
135
|
+
local map_json="$1"
|
|
136
|
+
echo "$map_json" > "$_SYM_MAP_FILE"
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# Load symbol map from file
|
|
140
|
+
_load_symbol_map() {
|
|
141
|
+
if [[ -f "$_SYM_MAP_FILE" ]]; then
|
|
142
|
+
cat "$_SYM_MAP_FILE"
|
|
143
|
+
else
|
|
144
|
+
echo "{}"
|
|
145
|
+
fi
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# Register cleanup (only if not already registered)
|
|
149
|
+
if [[ -z "${_SYM_CLEANUP_REGISTERED:-}" ]]; then
|
|
150
|
+
trap _cleanup_symbol_counters EXIT
|
|
151
|
+
_SYM_CLEANUP_REGISTERED=1
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# =============================================================================
|
|
155
|
+
# HELPER FUNCTIONS
|
|
156
|
+
# =============================================================================
|
|
157
|
+
|
|
158
|
+
# Check if a word is a keyword or builtin
|
|
159
|
+
# Usage: _is_preserved_word "word"
|
|
160
|
+
# Returns: 0 if preserved, 1 if not
|
|
161
|
+
_is_preserved_word() {
|
|
162
|
+
local word="$1"
|
|
163
|
+
local preserved=" $ALL_KEYWORDS $ALL_BUILTINS "
|
|
164
|
+
|
|
165
|
+
if [[ "$preserved" == *" $word "* ]]; then
|
|
166
|
+
return 0
|
|
167
|
+
fi
|
|
168
|
+
return 1
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# Check if identifier should be preserved (export, short, or keyword)
|
|
172
|
+
# Usage: _should_preserve "identifier" "is_exported"
|
|
173
|
+
_should_preserve() {
|
|
174
|
+
local identifier="$1"
|
|
175
|
+
local is_exported="${2:-false}"
|
|
176
|
+
|
|
177
|
+
# Preserve keywords and builtins
|
|
178
|
+
if _is_preserved_word "$identifier"; then
|
|
179
|
+
return 0
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
# Preserve short identifiers
|
|
183
|
+
local len=${#identifier}
|
|
184
|
+
if [[ $len -lt $MIN_IDENTIFIER_LENGTH ]]; then
|
|
185
|
+
return 0
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
# Preserve exports if configured
|
|
189
|
+
if [[ "$PRESERVE_EXPORTS" == "true" && "$is_exported" == "true" ]]; then
|
|
190
|
+
return 0
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
return 1
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# Get next symbol for a category
|
|
197
|
+
# Usage: _get_next_symbol "function|variable|class|method|type"
|
|
198
|
+
# Note: Uses file-based counters for subshell compatibility
|
|
199
|
+
_get_next_symbol() {
|
|
200
|
+
local category="$1"
|
|
201
|
+
local symbol=""
|
|
202
|
+
|
|
203
|
+
# Load current counter values from file
|
|
204
|
+
_load_symbol_counters
|
|
205
|
+
|
|
206
|
+
case "$category" in
|
|
207
|
+
function)
|
|
208
|
+
_SYM_FUNC_COUNTER=$((_SYM_FUNC_COUNTER + 1))
|
|
209
|
+
symbol="f${_SYM_FUNC_COUNTER}"
|
|
210
|
+
;;
|
|
211
|
+
variable)
|
|
212
|
+
_SYM_VAR_COUNTER=$((_SYM_VAR_COUNTER + 1))
|
|
213
|
+
symbol="v${_SYM_VAR_COUNTER}"
|
|
214
|
+
;;
|
|
215
|
+
class)
|
|
216
|
+
_SYM_CLASS_COUNTER=$((_SYM_CLASS_COUNTER + 1))
|
|
217
|
+
symbol="c${_SYM_CLASS_COUNTER}"
|
|
218
|
+
;;
|
|
219
|
+
method)
|
|
220
|
+
_SYM_METHOD_COUNTER=$((_SYM_METHOD_COUNTER + 1))
|
|
221
|
+
symbol="m${_SYM_METHOD_COUNTER}"
|
|
222
|
+
;;
|
|
223
|
+
type)
|
|
224
|
+
_SYM_TYPE_COUNTER=$((_SYM_TYPE_COUNTER + 1))
|
|
225
|
+
symbol="t${_SYM_TYPE_COUNTER}"
|
|
226
|
+
;;
|
|
227
|
+
*)
|
|
228
|
+
_SYM_VAR_COUNTER=$((_SYM_VAR_COUNTER + 1))
|
|
229
|
+
symbol="v${_SYM_VAR_COUNTER}"
|
|
230
|
+
;;
|
|
231
|
+
esac
|
|
232
|
+
|
|
233
|
+
# Save updated counters back to file
|
|
234
|
+
_save_symbol_counters
|
|
235
|
+
|
|
236
|
+
echo "$symbol"
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
# Detect identifier category based on context
|
|
240
|
+
# Usage: _detect_category "identifier" "context_before"
|
|
241
|
+
_detect_category() {
|
|
242
|
+
local identifier="$1"
|
|
243
|
+
local context="$2"
|
|
244
|
+
|
|
245
|
+
# Function detection patterns
|
|
246
|
+
if echo "$context" | grep -qE '(function|def|func|fn)\s*$'; then
|
|
247
|
+
echo "function"
|
|
248
|
+
return
|
|
249
|
+
fi
|
|
250
|
+
|
|
251
|
+
# Method detection (after dot or within class)
|
|
252
|
+
if echo "$context" | grep -qE '\.\s*$'; then
|
|
253
|
+
echo "method"
|
|
254
|
+
return
|
|
255
|
+
fi
|
|
256
|
+
|
|
257
|
+
# Class detection patterns
|
|
258
|
+
if echo "$context" | grep -qE '(class|struct|interface|type)\s*$'; then
|
|
259
|
+
echo "class"
|
|
260
|
+
return
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
# Type detection (after colon for type annotations)
|
|
264
|
+
if echo "$context" | grep -qE ':\s*$'; then
|
|
265
|
+
echo "type"
|
|
266
|
+
return
|
|
267
|
+
fi
|
|
268
|
+
|
|
269
|
+
# Variable detection (after assignment operators, var, let, const)
|
|
270
|
+
if echo "$context" | grep -qE '(var|let|const|local)\s*$'; then
|
|
271
|
+
echo "variable"
|
|
272
|
+
return
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
# Default to variable for unrecognized patterns
|
|
276
|
+
echo "variable"
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
# =============================================================================
|
|
280
|
+
# MAIN FUNCTIONS
|
|
281
|
+
# =============================================================================
|
|
282
|
+
|
|
283
|
+
# Generate a symbol map from code
|
|
284
|
+
# Extracts identifiers and creates mapping for compression
|
|
285
|
+
#
|
|
286
|
+
# Usage: generate_symbol_map "code"
|
|
287
|
+
# Output: JSON object with mappings { "originalName": {"symbol": "f1", "category": "function"}, ... }
|
|
288
|
+
generate_symbol_map() {
|
|
289
|
+
local code="$1"
|
|
290
|
+
|
|
291
|
+
if [[ "$ENABLE_SYMBOL_COMPRESSION" != "true" ]]; then
|
|
292
|
+
echo "{}"
|
|
293
|
+
return
|
|
294
|
+
fi
|
|
295
|
+
|
|
296
|
+
# Reset counters for fresh mapping
|
|
297
|
+
_reset_symbol_counters
|
|
298
|
+
|
|
299
|
+
# Clear any previous mapping
|
|
300
|
+
map_clear "SYMMAP"
|
|
301
|
+
map_clear "SYMMAP_CATEGORY"
|
|
302
|
+
|
|
303
|
+
# Remove string literals and comments to avoid extracting identifiers from them
|
|
304
|
+
local clean_code
|
|
305
|
+
clean_code=$(echo "$code" | sed -E '
|
|
306
|
+
# Remove single-line comments (// and #)
|
|
307
|
+
s|//[^"]*$||g
|
|
308
|
+
s|#[^"]*$||g
|
|
309
|
+
# Remove multi-line comment markers (simplified)
|
|
310
|
+
s|/\*[^*]*\*/||g
|
|
311
|
+
')
|
|
312
|
+
|
|
313
|
+
# Extract potential identifiers (alphanumeric + underscore, starting with letter or underscore)
|
|
314
|
+
# Pattern matches camelCase, snake_case, PascalCase identifiers
|
|
315
|
+
local identifiers
|
|
316
|
+
identifiers=$(echo "$clean_code" | grep -oE '\b[a-zA-Z_][a-zA-Z0-9_]*\b' | sort -u)
|
|
317
|
+
|
|
318
|
+
local json_entries=""
|
|
319
|
+
local first_entry=true
|
|
320
|
+
|
|
321
|
+
for identifier in $identifiers; do
|
|
322
|
+
# Skip if should be preserved
|
|
323
|
+
if _should_preserve "$identifier" "false"; then
|
|
324
|
+
continue
|
|
325
|
+
fi
|
|
326
|
+
|
|
327
|
+
# Skip if already mapped
|
|
328
|
+
if map_has "SYMMAP" "$identifier"; then
|
|
329
|
+
continue
|
|
330
|
+
fi
|
|
331
|
+
|
|
332
|
+
# Detect category from code context
|
|
333
|
+
local context_line
|
|
334
|
+
context_line=$(echo "$code" | grep -m1 -E "(^|[^a-zA-Z0-9_])$identifier([^a-zA-Z0-9_]|$)" | head -1)
|
|
335
|
+
local context_before
|
|
336
|
+
context_before=$(echo "$context_line" | sed -E "s/$identifier.*//")
|
|
337
|
+
|
|
338
|
+
local category
|
|
339
|
+
category=$(_detect_category "$identifier" "$context_before")
|
|
340
|
+
|
|
341
|
+
# Get symbol for this category
|
|
342
|
+
local symbol
|
|
343
|
+
symbol=$(_get_next_symbol "$category")
|
|
344
|
+
|
|
345
|
+
# Store in map
|
|
346
|
+
map_set "SYMMAP" "$identifier" "$symbol"
|
|
347
|
+
map_set "SYMMAP_CATEGORY" "$identifier" "$category"
|
|
348
|
+
|
|
349
|
+
# Build JSON entry
|
|
350
|
+
if [[ "$first_entry" == "true" ]]; then
|
|
351
|
+
first_entry=false
|
|
352
|
+
else
|
|
353
|
+
json_entries="$json_entries,"
|
|
354
|
+
fi
|
|
355
|
+
|
|
356
|
+
# Escape special characters in identifier for JSON
|
|
357
|
+
local safe_identifier
|
|
358
|
+
safe_identifier=$(echo "$identifier" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
359
|
+
|
|
360
|
+
json_entries="$json_entries
|
|
361
|
+
\"$safe_identifier\": {\"symbol\": \"$symbol\", \"category\": \"$category\"}"
|
|
362
|
+
done
|
|
363
|
+
|
|
364
|
+
local result="{$json_entries
|
|
365
|
+
}"
|
|
366
|
+
|
|
367
|
+
# Save to file for cross-subshell access
|
|
368
|
+
_save_symbol_map "$result"
|
|
369
|
+
|
|
370
|
+
echo "$result"
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
# Portable word boundary replacement (works on BSD and GNU sed)
|
|
374
|
+
# Replaces identifier only when surrounded by non-word characters
|
|
375
|
+
# Usage: _replace_word "text" "old_word" "new_word"
|
|
376
|
+
_replace_word() {
|
|
377
|
+
local text="$1"
|
|
378
|
+
local old_word="$2"
|
|
379
|
+
local new_word="$3"
|
|
380
|
+
|
|
381
|
+
# Use sed with explicit boundary patterns (portable across BSD/GNU)
|
|
382
|
+
# Match word at: start of line, after non-word char, before non-word char, end of line
|
|
383
|
+
echo "$text" | sed -E "
|
|
384
|
+
s/(^|[^a-zA-Z0-9_])${old_word}([^a-zA-Z0-9_]|$)/\1${new_word}\2/g
|
|
385
|
+
s/(^|[^a-zA-Z0-9_])${old_word}([^a-zA-Z0-9_]|$)/\1${new_word}\2/g
|
|
386
|
+
"
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
# Compress symbols in code using generated or provided map
|
|
390
|
+
#
|
|
391
|
+
# Usage: compress_symbols "code" [symbol_map_json]
|
|
392
|
+
# Output: Compressed code with identifiers replaced by short symbols
|
|
393
|
+
# Also sets SYMBOL_MAP_JSON global variable with the map
|
|
394
|
+
compress_symbols() {
|
|
395
|
+
local code="$1"
|
|
396
|
+
local provided_map="${2:-}"
|
|
397
|
+
|
|
398
|
+
if [[ "$ENABLE_SYMBOL_COMPRESSION" != "true" ]]; then
|
|
399
|
+
echo "$code"
|
|
400
|
+
return
|
|
401
|
+
fi
|
|
402
|
+
|
|
403
|
+
# Generate map if not provided
|
|
404
|
+
local map_json
|
|
405
|
+
if [[ -z "$provided_map" ]]; then
|
|
406
|
+
map_json=$(generate_symbol_map "$code")
|
|
407
|
+
else
|
|
408
|
+
map_json="$provided_map"
|
|
409
|
+
fi
|
|
410
|
+
|
|
411
|
+
# Export map for later decompression
|
|
412
|
+
SYMBOL_MAP_JSON="$map_json"
|
|
413
|
+
|
|
414
|
+
# Check if map is empty
|
|
415
|
+
if [[ "$map_json" == "{}" || "$map_json" == "{
|
|
416
|
+
}" ]]; then
|
|
417
|
+
echo "$code"
|
|
418
|
+
return
|
|
419
|
+
fi
|
|
420
|
+
|
|
421
|
+
local result="$code"
|
|
422
|
+
|
|
423
|
+
# Process each mapping (extract from JSON without jq dependency for portability)
|
|
424
|
+
# Parse JSON entries: "identifier": {"symbol": "f1", ...}
|
|
425
|
+
local entries
|
|
426
|
+
entries=$(echo "$map_json" | grep -oE '"[^"]+": \{"symbol": "[^"]+"' | sed 's/": {"symbol": "/ /; s/"//g')
|
|
427
|
+
|
|
428
|
+
# Sort by identifier length (longest first) to avoid partial replacements
|
|
429
|
+
entries=$(echo "$entries" | awk '{print length($1), $0}' | sort -rn | cut -d' ' -f2-)
|
|
430
|
+
|
|
431
|
+
while IFS=' ' read -r identifier symbol; do
|
|
432
|
+
if [[ -n "$identifier" && -n "$symbol" ]]; then
|
|
433
|
+
# Replace identifier with symbol using portable word boundaries
|
|
434
|
+
result=$(_replace_word "$result" "$identifier" "$symbol")
|
|
435
|
+
fi
|
|
436
|
+
done <<< "$entries"
|
|
437
|
+
|
|
438
|
+
echo "$result"
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
# Get the last generated symbol map (useful after compress_symbols in subshell)
|
|
442
|
+
# Usage: map_json=$(get_last_symbol_map)
|
|
443
|
+
get_last_symbol_map() {
|
|
444
|
+
_load_symbol_map
|
|
445
|
+
}
|