claude-code-workflow 6.3.19 → 6.3.20
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/agents/issue-plan-agent.md +31 -2
- package/.claude/commands/issue/new.md +92 -2
- package/.claude/commands/issue/plan.md +3 -2
- package/.codex/prompts/issue-execute.md +5 -0
- package/ccw/dist/core/routes/litellm-api-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/litellm-api-routes.js +8 -0
- package/ccw/dist/core/routes/litellm-api-routes.js.map +1 -1
- package/ccw/dist/core/server.d.ts.map +1 -1
- package/ccw/dist/core/server.js +5 -0
- package/ccw/dist/core/server.js.map +1 -1
- package/ccw/dist/core/services/api-key-tester.d.ts +11 -0
- package/ccw/dist/core/services/api-key-tester.d.ts.map +1 -1
- package/ccw/dist/core/services/api-key-tester.js +30 -10
- package/ccw/dist/core/services/api-key-tester.js.map +1 -1
- package/ccw/dist/core/services/health-check-service.d.ts +6 -0
- package/ccw/dist/core/services/health-check-service.d.ts.map +1 -1
- package/ccw/dist/core/services/health-check-service.js +22 -0
- package/ccw/dist/core/services/health-check-service.js.map +1 -1
- package/ccw/src/core/routes/litellm-api-routes.ts +8 -0
- package/ccw/src/core/server.ts +6 -0
- package/ccw/src/core/services/api-key-tester.ts +33 -10
- package/ccw/src/core/services/health-check-service.ts +26 -0
- package/ccw/src/templates/dashboard-js/i18n.js +10 -0
- package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +10 -0
- package/codex-lens/src/codexlens/__pycache__/config.cpython-312.pyc +0 -0
- package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/__pycache__/env_config.cpython-312.pyc +0 -0
- package/codex-lens/src/codexlens/__pycache__/env_config.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/embedding_manager.cpython-312.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/embedding_manager.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/embedding_manager.py +13 -4
- package/codex-lens/src/codexlens/config.py +35 -0
- package/codex-lens/src/codexlens/env_config.py +6 -0
- package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-312.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-312.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/chain_search.py +10 -0
- package/codex-lens/src/codexlens/search/ranking.py +50 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/chunker.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/chunker.py +328 -23
- package/codex-lens/src/codexlens/semantic/reranker/__pycache__/__init__.cpython-312.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/reranker/__pycache__/api_reranker.cpython-312.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/reranker/__pycache__/base.cpython-312.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/reranker/__pycache__/factory.cpython-312.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/reranker/__pycache__/fastembed_reranker.cpython-312.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/reranker/__pycache__/legacy.cpython-312.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/reranker/__pycache__/onnx_reranker.cpython-312.pyc +0 -0
- package/codex-lens/src/codexlens/storage/__pycache__/index_tree.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/index_tree.py +46 -2
- package/package.json +1 -1
|
@@ -43,6 +43,250 @@ class ChunkConfig:
|
|
|
43
43
|
strategy: str = "auto" # Chunking strategy: auto, symbol, sliding_window, hybrid
|
|
44
44
|
min_chunk_size: int = 50 # Minimum chunk size
|
|
45
45
|
skip_token_count: bool = False # Skip expensive token counting (use char/4 estimate)
|
|
46
|
+
strip_comments: bool = True # Remove comments from chunk content for embedding
|
|
47
|
+
strip_docstrings: bool = True # Remove docstrings from chunk content for embedding
|
|
48
|
+
preserve_original: bool = True # Store original content in metadata when stripping
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class CommentStripper:
|
|
52
|
+
"""Remove comments from source code while preserving structure."""
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def strip_python_comments(content: str) -> str:
|
|
56
|
+
"""Strip Python comments (# style) but preserve docstrings.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
content: Python source code
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Code with comments removed
|
|
63
|
+
"""
|
|
64
|
+
lines = content.splitlines(keepends=True)
|
|
65
|
+
result_lines: List[str] = []
|
|
66
|
+
in_string = False
|
|
67
|
+
string_char = None
|
|
68
|
+
|
|
69
|
+
for line in lines:
|
|
70
|
+
new_line = []
|
|
71
|
+
i = 0
|
|
72
|
+
while i < len(line):
|
|
73
|
+
char = line[i]
|
|
74
|
+
|
|
75
|
+
# Handle string literals
|
|
76
|
+
if char in ('"', "'") and not in_string:
|
|
77
|
+
# Check for triple quotes
|
|
78
|
+
if line[i:i+3] in ('"""', "'''"):
|
|
79
|
+
in_string = True
|
|
80
|
+
string_char = line[i:i+3]
|
|
81
|
+
new_line.append(line[i:i+3])
|
|
82
|
+
i += 3
|
|
83
|
+
continue
|
|
84
|
+
else:
|
|
85
|
+
in_string = True
|
|
86
|
+
string_char = char
|
|
87
|
+
elif in_string:
|
|
88
|
+
if string_char and len(string_char) == 3:
|
|
89
|
+
if line[i:i+3] == string_char:
|
|
90
|
+
in_string = False
|
|
91
|
+
new_line.append(line[i:i+3])
|
|
92
|
+
i += 3
|
|
93
|
+
string_char = None
|
|
94
|
+
continue
|
|
95
|
+
elif char == string_char:
|
|
96
|
+
# Check for escape
|
|
97
|
+
if i > 0 and line[i-1] != '\\':
|
|
98
|
+
in_string = False
|
|
99
|
+
string_char = None
|
|
100
|
+
|
|
101
|
+
# Handle comments (only outside strings)
|
|
102
|
+
if char == '#' and not in_string:
|
|
103
|
+
# Rest of line is comment, skip it
|
|
104
|
+
new_line.append('\n' if line.endswith('\n') else '')
|
|
105
|
+
break
|
|
106
|
+
|
|
107
|
+
new_line.append(char)
|
|
108
|
+
i += 1
|
|
109
|
+
|
|
110
|
+
result_lines.append(''.join(new_line))
|
|
111
|
+
|
|
112
|
+
return ''.join(result_lines)
|
|
113
|
+
|
|
114
|
+
@staticmethod
|
|
115
|
+
def strip_c_style_comments(content: str) -> str:
|
|
116
|
+
"""Strip C-style comments (// and /* */) from code.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
content: Source code with C-style comments
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Code with comments removed
|
|
123
|
+
"""
|
|
124
|
+
result = []
|
|
125
|
+
i = 0
|
|
126
|
+
in_string = False
|
|
127
|
+
string_char = None
|
|
128
|
+
in_multiline_comment = False
|
|
129
|
+
|
|
130
|
+
while i < len(content):
|
|
131
|
+
# Handle multi-line comment end
|
|
132
|
+
if in_multiline_comment:
|
|
133
|
+
if content[i:i+2] == '*/':
|
|
134
|
+
in_multiline_comment = False
|
|
135
|
+
i += 2
|
|
136
|
+
continue
|
|
137
|
+
i += 1
|
|
138
|
+
continue
|
|
139
|
+
|
|
140
|
+
char = content[i]
|
|
141
|
+
|
|
142
|
+
# Handle string literals
|
|
143
|
+
if char in ('"', "'", '`') and not in_string:
|
|
144
|
+
in_string = True
|
|
145
|
+
string_char = char
|
|
146
|
+
result.append(char)
|
|
147
|
+
i += 1
|
|
148
|
+
continue
|
|
149
|
+
elif in_string:
|
|
150
|
+
result.append(char)
|
|
151
|
+
if char == string_char and (i == 0 or content[i-1] != '\\'):
|
|
152
|
+
in_string = False
|
|
153
|
+
string_char = None
|
|
154
|
+
i += 1
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
# Handle comments
|
|
158
|
+
if content[i:i+2] == '//':
|
|
159
|
+
# Single line comment - skip to end of line
|
|
160
|
+
while i < len(content) and content[i] != '\n':
|
|
161
|
+
i += 1
|
|
162
|
+
if i < len(content):
|
|
163
|
+
result.append('\n')
|
|
164
|
+
i += 1
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
if content[i:i+2] == '/*':
|
|
168
|
+
in_multiline_comment = True
|
|
169
|
+
i += 2
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
result.append(char)
|
|
173
|
+
i += 1
|
|
174
|
+
|
|
175
|
+
return ''.join(result)
|
|
176
|
+
|
|
177
|
+
@classmethod
|
|
178
|
+
def strip_comments(cls, content: str, language: str) -> str:
|
|
179
|
+
"""Strip comments based on language.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
content: Source code content
|
|
183
|
+
language: Programming language
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Code with comments removed
|
|
187
|
+
"""
|
|
188
|
+
if language == "python":
|
|
189
|
+
return cls.strip_python_comments(content)
|
|
190
|
+
elif language in {"javascript", "typescript", "java", "c", "cpp", "go", "rust"}:
|
|
191
|
+
return cls.strip_c_style_comments(content)
|
|
192
|
+
return content
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class DocstringStripper:
|
|
196
|
+
"""Remove docstrings from source code."""
|
|
197
|
+
|
|
198
|
+
@staticmethod
|
|
199
|
+
def strip_python_docstrings(content: str) -> str:
|
|
200
|
+
"""Strip Python docstrings (triple-quoted strings at module/class/function level).
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
content: Python source code
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Code with docstrings removed
|
|
207
|
+
"""
|
|
208
|
+
lines = content.splitlines(keepends=True)
|
|
209
|
+
result_lines: List[str] = []
|
|
210
|
+
i = 0
|
|
211
|
+
|
|
212
|
+
while i < len(lines):
|
|
213
|
+
line = lines[i]
|
|
214
|
+
stripped = line.strip()
|
|
215
|
+
|
|
216
|
+
# Check for docstring start
|
|
217
|
+
if stripped.startswith('"""') or stripped.startswith("'''"):
|
|
218
|
+
quote_type = '"""' if stripped.startswith('"""') else "'''"
|
|
219
|
+
|
|
220
|
+
# Single line docstring
|
|
221
|
+
if stripped.count(quote_type) >= 2:
|
|
222
|
+
# Skip this line (docstring)
|
|
223
|
+
i += 1
|
|
224
|
+
continue
|
|
225
|
+
|
|
226
|
+
# Multi-line docstring - skip until closing
|
|
227
|
+
i += 1
|
|
228
|
+
while i < len(lines):
|
|
229
|
+
if quote_type in lines[i]:
|
|
230
|
+
i += 1
|
|
231
|
+
break
|
|
232
|
+
i += 1
|
|
233
|
+
continue
|
|
234
|
+
|
|
235
|
+
result_lines.append(line)
|
|
236
|
+
i += 1
|
|
237
|
+
|
|
238
|
+
return ''.join(result_lines)
|
|
239
|
+
|
|
240
|
+
@staticmethod
|
|
241
|
+
def strip_jsdoc_comments(content: str) -> str:
|
|
242
|
+
"""Strip JSDoc comments (/** ... */) from code.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
content: JavaScript/TypeScript source code
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Code with JSDoc comments removed
|
|
249
|
+
"""
|
|
250
|
+
result = []
|
|
251
|
+
i = 0
|
|
252
|
+
in_jsdoc = False
|
|
253
|
+
|
|
254
|
+
while i < len(content):
|
|
255
|
+
if in_jsdoc:
|
|
256
|
+
if content[i:i+2] == '*/':
|
|
257
|
+
in_jsdoc = False
|
|
258
|
+
i += 2
|
|
259
|
+
continue
|
|
260
|
+
i += 1
|
|
261
|
+
continue
|
|
262
|
+
|
|
263
|
+
# Check for JSDoc start (/** but not /*)
|
|
264
|
+
if content[i:i+3] == '/**':
|
|
265
|
+
in_jsdoc = True
|
|
266
|
+
i += 3
|
|
267
|
+
continue
|
|
268
|
+
|
|
269
|
+
result.append(content[i])
|
|
270
|
+
i += 1
|
|
271
|
+
|
|
272
|
+
return ''.join(result)
|
|
273
|
+
|
|
274
|
+
@classmethod
|
|
275
|
+
def strip_docstrings(cls, content: str, language: str) -> str:
|
|
276
|
+
"""Strip docstrings based on language.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
content: Source code content
|
|
280
|
+
language: Programming language
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
Code with docstrings removed
|
|
284
|
+
"""
|
|
285
|
+
if language == "python":
|
|
286
|
+
return cls.strip_python_docstrings(content)
|
|
287
|
+
elif language in {"javascript", "typescript"}:
|
|
288
|
+
return cls.strip_jsdoc_comments(content)
|
|
289
|
+
return content
|
|
46
290
|
|
|
47
291
|
|
|
48
292
|
class Chunker:
|
|
@@ -51,6 +295,33 @@ class Chunker:
|
|
|
51
295
|
def __init__(self, config: ChunkConfig | None = None) -> None:
|
|
52
296
|
self.config = config or ChunkConfig()
|
|
53
297
|
self._tokenizer = get_default_tokenizer()
|
|
298
|
+
self._comment_stripper = CommentStripper()
|
|
299
|
+
self._docstring_stripper = DocstringStripper()
|
|
300
|
+
|
|
301
|
+
def _process_content(self, content: str, language: str) -> Tuple[str, Optional[str]]:
|
|
302
|
+
"""Process chunk content by stripping comments/docstrings if configured.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
content: Original chunk content
|
|
306
|
+
language: Programming language
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
Tuple of (processed_content, original_content_if_preserved)
|
|
310
|
+
"""
|
|
311
|
+
original = content if self.config.preserve_original else None
|
|
312
|
+
processed = content
|
|
313
|
+
|
|
314
|
+
if self.config.strip_comments:
|
|
315
|
+
processed = self._comment_stripper.strip_comments(processed, language)
|
|
316
|
+
|
|
317
|
+
if self.config.strip_docstrings:
|
|
318
|
+
processed = self._docstring_stripper.strip_docstrings(processed, language)
|
|
319
|
+
|
|
320
|
+
# If nothing changed, don't store original
|
|
321
|
+
if processed == content:
|
|
322
|
+
original = None
|
|
323
|
+
|
|
324
|
+
return processed, original
|
|
54
325
|
|
|
55
326
|
def _estimate_token_count(self, text: str) -> int:
|
|
56
327
|
"""Estimate token count based on config.
|
|
@@ -120,30 +391,45 @@ class Chunker:
|
|
|
120
391
|
sub_chunk.metadata["symbol_name"] = symbol.name
|
|
121
392
|
sub_chunk.metadata["symbol_kind"] = symbol.kind
|
|
122
393
|
sub_chunk.metadata["strategy"] = "symbol_split"
|
|
394
|
+
sub_chunk.metadata["chunk_type"] = "code"
|
|
123
395
|
sub_chunk.metadata["parent_symbol_range"] = (start_line, end_line)
|
|
124
396
|
|
|
125
397
|
chunks.extend(sub_chunks)
|
|
126
398
|
else:
|
|
399
|
+
# Process content (strip comments/docstrings if configured)
|
|
400
|
+
processed_content, original_content = self._process_content(chunk_content, language)
|
|
401
|
+
|
|
402
|
+
# Skip if processed content is too small
|
|
403
|
+
if len(processed_content.strip()) < self.config.min_chunk_size:
|
|
404
|
+
continue
|
|
405
|
+
|
|
127
406
|
# Calculate token count if not provided
|
|
128
407
|
token_count = None
|
|
129
408
|
if symbol_token_counts and symbol.name in symbol_token_counts:
|
|
130
409
|
token_count = symbol_token_counts[symbol.name]
|
|
131
410
|
else:
|
|
132
|
-
token_count = self._estimate_token_count(
|
|
411
|
+
token_count = self._estimate_token_count(processed_content)
|
|
412
|
+
|
|
413
|
+
metadata = {
|
|
414
|
+
"file": str(file_path),
|
|
415
|
+
"language": language,
|
|
416
|
+
"symbol_name": symbol.name,
|
|
417
|
+
"symbol_kind": symbol.kind,
|
|
418
|
+
"start_line": start_line,
|
|
419
|
+
"end_line": end_line,
|
|
420
|
+
"strategy": "symbol",
|
|
421
|
+
"chunk_type": "code",
|
|
422
|
+
"token_count": token_count,
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
# Store original content if it was modified
|
|
426
|
+
if original_content is not None:
|
|
427
|
+
metadata["original_content"] = original_content
|
|
133
428
|
|
|
134
429
|
chunks.append(SemanticChunk(
|
|
135
|
-
content=
|
|
430
|
+
content=processed_content,
|
|
136
431
|
embedding=None,
|
|
137
|
-
metadata=
|
|
138
|
-
"file": str(file_path),
|
|
139
|
-
"language": language,
|
|
140
|
-
"symbol_name": symbol.name,
|
|
141
|
-
"symbol_kind": symbol.kind,
|
|
142
|
-
"start_line": start_line,
|
|
143
|
-
"end_line": end_line,
|
|
144
|
-
"strategy": "symbol",
|
|
145
|
-
"token_count": token_count,
|
|
146
|
-
}
|
|
432
|
+
metadata=metadata
|
|
147
433
|
))
|
|
148
434
|
|
|
149
435
|
return chunks
|
|
@@ -188,7 +474,19 @@ class Chunker:
|
|
|
188
474
|
chunk_content = "".join(lines[start:end])
|
|
189
475
|
|
|
190
476
|
if len(chunk_content.strip()) >= self.config.min_chunk_size:
|
|
191
|
-
|
|
477
|
+
# Process content (strip comments/docstrings if configured)
|
|
478
|
+
processed_content, original_content = self._process_content(chunk_content, language)
|
|
479
|
+
|
|
480
|
+
# Skip if processed content is too small
|
|
481
|
+
if len(processed_content.strip()) < self.config.min_chunk_size:
|
|
482
|
+
# Move window forward
|
|
483
|
+
step = lines_per_chunk - overlap_lines
|
|
484
|
+
if step <= 0:
|
|
485
|
+
step = 1
|
|
486
|
+
start += step
|
|
487
|
+
continue
|
|
488
|
+
|
|
489
|
+
token_count = self._estimate_token_count(processed_content)
|
|
192
490
|
|
|
193
491
|
# Calculate correct line numbers
|
|
194
492
|
if line_mapping:
|
|
@@ -200,18 +498,25 @@ class Chunker:
|
|
|
200
498
|
start_line = start + 1
|
|
201
499
|
end_line = end
|
|
202
500
|
|
|
501
|
+
metadata = {
|
|
502
|
+
"file": str(file_path),
|
|
503
|
+
"language": language,
|
|
504
|
+
"chunk_index": chunk_idx,
|
|
505
|
+
"start_line": start_line,
|
|
506
|
+
"end_line": end_line,
|
|
507
|
+
"strategy": "sliding_window",
|
|
508
|
+
"chunk_type": "code",
|
|
509
|
+
"token_count": token_count,
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
# Store original content if it was modified
|
|
513
|
+
if original_content is not None:
|
|
514
|
+
metadata["original_content"] = original_content
|
|
515
|
+
|
|
203
516
|
chunks.append(SemanticChunk(
|
|
204
|
-
content=
|
|
517
|
+
content=processed_content,
|
|
205
518
|
embedding=None,
|
|
206
|
-
metadata=
|
|
207
|
-
"file": str(file_path),
|
|
208
|
-
"language": language,
|
|
209
|
-
"chunk_index": chunk_idx,
|
|
210
|
-
"start_line": start_line,
|
|
211
|
-
"end_line": end_line,
|
|
212
|
-
"strategy": "sliding_window",
|
|
213
|
-
"token_count": token_count,
|
|
214
|
-
}
|
|
519
|
+
metadata=metadata
|
|
215
520
|
))
|
|
216
521
|
chunk_idx += 1
|
|
217
522
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/codex-lens/src/codexlens/semantic/reranker/__pycache__/fastembed_reranker.cpython-312.pyc
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -412,7 +412,8 @@ class IndexTreeBuilder:
|
|
|
412
412
|
A directory is indexed if:
|
|
413
413
|
1. It's not in IGNORE_DIRS
|
|
414
414
|
2. It doesn't start with '.'
|
|
415
|
-
3. It contains at least one supported language file
|
|
415
|
+
3. It contains at least one supported language file, OR
|
|
416
|
+
4. It has subdirectories that contain supported files (transitive)
|
|
416
417
|
|
|
417
418
|
Args:
|
|
418
419
|
dir_path: Directory to check
|
|
@@ -427,7 +428,50 @@ class IndexTreeBuilder:
|
|
|
427
428
|
|
|
428
429
|
# Check for supported files in this directory
|
|
429
430
|
source_files = self._iter_source_files(dir_path, languages)
|
|
430
|
-
|
|
431
|
+
if len(source_files) > 0:
|
|
432
|
+
return True
|
|
433
|
+
|
|
434
|
+
# Check if any subdirectory has indexable files (transitive)
|
|
435
|
+
# This handles cases like 'src' which has no direct files but has 'src/codexlens'
|
|
436
|
+
for item in dir_path.iterdir():
|
|
437
|
+
if not item.is_dir():
|
|
438
|
+
continue
|
|
439
|
+
if item.name in self.IGNORE_DIRS or item.name.startswith("."):
|
|
440
|
+
continue
|
|
441
|
+
# Recursively check subdirectories
|
|
442
|
+
if self._has_indexable_files_recursive(item, languages):
|
|
443
|
+
return True
|
|
444
|
+
|
|
445
|
+
return False
|
|
446
|
+
|
|
447
|
+
def _has_indexable_files_recursive(self, dir_path: Path, languages: List[str] = None) -> bool:
|
|
448
|
+
"""Check if directory or any subdirectory has indexable files.
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
dir_path: Directory to check
|
|
452
|
+
languages: Optional language filter
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
True if directory tree contains indexable files
|
|
456
|
+
"""
|
|
457
|
+
# Check for supported files in this directory
|
|
458
|
+
source_files = self._iter_source_files(dir_path, languages)
|
|
459
|
+
if len(source_files) > 0:
|
|
460
|
+
return True
|
|
461
|
+
|
|
462
|
+
# Check subdirectories
|
|
463
|
+
try:
|
|
464
|
+
for item in dir_path.iterdir():
|
|
465
|
+
if not item.is_dir():
|
|
466
|
+
continue
|
|
467
|
+
if item.name in self.IGNORE_DIRS or item.name.startswith("."):
|
|
468
|
+
continue
|
|
469
|
+
if self._has_indexable_files_recursive(item, languages):
|
|
470
|
+
return True
|
|
471
|
+
except PermissionError:
|
|
472
|
+
pass
|
|
473
|
+
|
|
474
|
+
return False
|
|
431
475
|
|
|
432
476
|
def _build_level_parallel(
|
|
433
477
|
self,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-workflow",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.20",
|
|
4
4
|
"description": "JSON-driven multi-agent development framework with intelligent CLI orchestration (Gemini/Qwen/Codex), context-first architecture, and automated workflow execution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "ccw/src/index.js",
|