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,880 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# chunking.sh - Semantic chunking for code files (Bash 3.2 compatible)
|
|
3
|
+
|
|
4
|
+
# Source common utilities
|
|
5
|
+
_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
source "$_SCRIPT_DIR/common.sh" 2>/dev/null || true
|
|
7
|
+
|
|
8
|
+
# Logging stubs if common.sh unavailable
|
|
9
|
+
if ! command -v log_debug &>/dev/null; then
|
|
10
|
+
log_debug() { :; }
|
|
11
|
+
log_info() { echo "[INFO] $1" >&2; }
|
|
12
|
+
log_warn() { echo "[WARN] $1" >&2; }
|
|
13
|
+
log_error() { echo "[ERROR] $1" >&2; }
|
|
14
|
+
estimate_tokens() { echo $(( $(echo -n "$1" | wc -c | tr -d ' ') / 4 )); }
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# =============================================================================
|
|
18
|
+
# CONFIGURATION
|
|
19
|
+
# =============================================================================
|
|
20
|
+
|
|
21
|
+
# Maximum tokens per chunk (approximate: 1 token ~ 4 chars)
|
|
22
|
+
CHUNK_MAX_TOKENS="${CHUNK_MAX_TOKENS:-500}"
|
|
23
|
+
|
|
24
|
+
# Number of lines to overlap between adjacent chunks
|
|
25
|
+
CHUNK_OVERLAP_LINES="${CHUNK_OVERLAP_LINES:-5}"
|
|
26
|
+
|
|
27
|
+
# Priority keywords (comma-separated) for scoring chunks
|
|
28
|
+
CHUNK_PRIORITY_KEYWORDS="${CHUNK_PRIORITY_KEYWORDS:-main,export,public,api,handler,controller}"
|
|
29
|
+
|
|
30
|
+
# Temporary directory for chunk storage
|
|
31
|
+
CHUNK_TEMP_DIR="${CHUNK_TEMP_DIR:-/tmp/ai_consultants_chunks}"
|
|
32
|
+
|
|
33
|
+
# Initialize chunk storage directory with secure permissions
|
|
34
|
+
if [[ ! -d "$CHUNK_TEMP_DIR" ]]; then
|
|
35
|
+
mkdir -p "$CHUNK_TEMP_DIR" 2>/dev/null || true
|
|
36
|
+
fi
|
|
37
|
+
chmod 700 "$CHUNK_TEMP_DIR" 2>/dev/null || true
|
|
38
|
+
|
|
39
|
+
# =============================================================================
|
|
40
|
+
# INTERNAL STATE (file-scoped, cleared between operations)
|
|
41
|
+
# =============================================================================
|
|
42
|
+
|
|
43
|
+
# Current file being processed
|
|
44
|
+
_CHUNK_CURRENT_FILE=""
|
|
45
|
+
|
|
46
|
+
# Chunk counter for generating IDs
|
|
47
|
+
_CHUNK_COUNTER=0
|
|
48
|
+
|
|
49
|
+
# Session ID for current chunking operation
|
|
50
|
+
_CHUNK_SESSION_ID=""
|
|
51
|
+
|
|
52
|
+
# =============================================================================
|
|
53
|
+
# LANGUAGE DETECTION
|
|
54
|
+
# =============================================================================
|
|
55
|
+
|
|
56
|
+
# Detect programming language from file extension
|
|
57
|
+
# Usage: _detect_language "file.py"
|
|
58
|
+
# Returns: python|javascript|typescript|go|java|ruby|bash|c|cpp|rust|unknown
|
|
59
|
+
_detect_language() {
|
|
60
|
+
local file_path="$1"
|
|
61
|
+
local extension="${file_path##*.}"
|
|
62
|
+
|
|
63
|
+
case "$extension" in
|
|
64
|
+
py) echo "python" ;;
|
|
65
|
+
js|mjs|cjs) echo "javascript" ;;
|
|
66
|
+
ts|tsx) echo "typescript" ;;
|
|
67
|
+
go) echo "go" ;;
|
|
68
|
+
java) echo "java" ;;
|
|
69
|
+
rb) echo "ruby" ;;
|
|
70
|
+
sh|bash) echo "bash" ;;
|
|
71
|
+
c|h) echo "c" ;;
|
|
72
|
+
cpp|cc|hpp) echo "cpp" ;;
|
|
73
|
+
rs) echo "rust" ;;
|
|
74
|
+
*) echo "unknown" ;;
|
|
75
|
+
esac
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# =============================================================================
|
|
79
|
+
# PATTERN DEFINITIONS
|
|
80
|
+
# =============================================================================
|
|
81
|
+
|
|
82
|
+
# Get function pattern for a language
|
|
83
|
+
# Usage: _get_function_pattern "python"
|
|
84
|
+
_get_function_pattern() {
|
|
85
|
+
local lang="$1"
|
|
86
|
+
|
|
87
|
+
case "$lang" in
|
|
88
|
+
python)
|
|
89
|
+
echo '^[[:space:]]*(async[[:space:]]+)?def[[:space:]]+[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*\('
|
|
90
|
+
;;
|
|
91
|
+
javascript|typescript)
|
|
92
|
+
# Match: function name(), async function name(), const name = () =>, const name = function()
|
|
93
|
+
echo '^[[:space:]]*(export[[:space:]]+)?(async[[:space:]]+)?function[[:space:]]+[a-zA-Z_][a-zA-Z0-9_]*|^[[:space:]]*(export[[:space:]]+)?(const|let|var)[[:space:]]+[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*=[[:space:]]*(async[[:space:]]*)?\('
|
|
94
|
+
;;
|
|
95
|
+
go)
|
|
96
|
+
echo '^func[[:space:]]+(\([^)]+\)[[:space:]]+)?[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*\('
|
|
97
|
+
;;
|
|
98
|
+
java)
|
|
99
|
+
echo '^[[:space:]]*(public|private|protected)?[[:space:]]*(static)?[[:space:]]*(void|[A-Za-z_][A-Za-z0-9_<>,]*)[[:space:]]+[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*\('
|
|
100
|
+
;;
|
|
101
|
+
ruby)
|
|
102
|
+
echo '^[[:space:]]*def[[:space:]]+[a-zA-Z_][a-zA-Z0-9_?!]*'
|
|
103
|
+
;;
|
|
104
|
+
bash)
|
|
105
|
+
echo '^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*\(\)[[:space:]]*\{|^[[:space:]]*function[[:space:]]+[a-zA-Z_][a-zA-Z0-9_]*'
|
|
106
|
+
;;
|
|
107
|
+
c|cpp)
|
|
108
|
+
echo '^[a-zA-Z_][a-zA-Z0-9_*[:space:]]+[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*\([^;]*$'
|
|
109
|
+
;;
|
|
110
|
+
rust)
|
|
111
|
+
echo '^[[:space:]]*(pub[[:space:]]+)?(async[[:space:]]+)?fn[[:space:]]+[a-zA-Z_][a-zA-Z0-9_]*'
|
|
112
|
+
;;
|
|
113
|
+
*)
|
|
114
|
+
echo '^(function|def|fn|func)[[:space:]]+[a-zA-Z_]'
|
|
115
|
+
;;
|
|
116
|
+
esac
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# Get class pattern for a language
|
|
120
|
+
# Usage: _get_class_pattern "python"
|
|
121
|
+
_get_class_pattern() {
|
|
122
|
+
local lang="$1"
|
|
123
|
+
|
|
124
|
+
case "$lang" in
|
|
125
|
+
python)
|
|
126
|
+
echo '^class[[:space:]]+[a-zA-Z_][a-zA-Z0-9_]*'
|
|
127
|
+
;;
|
|
128
|
+
javascript|typescript)
|
|
129
|
+
echo '^[[:space:]]*(export[[:space:]]+)?(default[[:space:]]+)?class[[:space:]]+[a-zA-Z_][a-zA-Z0-9_]*'
|
|
130
|
+
;;
|
|
131
|
+
java)
|
|
132
|
+
echo '^[[:space:]]*(public|private)?[[:space:]]*(abstract)?[[:space:]]*class[[:space:]]+[a-zA-Z_][a-zA-Z0-9_]*'
|
|
133
|
+
;;
|
|
134
|
+
ruby)
|
|
135
|
+
echo '^class[[:space:]]+[A-Z][a-zA-Z0-9_]*'
|
|
136
|
+
;;
|
|
137
|
+
cpp)
|
|
138
|
+
echo '^[[:space:]]*(class|struct)[[:space:]]+[a-zA-Z_][a-zA-Z0-9_]*'
|
|
139
|
+
;;
|
|
140
|
+
rust)
|
|
141
|
+
echo '^[[:space:]]*(pub[[:space:]]+)?(struct|enum|impl)[[:space:]]+[a-zA-Z_][a-zA-Z0-9_]*'
|
|
142
|
+
;;
|
|
143
|
+
go)
|
|
144
|
+
echo '^type[[:space:]]+[A-Z][a-zA-Z0-9_]*[[:space:]]+(struct|interface)'
|
|
145
|
+
;;
|
|
146
|
+
*)
|
|
147
|
+
echo '^class[[:space:]]+[a-zA-Z_]'
|
|
148
|
+
;;
|
|
149
|
+
esac
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
# Get import pattern for a language
|
|
153
|
+
# Usage: _get_import_pattern "python"
|
|
154
|
+
_get_import_pattern() {
|
|
155
|
+
local lang="$1"
|
|
156
|
+
|
|
157
|
+
case "$lang" in
|
|
158
|
+
python)
|
|
159
|
+
echo '^(import[[:space:]]|from[[:space:]])'
|
|
160
|
+
;;
|
|
161
|
+
javascript|typescript)
|
|
162
|
+
echo "^(import[[:space:]]|const[[:space:]]+.*=[[:space:]]*require\(|export[[:space:]]+\{)"
|
|
163
|
+
;;
|
|
164
|
+
go)
|
|
165
|
+
echo '^import[[:space:]]'
|
|
166
|
+
;;
|
|
167
|
+
java)
|
|
168
|
+
echo '^import[[:space:]]'
|
|
169
|
+
;;
|
|
170
|
+
ruby)
|
|
171
|
+
echo "^(require[[:space:]]|require_relative[[:space:]]|include[[:space:]])"
|
|
172
|
+
;;
|
|
173
|
+
rust)
|
|
174
|
+
echo '^use[[:space:]]'
|
|
175
|
+
;;
|
|
176
|
+
c|cpp)
|
|
177
|
+
echo '^#include[[:space:]]'
|
|
178
|
+
;;
|
|
179
|
+
*)
|
|
180
|
+
echo '^import[[:space:]]'
|
|
181
|
+
;;
|
|
182
|
+
esac
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# =============================================================================
|
|
186
|
+
# CHECKSUM UTILITIES
|
|
187
|
+
# =============================================================================
|
|
188
|
+
|
|
189
|
+
# Calculate MD5 checksum of content (portable)
|
|
190
|
+
# Usage: _calculate_checksum "content"
|
|
191
|
+
_calculate_checksum() {
|
|
192
|
+
local content="$1"
|
|
193
|
+
|
|
194
|
+
# Try md5sum (Linux) first, then md5 (macOS)
|
|
195
|
+
if command -v md5sum &>/dev/null; then
|
|
196
|
+
echo -n "$content" | md5sum | cut -d' ' -f1
|
|
197
|
+
elif command -v md5 &>/dev/null; then
|
|
198
|
+
echo -n "$content" | md5 -q
|
|
199
|
+
else
|
|
200
|
+
# Fallback: use character count and line count as pseudo-checksum
|
|
201
|
+
local chars lines
|
|
202
|
+
chars=$(echo -n "$content" | wc -c | tr -d ' ')
|
|
203
|
+
lines=$(echo "$content" | wc -l | tr -d ' ')
|
|
204
|
+
echo "fallback_${chars}_${lines}"
|
|
205
|
+
fi
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
# =============================================================================
|
|
209
|
+
# LINE EXTRACTION UTILITIES
|
|
210
|
+
# =============================================================================
|
|
211
|
+
|
|
212
|
+
# Extract lines from file (1-indexed, inclusive)
|
|
213
|
+
# Usage: _extract_lines "file" start_line end_line
|
|
214
|
+
_extract_lines() {
|
|
215
|
+
local file="$1"
|
|
216
|
+
local start_line="$2"
|
|
217
|
+
local end_line="$3"
|
|
218
|
+
|
|
219
|
+
# Validate inputs
|
|
220
|
+
if [[ ! -f "$file" ]]; then
|
|
221
|
+
log_error "File not found: $file"
|
|
222
|
+
return 1
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
# Use sed for portable line extraction
|
|
226
|
+
sed -n "${start_line},${end_line}p" "$file"
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
# Count total lines in file
|
|
230
|
+
# Usage: _count_lines "file"
|
|
231
|
+
_count_lines() {
|
|
232
|
+
local file="$1"
|
|
233
|
+
wc -l < "$file" | tr -d ' '
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
# =============================================================================
|
|
237
|
+
# SEMANTIC BOUNDARY DETECTION
|
|
238
|
+
# =============================================================================
|
|
239
|
+
|
|
240
|
+
# Find all semantic boundaries in a file
|
|
241
|
+
# Usage: _find_semantic_boundaries "file" "language"
|
|
242
|
+
# Output: Line numbers where semantic units start (one per line)
|
|
243
|
+
_find_semantic_boundaries() {
|
|
244
|
+
local file="$1"
|
|
245
|
+
local lang="$2"
|
|
246
|
+
|
|
247
|
+
local func_pattern class_pattern import_pattern
|
|
248
|
+
func_pattern=$(_get_function_pattern "$lang")
|
|
249
|
+
class_pattern=$(_get_class_pattern "$lang")
|
|
250
|
+
import_pattern=$(_get_import_pattern "$lang")
|
|
251
|
+
|
|
252
|
+
local line_num=0
|
|
253
|
+
local in_import_block=0
|
|
254
|
+
local last_import_line=0
|
|
255
|
+
local boundaries=""
|
|
256
|
+
|
|
257
|
+
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
258
|
+
line_num=$((line_num + 1))
|
|
259
|
+
|
|
260
|
+
# Check for import lines (group consecutive imports)
|
|
261
|
+
if echo "$line" | grep -qE "$import_pattern" 2>/dev/null; then
|
|
262
|
+
if [[ $in_import_block -eq 0 ]]; then
|
|
263
|
+
# Start of import block
|
|
264
|
+
boundaries="${boundaries}${line_num}:import
|
|
265
|
+
"
|
|
266
|
+
in_import_block=1
|
|
267
|
+
fi
|
|
268
|
+
last_import_line=$line_num
|
|
269
|
+
continue
|
|
270
|
+
fi
|
|
271
|
+
|
|
272
|
+
# End import block if we've moved past imports
|
|
273
|
+
if [[ $in_import_block -eq 1 && $line_num -gt $((last_import_line + 1)) ]]; then
|
|
274
|
+
in_import_block=0
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
# Check for class definitions
|
|
278
|
+
if echo "$line" | grep -qE "$class_pattern" 2>/dev/null; then
|
|
279
|
+
boundaries="${boundaries}${line_num}:class
|
|
280
|
+
"
|
|
281
|
+
continue
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
# Check for function definitions
|
|
285
|
+
if echo "$line" | grep -qE "$func_pattern" 2>/dev/null; then
|
|
286
|
+
boundaries="${boundaries}${line_num}:function
|
|
287
|
+
"
|
|
288
|
+
continue
|
|
289
|
+
fi
|
|
290
|
+
|
|
291
|
+
done < "$file"
|
|
292
|
+
|
|
293
|
+
echo -n "$boundaries"
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
# Find the end of a semantic unit (function, class, etc.)
|
|
297
|
+
# Usage: _find_unit_end "file" start_line unit_type language
|
|
298
|
+
_find_unit_end() {
|
|
299
|
+
local file="$1"
|
|
300
|
+
local start_line="$2"
|
|
301
|
+
local unit_type="$3"
|
|
302
|
+
local lang="$4"
|
|
303
|
+
|
|
304
|
+
local total_lines
|
|
305
|
+
total_lines=$(_count_lines "$file")
|
|
306
|
+
|
|
307
|
+
# For imports, find consecutive import lines
|
|
308
|
+
if [[ "$unit_type" == "import" ]]; then
|
|
309
|
+
local import_pattern
|
|
310
|
+
import_pattern=$(_get_import_pattern "$lang")
|
|
311
|
+
local current_line=$start_line
|
|
312
|
+
local last_import=$start_line
|
|
313
|
+
|
|
314
|
+
while [[ $current_line -le $total_lines ]]; do
|
|
315
|
+
local line
|
|
316
|
+
line=$(sed -n "${current_line}p" "$file")
|
|
317
|
+
|
|
318
|
+
# Check if line is empty or comment (allow gaps)
|
|
319
|
+
if [[ -z "$line" || "$line" =~ ^[[:space:]]*# || "$line" =~ ^[[:space:]]*// ]]; then
|
|
320
|
+
current_line=$((current_line + 1))
|
|
321
|
+
# Allow up to 2 blank lines in import block
|
|
322
|
+
if [[ $current_line -gt $((last_import + 3)) ]]; then
|
|
323
|
+
break
|
|
324
|
+
fi
|
|
325
|
+
continue
|
|
326
|
+
fi
|
|
327
|
+
|
|
328
|
+
# Check if still in imports
|
|
329
|
+
if echo "$line" | grep -qE "$import_pattern" 2>/dev/null; then
|
|
330
|
+
last_import=$current_line
|
|
331
|
+
current_line=$((current_line + 1))
|
|
332
|
+
else
|
|
333
|
+
break
|
|
334
|
+
fi
|
|
335
|
+
done
|
|
336
|
+
|
|
337
|
+
echo $last_import
|
|
338
|
+
return
|
|
339
|
+
fi
|
|
340
|
+
|
|
341
|
+
# For functions and classes, use brace/indent counting
|
|
342
|
+
local brace_count=0
|
|
343
|
+
local indent_based=0
|
|
344
|
+
local base_indent=""
|
|
345
|
+
local found_start=0
|
|
346
|
+
local current_line=$start_line
|
|
347
|
+
|
|
348
|
+
# Python and Ruby use indentation
|
|
349
|
+
if [[ "$lang" == "python" || "$lang" == "ruby" ]]; then
|
|
350
|
+
indent_based=1
|
|
351
|
+
# Get base indentation of the definition line
|
|
352
|
+
local def_line
|
|
353
|
+
def_line=$(sed -n "${start_line}p" "$file")
|
|
354
|
+
base_indent=$(echo "$def_line" | sed 's/[^[:space:]].*//')
|
|
355
|
+
fi
|
|
356
|
+
|
|
357
|
+
while [[ $current_line -le $total_lines ]]; do
|
|
358
|
+
local line
|
|
359
|
+
line=$(sed -n "${current_line}p" "$file")
|
|
360
|
+
|
|
361
|
+
if [[ $indent_based -eq 1 ]]; then
|
|
362
|
+
# Skip the definition line itself
|
|
363
|
+
if [[ $current_line -eq $start_line ]]; then
|
|
364
|
+
current_line=$((current_line + 1))
|
|
365
|
+
continue
|
|
366
|
+
fi
|
|
367
|
+
|
|
368
|
+
# Skip empty lines
|
|
369
|
+
if [[ -z "${line// /}" ]]; then
|
|
370
|
+
current_line=$((current_line + 1))
|
|
371
|
+
continue
|
|
372
|
+
fi
|
|
373
|
+
|
|
374
|
+
# Get current line's indentation
|
|
375
|
+
local current_indent
|
|
376
|
+
current_indent=$(echo "$line" | sed 's/[^[:space:]].*//')
|
|
377
|
+
|
|
378
|
+
# If we've returned to base indentation (or less), we're done
|
|
379
|
+
if [[ ${#current_indent} -le ${#base_indent} ]]; then
|
|
380
|
+
echo $((current_line - 1))
|
|
381
|
+
return
|
|
382
|
+
fi
|
|
383
|
+
else
|
|
384
|
+
# Brace counting for C-like languages
|
|
385
|
+
local open_braces close_braces
|
|
386
|
+
|
|
387
|
+
# Count braces in line
|
|
388
|
+
open_braces=$(echo "$line" | tr -cd '{' | wc -c | tr -d ' ')
|
|
389
|
+
close_braces=$(echo "$line" | tr -cd '}' | wc -c | tr -d ' ')
|
|
390
|
+
|
|
391
|
+
brace_count=$((brace_count + open_braces - close_braces))
|
|
392
|
+
|
|
393
|
+
# Mark that we've found the opening brace
|
|
394
|
+
if [[ $open_braces -gt 0 ]]; then
|
|
395
|
+
found_start=1
|
|
396
|
+
fi
|
|
397
|
+
|
|
398
|
+
# If we've closed all braces after opening, we're done
|
|
399
|
+
if [[ $found_start -eq 1 && $brace_count -le 0 ]]; then
|
|
400
|
+
echo $current_line
|
|
401
|
+
return
|
|
402
|
+
fi
|
|
403
|
+
fi
|
|
404
|
+
|
|
405
|
+
current_line=$((current_line + 1))
|
|
406
|
+
done
|
|
407
|
+
|
|
408
|
+
# If we reach end of file, return last line
|
|
409
|
+
echo $total_lines
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
# =============================================================================
|
|
413
|
+
# NESTING DEPTH CALCULATION
|
|
414
|
+
# =============================================================================
|
|
415
|
+
|
|
416
|
+
# Calculate maximum nesting depth in a code block
|
|
417
|
+
# Usage: _calculate_nesting_depth "content"
|
|
418
|
+
_calculate_nesting_depth() {
|
|
419
|
+
local content="$1"
|
|
420
|
+
local max_depth=0
|
|
421
|
+
local current_depth=0
|
|
422
|
+
|
|
423
|
+
while IFS= read -r line; do
|
|
424
|
+
# Count opening and closing braces/indentation markers
|
|
425
|
+
local opens closes
|
|
426
|
+
opens=$(echo "$line" | tr -cd '{(' | wc -c | tr -d ' ')
|
|
427
|
+
closes=$(echo "$line" | tr -cd '})' | wc -c | tr -d ' ')
|
|
428
|
+
|
|
429
|
+
current_depth=$((current_depth + opens))
|
|
430
|
+
if [[ $current_depth -gt $max_depth ]]; then
|
|
431
|
+
max_depth=$current_depth
|
|
432
|
+
fi
|
|
433
|
+
current_depth=$((current_depth - closes))
|
|
434
|
+
if [[ $current_depth -lt 0 ]]; then
|
|
435
|
+
current_depth=0
|
|
436
|
+
fi
|
|
437
|
+
done <<< "$content"
|
|
438
|
+
|
|
439
|
+
echo $max_depth
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
# =============================================================================
|
|
443
|
+
# DEPENDENCY EXTRACTION
|
|
444
|
+
# =============================================================================
|
|
445
|
+
|
|
446
|
+
# Extract dependencies (function/class references) from content
|
|
447
|
+
# Usage: _extract_dependencies "content" "language"
|
|
448
|
+
_extract_dependencies() {
|
|
449
|
+
local content="$1"
|
|
450
|
+
local lang="$2"
|
|
451
|
+
|
|
452
|
+
local deps=""
|
|
453
|
+
|
|
454
|
+
case "$lang" in
|
|
455
|
+
python)
|
|
456
|
+
# Look for function calls and class instantiations
|
|
457
|
+
deps=$(echo "$content" | grep -oE '[a-zA-Z_][a-zA-Z0-9_]*\(' 2>/dev/null | \
|
|
458
|
+
sed 's/($//' | sort -u | tr '\n' ',' | sed 's/,$//')
|
|
459
|
+
;;
|
|
460
|
+
javascript|typescript)
|
|
461
|
+
# Look for function calls, imports references
|
|
462
|
+
deps=$(echo "$content" | grep -oE '[a-zA-Z_][a-zA-Z0-9_]*\(' 2>/dev/null | \
|
|
463
|
+
sed 's/($//' | sort -u | tr '\n' ',' | sed 's/,$//')
|
|
464
|
+
;;
|
|
465
|
+
go)
|
|
466
|
+
# Look for function calls and type references
|
|
467
|
+
deps=$(echo "$content" | grep -oE '[a-zA-Z_][a-zA-Z0-9_]*\(' 2>/dev/null | \
|
|
468
|
+
sed 's/($//' | sort -u | tr '\n' ',' | sed 's/,$//')
|
|
469
|
+
;;
|
|
470
|
+
*)
|
|
471
|
+
# Generic function call detection
|
|
472
|
+
deps=$(echo "$content" | grep -oE '[a-zA-Z_][a-zA-Z0-9_]*\(' 2>/dev/null | \
|
|
473
|
+
sed 's/($//' | sort -u | tr '\n' ',' | sed 's/,$//')
|
|
474
|
+
;;
|
|
475
|
+
esac
|
|
476
|
+
|
|
477
|
+
echo "$deps"
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
# =============================================================================
|
|
481
|
+
# PRIORITY SCORING
|
|
482
|
+
# =============================================================================
|
|
483
|
+
|
|
484
|
+
# Calculate priority score for a chunk
|
|
485
|
+
# Usage: _calculate_priority "content" "chunk_type" "query_keywords"
|
|
486
|
+
_calculate_priority() {
|
|
487
|
+
local content="$1"
|
|
488
|
+
local chunk_type="$2"
|
|
489
|
+
local query_keywords="${3:-}"
|
|
490
|
+
|
|
491
|
+
local score=50 # Base score
|
|
492
|
+
|
|
493
|
+
# Boost for entry points
|
|
494
|
+
if echo "$content" | grep -qiE '(^|\s)(main|__main__|export\s+default|module\.exports)' 2>/dev/null; then
|
|
495
|
+
score=$((score + 20))
|
|
496
|
+
fi
|
|
497
|
+
|
|
498
|
+
# Boost for public/exported items
|
|
499
|
+
if echo "$content" | grep -qiE '(^|\s)(public|export|pub\s+fn)' 2>/dev/null; then
|
|
500
|
+
score=$((score + 15))
|
|
501
|
+
fi
|
|
502
|
+
|
|
503
|
+
# Boost for API handlers
|
|
504
|
+
if echo "$content" | grep -qiE '(handler|controller|endpoint|route|api)' 2>/dev/null; then
|
|
505
|
+
score=$((score + 10))
|
|
506
|
+
fi
|
|
507
|
+
|
|
508
|
+
# Check against configured priority keywords
|
|
509
|
+
local IFS=','
|
|
510
|
+
local keywords
|
|
511
|
+
read -ra keywords <<< "$CHUNK_PRIORITY_KEYWORDS"
|
|
512
|
+
for keyword in "${keywords[@]}"; do
|
|
513
|
+
keyword=$(echo "$keyword" | tr -d ' ')
|
|
514
|
+
if [[ -n "$keyword" ]] && echo "$content" | grep -qi "$keyword" 2>/dev/null; then
|
|
515
|
+
score=$((score + 5))
|
|
516
|
+
fi
|
|
517
|
+
done
|
|
518
|
+
|
|
519
|
+
# Check against query-specific keywords
|
|
520
|
+
if [[ -n "$query_keywords" ]]; then
|
|
521
|
+
read -ra keywords <<< "$query_keywords"
|
|
522
|
+
for keyword in "${keywords[@]}"; do
|
|
523
|
+
keyword=$(echo "$keyword" | tr -d ' ')
|
|
524
|
+
if [[ -n "$keyword" ]] && echo "$content" | grep -qi "$keyword" 2>/dev/null; then
|
|
525
|
+
score=$((score + 10))
|
|
526
|
+
fi
|
|
527
|
+
done
|
|
528
|
+
fi
|
|
529
|
+
|
|
530
|
+
# Adjust based on complexity (moderate complexity is good)
|
|
531
|
+
local lines nesting
|
|
532
|
+
lines=$(echo "$content" | wc -l | tr -d ' ')
|
|
533
|
+
nesting=$(_calculate_nesting_depth "$content")
|
|
534
|
+
|
|
535
|
+
# Penalize very short chunks
|
|
536
|
+
if [[ $lines -lt 5 ]]; then
|
|
537
|
+
score=$((score - 10))
|
|
538
|
+
fi
|
|
539
|
+
|
|
540
|
+
# Penalize very complex chunks
|
|
541
|
+
if [[ $nesting -gt 5 ]]; then
|
|
542
|
+
score=$((score - 5))
|
|
543
|
+
fi
|
|
544
|
+
|
|
545
|
+
# Cap score between 0 and 100
|
|
546
|
+
if [[ $score -lt 0 ]]; then
|
|
547
|
+
score=0
|
|
548
|
+
elif [[ $score -gt 100 ]]; then
|
|
549
|
+
score=100
|
|
550
|
+
fi
|
|
551
|
+
|
|
552
|
+
echo $score
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
# =============================================================================
|
|
556
|
+
# CHUNK GENERATION
|
|
557
|
+
# =============================================================================
|
|
558
|
+
|
|
559
|
+
# Generate a unique chunk ID
|
|
560
|
+
# Usage: _generate_chunk_id "file_path" chunk_number
|
|
561
|
+
_generate_chunk_id() {
|
|
562
|
+
local file_path="$1"
|
|
563
|
+
local chunk_num="$2"
|
|
564
|
+
|
|
565
|
+
local file_hash
|
|
566
|
+
file_hash=$(echo -n "$file_path" | _calculate_checksum "$(cat)")
|
|
567
|
+
|
|
568
|
+
echo "chunk_${file_hash:0:8}_${chunk_num}"
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
# Create a chunk JSON object
|
|
572
|
+
# Usage: _create_chunk_json id content start_line end_line type priority deps checksum
|
|
573
|
+
_create_chunk_json() {
|
|
574
|
+
local chunk_id="$1"
|
|
575
|
+
local content="$2"
|
|
576
|
+
local start_line="$3"
|
|
577
|
+
local end_line="$4"
|
|
578
|
+
local chunk_type="$5"
|
|
579
|
+
local priority="$6"
|
|
580
|
+
local dependencies="$7"
|
|
581
|
+
local checksum="$8"
|
|
582
|
+
|
|
583
|
+
local lines token_estimate
|
|
584
|
+
lines=$((end_line - start_line + 1))
|
|
585
|
+
token_estimate=$(estimate_tokens "$content")
|
|
586
|
+
|
|
587
|
+
# Build dependencies array
|
|
588
|
+
local deps_array="[]"
|
|
589
|
+
if [[ -n "$dependencies" ]]; then
|
|
590
|
+
deps_array=$(echo "$dependencies" | tr ',' '\n' | jq -R . | jq -s .)
|
|
591
|
+
fi
|
|
592
|
+
|
|
593
|
+
jq -n \
|
|
594
|
+
--arg id "$chunk_id" \
|
|
595
|
+
--arg content "$content" \
|
|
596
|
+
--argjson start "$start_line" \
|
|
597
|
+
--argjson end "$end_line" \
|
|
598
|
+
--arg type "$chunk_type" \
|
|
599
|
+
--argjson lines "$lines" \
|
|
600
|
+
--argjson tokens "$token_estimate" \
|
|
601
|
+
--argjson priority "$priority" \
|
|
602
|
+
--argjson deps "$deps_array" \
|
|
603
|
+
--arg checksum "$checksum" \
|
|
604
|
+
'{
|
|
605
|
+
id: $id,
|
|
606
|
+
content: $content,
|
|
607
|
+
metadata: {
|
|
608
|
+
start_line: $start,
|
|
609
|
+
end_line: $end,
|
|
610
|
+
type: $type,
|
|
611
|
+
lines: $lines,
|
|
612
|
+
estimated_tokens: $tokens,
|
|
613
|
+
priority: $priority,
|
|
614
|
+
dependencies: $deps,
|
|
615
|
+
checksum: $checksum
|
|
616
|
+
}
|
|
617
|
+
}'
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
# =============================================================================
|
|
621
|
+
# MAIN CHUNKING FUNCTION
|
|
622
|
+
# =============================================================================
|
|
623
|
+
|
|
624
|
+
# Split a file into semantic chunks
|
|
625
|
+
# Usage: chunk_file_semantically "file_path" [query_keywords]
|
|
626
|
+
# Output: JSON array of chunks
|
|
627
|
+
chunk_file_semantically() {
|
|
628
|
+
local file_path="$1"
|
|
629
|
+
local query_keywords="${2:-}"
|
|
630
|
+
|
|
631
|
+
# Basic path validation (bash 3.2 compatible)
|
|
632
|
+
if [[ -z "$file_path" ]]; then
|
|
633
|
+
log_error "Empty file path provided"
|
|
634
|
+
echo "[]"
|
|
635
|
+
return 1
|
|
636
|
+
fi
|
|
637
|
+
|
|
638
|
+
# Check for path traversal
|
|
639
|
+
if [[ "$file_path" == *".."* ]]; then
|
|
640
|
+
log_error "Path traversal detected in: $file_path"
|
|
641
|
+
echo "[]"
|
|
642
|
+
return 1
|
|
643
|
+
fi
|
|
644
|
+
|
|
645
|
+
if [[ ! -f "$file_path" ]]; then
|
|
646
|
+
log_error "File not found: $file_path"
|
|
647
|
+
echo "[]"
|
|
648
|
+
return 1
|
|
649
|
+
fi
|
|
650
|
+
|
|
651
|
+
# Initialize session
|
|
652
|
+
_CHUNK_CURRENT_FILE="$file_path"
|
|
653
|
+
_CHUNK_COUNTER=0
|
|
654
|
+
_CHUNK_SESSION_ID=$(date +%Y%m%d_%H%M%S)_$$
|
|
655
|
+
|
|
656
|
+
local lang
|
|
657
|
+
lang=$(_detect_language "$file_path")
|
|
658
|
+
log_debug "Detected language: $lang for file: $file_path"
|
|
659
|
+
|
|
660
|
+
# Find semantic boundaries
|
|
661
|
+
local boundaries
|
|
662
|
+
boundaries=$(_find_semantic_boundaries "$file_path" "$lang")
|
|
663
|
+
|
|
664
|
+
if [[ -z "$boundaries" ]]; then
|
|
665
|
+
log_warn "No semantic boundaries found, chunking by token limit"
|
|
666
|
+
_chunk_by_tokens "$file_path" "$query_keywords"
|
|
667
|
+
return
|
|
668
|
+
fi
|
|
669
|
+
|
|
670
|
+
local total_lines
|
|
671
|
+
total_lines=$(_count_lines "$file_path")
|
|
672
|
+
|
|
673
|
+
local chunks="[]"
|
|
674
|
+
local prev_end=0
|
|
675
|
+
local max_chars=$((CHUNK_MAX_TOKENS * 4)) # Approximate chars from tokens
|
|
676
|
+
|
|
677
|
+
# Process each boundary
|
|
678
|
+
while IFS= read -r boundary; do
|
|
679
|
+
[[ -z "$boundary" ]] && continue
|
|
680
|
+
|
|
681
|
+
local line_num unit_type
|
|
682
|
+
line_num=$(echo "$boundary" | cut -d: -f1)
|
|
683
|
+
unit_type=$(echo "$boundary" | cut -d: -f2)
|
|
684
|
+
|
|
685
|
+
# Handle gap before this boundary (if significant)
|
|
686
|
+
if [[ $((line_num - 1)) -gt $((prev_end + CHUNK_OVERLAP_LINES)) ]]; then
|
|
687
|
+
local gap_start=$((prev_end + 1))
|
|
688
|
+
local gap_end=$((line_num - 1))
|
|
689
|
+
|
|
690
|
+
# Only create chunk if gap has content
|
|
691
|
+
local gap_content
|
|
692
|
+
gap_content=$(_extract_lines "$file_path" "$gap_start" "$gap_end")
|
|
693
|
+
|
|
694
|
+
if [[ -n "${gap_content// /}" ]]; then
|
|
695
|
+
local gap_tokens
|
|
696
|
+
gap_tokens=$(estimate_tokens "$gap_content")
|
|
697
|
+
|
|
698
|
+
if [[ $gap_tokens -gt 10 ]]; then
|
|
699
|
+
_CHUNK_COUNTER=$((_CHUNK_COUNTER + 1))
|
|
700
|
+
local gap_chunk
|
|
701
|
+
gap_chunk=$(_create_chunk_for_content "$file_path" "$gap_content" "$gap_start" "$gap_end" "gap" "$lang" "$query_keywords")
|
|
702
|
+
chunks=$(echo "$chunks" | jq --argjson chunk "$gap_chunk" '. + [$chunk]')
|
|
703
|
+
fi
|
|
704
|
+
fi
|
|
705
|
+
fi
|
|
706
|
+
|
|
707
|
+
# Find end of this semantic unit
|
|
708
|
+
local unit_end
|
|
709
|
+
unit_end=$(_find_unit_end "$file_path" "$line_num" "$unit_type" "$lang")
|
|
710
|
+
|
|
711
|
+
# Extract content with overlap
|
|
712
|
+
local overlap_start=$((line_num - CHUNK_OVERLAP_LINES))
|
|
713
|
+
if [[ $overlap_start -lt 1 ]]; then
|
|
714
|
+
overlap_start=1
|
|
715
|
+
fi
|
|
716
|
+
|
|
717
|
+
local content
|
|
718
|
+
content=$(_extract_lines "$file_path" "$overlap_start" "$unit_end")
|
|
719
|
+
|
|
720
|
+
# Check if chunk exceeds token limit
|
|
721
|
+
local token_count
|
|
722
|
+
token_count=$(estimate_tokens "$content")
|
|
723
|
+
|
|
724
|
+
if [[ $token_count -gt $CHUNK_MAX_TOKENS ]]; then
|
|
725
|
+
# Split large unit into sub-chunks
|
|
726
|
+
local sub_chunks
|
|
727
|
+
sub_chunks=$(_split_large_unit "$file_path" "$overlap_start" "$unit_end" "$unit_type" "$lang" "$query_keywords")
|
|
728
|
+
chunks=$(echo "$chunks" | jq --argjson sub "$sub_chunks" '. + $sub')
|
|
729
|
+
else
|
|
730
|
+
_CHUNK_COUNTER=$((_CHUNK_COUNTER + 1))
|
|
731
|
+
local chunk
|
|
732
|
+
chunk=$(_create_chunk_for_content "$file_path" "$content" "$overlap_start" "$unit_end" "$unit_type" "$lang" "$query_keywords")
|
|
733
|
+
chunks=$(echo "$chunks" | jq --argjson chunk "$chunk" '. + [$chunk]')
|
|
734
|
+
fi
|
|
735
|
+
|
|
736
|
+
prev_end=$unit_end
|
|
737
|
+
done <<< "$boundaries"
|
|
738
|
+
|
|
739
|
+
# Handle remaining content after last boundary
|
|
740
|
+
if [[ $prev_end -lt $total_lines ]]; then
|
|
741
|
+
local remaining_start=$((prev_end + 1))
|
|
742
|
+
local remaining_content
|
|
743
|
+
remaining_content=$(_extract_lines "$file_path" "$remaining_start" "$total_lines")
|
|
744
|
+
|
|
745
|
+
if [[ -n "${remaining_content// /}" ]]; then
|
|
746
|
+
local remaining_tokens
|
|
747
|
+
remaining_tokens=$(estimate_tokens "$remaining_content")
|
|
748
|
+
|
|
749
|
+
if [[ $remaining_tokens -gt 10 ]]; then
|
|
750
|
+
_CHUNK_COUNTER=$((_CHUNK_COUNTER + 1))
|
|
751
|
+
local remaining_chunk
|
|
752
|
+
remaining_chunk=$(_create_chunk_for_content "$file_path" "$remaining_content" "$remaining_start" "$total_lines" "trailing" "$lang" "$query_keywords")
|
|
753
|
+
chunks=$(echo "$chunks" | jq --argjson chunk "$remaining_chunk" '. + [$chunk]')
|
|
754
|
+
fi
|
|
755
|
+
fi
|
|
756
|
+
fi
|
|
757
|
+
|
|
758
|
+
# Save chunks to session file
|
|
759
|
+
local session_file="$CHUNK_TEMP_DIR/${_CHUNK_SESSION_ID}_chunks.json"
|
|
760
|
+
echo "$chunks" > "$session_file"
|
|
761
|
+
chmod 600 "$session_file" 2>/dev/null || true
|
|
762
|
+
|
|
763
|
+
log_info "Created $_CHUNK_COUNTER chunks for: $file_path"
|
|
764
|
+
echo "$chunks"
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
# Helper to create chunk for extracted content
|
|
768
|
+
# Usage: _create_chunk_for_content file content start end type lang query_keywords
|
|
769
|
+
_create_chunk_for_content() {
|
|
770
|
+
local file_path="$1"
|
|
771
|
+
local content="$2"
|
|
772
|
+
local start_line="$3"
|
|
773
|
+
local end_line="$4"
|
|
774
|
+
local chunk_type="$5"
|
|
775
|
+
local lang="$6"
|
|
776
|
+
local query_keywords="$7"
|
|
777
|
+
|
|
778
|
+
local chunk_id priority deps checksum
|
|
779
|
+
chunk_id=$(_generate_chunk_id "$file_path" "$_CHUNK_COUNTER")
|
|
780
|
+
priority=$(_calculate_priority "$content" "$chunk_type" "$query_keywords")
|
|
781
|
+
deps=$(_extract_dependencies "$content" "$lang")
|
|
782
|
+
checksum=$(_calculate_checksum "$content")
|
|
783
|
+
|
|
784
|
+
_create_chunk_json "$chunk_id" "$content" "$start_line" "$end_line" "$chunk_type" "$priority" "$deps" "$checksum"
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
# Split a large semantic unit into smaller chunks
|
|
788
|
+
# Usage: _split_large_unit file start end type lang query_keywords
|
|
789
|
+
_split_large_unit() {
|
|
790
|
+
local file_path="$1"
|
|
791
|
+
local start_line="$2"
|
|
792
|
+
local end_line="$3"
|
|
793
|
+
local unit_type="$4"
|
|
794
|
+
local lang="$5"
|
|
795
|
+
local query_keywords="$6"
|
|
796
|
+
|
|
797
|
+
local chunks="[]"
|
|
798
|
+
local max_lines=$((CHUNK_MAX_TOKENS / 10)) # Rough estimate: 10 tokens per line
|
|
799
|
+
local current_start=$start_line
|
|
800
|
+
|
|
801
|
+
while [[ $current_start -le $end_line ]]; do
|
|
802
|
+
local current_end=$((current_start + max_lines - 1))
|
|
803
|
+
if [[ $current_end -gt $end_line ]]; then
|
|
804
|
+
current_end=$end_line
|
|
805
|
+
fi
|
|
806
|
+
|
|
807
|
+
# Add overlap for non-first chunks
|
|
808
|
+
local overlap_start=$current_start
|
|
809
|
+
if [[ $current_start -gt $start_line ]]; then
|
|
810
|
+
overlap_start=$((current_start - CHUNK_OVERLAP_LINES))
|
|
811
|
+
if [[ $overlap_start -lt $start_line ]]; then
|
|
812
|
+
overlap_start=$start_line
|
|
813
|
+
fi
|
|
814
|
+
fi
|
|
815
|
+
|
|
816
|
+
local content
|
|
817
|
+
content=$(_extract_lines "$file_path" "$overlap_start" "$current_end")
|
|
818
|
+
|
|
819
|
+
_CHUNK_COUNTER=$((_CHUNK_COUNTER + 1))
|
|
820
|
+
local chunk
|
|
821
|
+
chunk=$(_create_chunk_for_content "$file_path" "$content" "$overlap_start" "$current_end" "${unit_type}_part" "$lang" "$query_keywords")
|
|
822
|
+
chunks=$(echo "$chunks" | jq --argjson chunk "$chunk" '. + [$chunk]')
|
|
823
|
+
|
|
824
|
+
current_start=$((current_end + 1))
|
|
825
|
+
done
|
|
826
|
+
|
|
827
|
+
echo "$chunks"
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
# Fallback chunking by token limit when no semantic boundaries found
|
|
831
|
+
# Usage: _chunk_by_tokens file query_keywords
|
|
832
|
+
_chunk_by_tokens() {
|
|
833
|
+
local file_path="$1"
|
|
834
|
+
local query_keywords="$2"
|
|
835
|
+
|
|
836
|
+
local lang
|
|
837
|
+
lang=$(_detect_language "$file_path")
|
|
838
|
+
|
|
839
|
+
local total_lines
|
|
840
|
+
total_lines=$(_count_lines "$file_path")
|
|
841
|
+
|
|
842
|
+
local chunks="[]"
|
|
843
|
+
local max_lines=$((CHUNK_MAX_TOKENS / 10))
|
|
844
|
+
local current_start=1
|
|
845
|
+
|
|
846
|
+
while [[ $current_start -le $total_lines ]]; do
|
|
847
|
+
local current_end=$((current_start + max_lines - 1))
|
|
848
|
+
if [[ $current_end -gt $total_lines ]]; then
|
|
849
|
+
current_end=$total_lines
|
|
850
|
+
fi
|
|
851
|
+
|
|
852
|
+
# Add overlap for non-first chunks
|
|
853
|
+
local overlap_start=$current_start
|
|
854
|
+
if [[ $current_start -gt 1 ]]; then
|
|
855
|
+
overlap_start=$((current_start - CHUNK_OVERLAP_LINES))
|
|
856
|
+
if [[ $overlap_start -lt 1 ]]; then
|
|
857
|
+
overlap_start=1
|
|
858
|
+
fi
|
|
859
|
+
fi
|
|
860
|
+
|
|
861
|
+
local content
|
|
862
|
+
content=$(_extract_lines "$file_path" "$overlap_start" "$current_end")
|
|
863
|
+
|
|
864
|
+
_CHUNK_COUNTER=$((_CHUNK_COUNTER + 1))
|
|
865
|
+
local chunk
|
|
866
|
+
chunk=$(_create_chunk_for_content "$file_path" "$content" "$overlap_start" "$current_end" "token_based" "$lang" "$query_keywords")
|
|
867
|
+
chunks=$(echo "$chunks" | jq --argjson chunk "$chunk" '. + [$chunk]')
|
|
868
|
+
|
|
869
|
+
current_start=$((current_end + 1))
|
|
870
|
+
done
|
|
871
|
+
|
|
872
|
+
# Save to session file
|
|
873
|
+
local session_file="$CHUNK_TEMP_DIR/${_CHUNK_SESSION_ID}_chunks.json"
|
|
874
|
+
echo "$chunks" > "$session_file"
|
|
875
|
+
chmod 600 "$session_file" 2>/dev/null || true
|
|
876
|
+
|
|
877
|
+
log_info "Created $_CHUNK_COUNTER token-based chunks for: $file_path"
|
|
878
|
+
echo "$chunks"
|
|
879
|
+
}
|
|
880
|
+
|