claude-code-workflow 6.3.11 → 6.3.13
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/CLAUDE.md +33 -33
- package/.claude/agents/issue-plan-agent.md +77 -5
- package/.claude/agents/issue-queue-agent.md +122 -18
- package/.claude/commands/issue/execute.md +53 -40
- package/.claude/commands/issue/new.md +113 -11
- package/.claude/commands/issue/plan.md +112 -37
- package/.claude/commands/issue/queue.md +28 -18
- package/.claude/skills/software-manual/scripts/assemble_docsify.py +584 -0
- package/.claude/skills/software-manual/templates/css/docsify-base.css +984 -0
- package/.claude/skills/software-manual/templates/docsify-shell.html +466 -0
- package/.claude/workflows/cli-templates/schemas/issues-jsonl-schema.json +141 -168
- package/.claude/workflows/cli-templates/schemas/solution-schema.json +3 -2
- package/.codex/prompts/issue-execute.md +3 -3
- package/.codex/prompts/issue-queue.md +3 -3
- package/ccw/dist/commands/issue.d.ts.map +1 -1
- package/ccw/dist/commands/issue.js +2 -1
- package/ccw/dist/commands/issue.js.map +1 -1
- package/ccw/src/commands/issue.ts +2 -1
- package/ccw/src/templates/dashboard-css/33-cli-stream-viewer.css +580 -467
- package/ccw/src/templates/dashboard-js/components/cli-stream-viewer.js +532 -461
- package/ccw/src/templates/dashboard-js/components/notifications.js +774 -774
- package/ccw/src/templates/dashboard-js/i18n.js +4 -0
- package/ccw/src/templates/dashboard.html +10 -0
- package/ccw/src/tools/claude-cli-tools.ts +388 -388
- package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/config.py +19 -3
- package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/ranking.py +15 -4
- package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/vector_store.py +57 -47
- package/codex-lens/src/codexlens/storage/__pycache__/registry.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/registry.py +114 -101
- package/package.json +83 -83
|
Binary file
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
+
import logging
|
|
6
7
|
import os
|
|
7
8
|
from dataclasses import dataclass, field
|
|
8
9
|
from functools import cached_property
|
|
@@ -18,6 +19,8 @@ WORKSPACE_DIR_NAME = ".codexlens"
|
|
|
18
19
|
# Settings file name
|
|
19
20
|
SETTINGS_FILE_NAME = "settings.json"
|
|
20
21
|
|
|
22
|
+
log = logging.getLogger(__name__)
|
|
23
|
+
|
|
21
24
|
|
|
22
25
|
def _default_global_dir() -> Path:
|
|
23
26
|
"""Get global CodexLens data directory."""
|
|
@@ -200,7 +203,15 @@ class Config:
|
|
|
200
203
|
# Load embedding settings
|
|
201
204
|
embedding = settings.get("embedding", {})
|
|
202
205
|
if "backend" in embedding:
|
|
203
|
-
|
|
206
|
+
backend = embedding["backend"]
|
|
207
|
+
if backend in {"fastembed", "litellm"}:
|
|
208
|
+
self.embedding_backend = backend
|
|
209
|
+
else:
|
|
210
|
+
log.warning(
|
|
211
|
+
"Invalid embedding backend in %s: %r (expected 'fastembed' or 'litellm')",
|
|
212
|
+
self.settings_path,
|
|
213
|
+
backend,
|
|
214
|
+
)
|
|
204
215
|
if "model" in embedding:
|
|
205
216
|
self.embedding_model = embedding["model"]
|
|
206
217
|
if "use_gpu" in embedding:
|
|
@@ -224,8 +235,13 @@ class Config:
|
|
|
224
235
|
self.llm_timeout_ms = llm["timeout_ms"]
|
|
225
236
|
if "batch_size" in llm:
|
|
226
237
|
self.llm_batch_size = llm["batch_size"]
|
|
227
|
-
except Exception:
|
|
228
|
-
|
|
238
|
+
except Exception as exc:
|
|
239
|
+
log.warning(
|
|
240
|
+
"Failed to load settings from %s (%s): %s",
|
|
241
|
+
self.settings_path,
|
|
242
|
+
type(exc).__name__,
|
|
243
|
+
exc,
|
|
244
|
+
)
|
|
229
245
|
|
|
230
246
|
@classmethod
|
|
231
247
|
def load(cls) -> "Config":
|
|
Binary file
|
|
@@ -22,12 +22,23 @@ class QueryIntent(str, Enum):
|
|
|
22
22
|
MIXED = "mixed"
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
def normalize_weights(weights: Dict[str, float]) -> Dict[str, float]:
|
|
25
|
+
def normalize_weights(weights: Dict[str, float | None]) -> Dict[str, float | None]:
|
|
26
26
|
"""Normalize weights to sum to 1.0 (best-effort)."""
|
|
27
27
|
total = sum(float(v) for v in weights.values() if v is not None)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
|
|
29
|
+
# NaN total: do not attempt to normalize (division would propagate NaNs).
|
|
30
|
+
if math.isnan(total):
|
|
31
|
+
return dict(weights)
|
|
32
|
+
|
|
33
|
+
# Infinite total: do not attempt to normalize (division yields 0 or NaN).
|
|
34
|
+
if not math.isfinite(total):
|
|
35
|
+
return dict(weights)
|
|
36
|
+
|
|
37
|
+
# Zero/negative total: do not attempt to normalize (invalid denominator).
|
|
38
|
+
if total <= 0:
|
|
39
|
+
return dict(weights)
|
|
40
|
+
|
|
41
|
+
return {k: (float(v) / total if v is not None else None) for k, v in weights.items()}
|
|
31
42
|
|
|
32
43
|
|
|
33
44
|
def detect_query_intent(query: str) -> QueryIntent:
|
|
Binary file
|
|
@@ -16,43 +16,53 @@ import threading
|
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
from typing import Any, Dict, List, Optional, Tuple
|
|
18
18
|
|
|
19
|
-
from codexlens.entities import SearchResult, SemanticChunk
|
|
20
|
-
from codexlens.errors import StorageError
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
from codexlens.entities import SearchResult, SemanticChunk
|
|
20
|
+
from codexlens.errors import StorageError
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
import numpy as np
|
|
24
|
+
NUMPY_AVAILABLE = True
|
|
25
|
+
except ImportError:
|
|
26
|
+
np = None # type: ignore[assignment]
|
|
27
|
+
NUMPY_AVAILABLE = False
|
|
28
|
+
|
|
29
|
+
# Try to import ANN index (optional hnswlib dependency)
|
|
30
|
+
try:
|
|
31
|
+
from codexlens.semantic.ann_index import ANNIndex, HNSWLIB_AVAILABLE
|
|
30
32
|
except ImportError:
|
|
31
33
|
HNSWLIB_AVAILABLE = False
|
|
32
34
|
ANNIndex = None
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
logger = logging.getLogger(__name__)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
# Epsilon used to guard against floating point precision edge cases (e.g., near-zero norms).
|
|
40
|
+
EPSILON = 1e-10
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _cosine_similarity(a: List[float], b: List[float]) -> float:
|
|
44
|
+
"""Compute cosine similarity between two vectors."""
|
|
45
|
+
if not NUMPY_AVAILABLE:
|
|
46
|
+
raise ImportError("numpy required for vector operations")
|
|
47
|
+
|
|
48
|
+
a_arr = np.array(a)
|
|
49
|
+
b_arr = np.array(b)
|
|
50
|
+
|
|
51
|
+
norm_a = np.linalg.norm(a_arr)
|
|
52
|
+
norm_b = np.linalg.norm(b_arr)
|
|
53
|
+
|
|
54
|
+
# Use epsilon tolerance to avoid division by (near-)zero due to floating point precision.
|
|
55
|
+
if norm_a < EPSILON or norm_b < EPSILON:
|
|
56
|
+
return 0.0
|
|
57
|
+
|
|
58
|
+
denom = norm_a * norm_b
|
|
59
|
+
if denom < EPSILON:
|
|
60
|
+
return 0.0
|
|
61
|
+
|
|
62
|
+
return float(np.dot(a_arr, b_arr) / denom)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class VectorStore:
|
|
56
66
|
"""SQLite-based vector storage with HNSW-accelerated similarity search.
|
|
57
67
|
|
|
58
68
|
Performance optimizations:
|
|
@@ -67,12 +77,12 @@ class VectorStore:
|
|
|
67
77
|
# Default embedding dimension (used when creating new index)
|
|
68
78
|
DEFAULT_DIM = 768
|
|
69
79
|
|
|
70
|
-
def __init__(self, db_path: str | Path) -> None:
|
|
71
|
-
if not
|
|
72
|
-
raise ImportError(
|
|
73
|
-
"Semantic search dependencies not available. "
|
|
74
|
-
"Install with: pip install codexlens[semantic]"
|
|
75
|
-
)
|
|
80
|
+
def __init__(self, db_path: str | Path) -> None:
|
|
81
|
+
if not NUMPY_AVAILABLE:
|
|
82
|
+
raise ImportError(
|
|
83
|
+
"Semantic search dependencies not available. "
|
|
84
|
+
"Install with: pip install codexlens[semantic]"
|
|
85
|
+
)
|
|
76
86
|
|
|
77
87
|
self.db_path = Path(db_path)
|
|
78
88
|
self.db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -299,14 +309,14 @@ class VectorStore:
|
|
|
299
309
|
]
|
|
300
310
|
self._embedding_matrix = np.vstack(embeddings)
|
|
301
311
|
|
|
302
|
-
# Pre-compute norms for faster similarity calculation
|
|
303
|
-
self._embedding_norms = np.linalg.norm(
|
|
304
|
-
self._embedding_matrix, axis=1, keepdims=True
|
|
305
|
-
)
|
|
306
|
-
# Avoid division by zero
|
|
307
|
-
self._embedding_norms = np.where(
|
|
308
|
-
self._embedding_norms == 0,
|
|
309
|
-
)
|
|
312
|
+
# Pre-compute norms for faster similarity calculation
|
|
313
|
+
self._embedding_norms = np.linalg.norm(
|
|
314
|
+
self._embedding_matrix, axis=1, keepdims=True
|
|
315
|
+
)
|
|
316
|
+
# Avoid division by zero
|
|
317
|
+
self._embedding_norms = np.where(
|
|
318
|
+
self._embedding_norms == 0, EPSILON, self._embedding_norms
|
|
319
|
+
)
|
|
310
320
|
|
|
311
321
|
return True
|
|
312
322
|
|
|
Binary file
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"""Global project registry for CodexLens - SQLite storage."""
|
|
2
2
|
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
from
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import platform
|
|
6
|
+
import sqlite3
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
10
11
|
from typing import Any, Dict, List, Optional
|
|
11
12
|
|
|
12
13
|
from codexlens.errors import StorageError
|
|
@@ -39,7 +40,7 @@ class DirMapping:
|
|
|
39
40
|
last_updated: float
|
|
40
41
|
|
|
41
42
|
|
|
42
|
-
class RegistryStore:
|
|
43
|
+
class RegistryStore:
|
|
43
44
|
"""Global project registry - SQLite storage.
|
|
44
45
|
|
|
45
46
|
Manages indexed projects and directory-to-index path mappings.
|
|
@@ -106,8 +107,8 @@ class RegistryStore:
|
|
|
106
107
|
conn = self._get_connection()
|
|
107
108
|
self._create_schema(conn)
|
|
108
109
|
|
|
109
|
-
def _create_schema(self, conn: sqlite3.Connection) -> None:
|
|
110
|
-
"""Create database schema."""
|
|
110
|
+
def _create_schema(self, conn: sqlite3.Connection) -> None:
|
|
111
|
+
"""Create database schema."""
|
|
111
112
|
try:
|
|
112
113
|
conn.execute(
|
|
113
114
|
"""
|
|
@@ -150,12 +151,23 @@ class RegistryStore:
|
|
|
150
151
|
)
|
|
151
152
|
|
|
152
153
|
conn.commit()
|
|
153
|
-
except sqlite3.DatabaseError as exc:
|
|
154
|
-
raise StorageError(f"Failed to initialize registry schema: {exc}") from exc
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
154
|
+
except sqlite3.DatabaseError as exc:
|
|
155
|
+
raise StorageError(f"Failed to initialize registry schema: {exc}") from exc
|
|
156
|
+
|
|
157
|
+
def _normalize_path_for_comparison(self, path: Path) -> str:
|
|
158
|
+
"""Normalize paths for comparisons and storage.
|
|
159
|
+
|
|
160
|
+
Windows paths are treated as case-insensitive, so normalize to lowercase.
|
|
161
|
+
Unix platforms preserve case sensitivity.
|
|
162
|
+
"""
|
|
163
|
+
path_str = str(path)
|
|
164
|
+
if platform.system() == "Windows":
|
|
165
|
+
return path_str.lower()
|
|
166
|
+
return path_str
|
|
167
|
+
|
|
168
|
+
# === Project Operations ===
|
|
169
|
+
|
|
170
|
+
def register_project(self, source_root: Path, index_root: Path) -> ProjectInfo:
|
|
159
171
|
"""Register a new project or update existing one.
|
|
160
172
|
|
|
161
173
|
Args:
|
|
@@ -164,12 +176,12 @@ class RegistryStore:
|
|
|
164
176
|
|
|
165
177
|
Returns:
|
|
166
178
|
ProjectInfo for the registered project
|
|
167
|
-
"""
|
|
168
|
-
with self._lock:
|
|
169
|
-
conn = self._get_connection()
|
|
170
|
-
source_root_str =
|
|
171
|
-
index_root_str = str(index_root.resolve())
|
|
172
|
-
now = time.time()
|
|
179
|
+
"""
|
|
180
|
+
with self._lock:
|
|
181
|
+
conn = self._get_connection()
|
|
182
|
+
source_root_str = self._normalize_path_for_comparison(source_root.resolve())
|
|
183
|
+
index_root_str = str(index_root.resolve())
|
|
184
|
+
now = time.time()
|
|
173
185
|
|
|
174
186
|
conn.execute(
|
|
175
187
|
"""
|
|
@@ -194,7 +206,7 @@ class RegistryStore:
|
|
|
194
206
|
|
|
195
207
|
return self._row_to_project_info(row)
|
|
196
208
|
|
|
197
|
-
def unregister_project(self, source_root: Path) -> bool:
|
|
209
|
+
def unregister_project(self, source_root: Path) -> bool:
|
|
198
210
|
"""Remove a project registration (cascades to directory mappings).
|
|
199
211
|
|
|
200
212
|
Args:
|
|
@@ -202,10 +214,10 @@ class RegistryStore:
|
|
|
202
214
|
|
|
203
215
|
Returns:
|
|
204
216
|
True if project was removed, False if not found
|
|
205
|
-
"""
|
|
206
|
-
with self._lock:
|
|
207
|
-
conn = self._get_connection()
|
|
208
|
-
source_root_str =
|
|
217
|
+
"""
|
|
218
|
+
with self._lock:
|
|
219
|
+
conn = self._get_connection()
|
|
220
|
+
source_root_str = self._normalize_path_for_comparison(source_root.resolve())
|
|
209
221
|
|
|
210
222
|
row = conn.execute(
|
|
211
223
|
"SELECT id FROM projects WHERE source_root=?", (source_root_str,)
|
|
@@ -218,7 +230,7 @@ class RegistryStore:
|
|
|
218
230
|
conn.commit()
|
|
219
231
|
return True
|
|
220
232
|
|
|
221
|
-
def get_project(self, source_root: Path) -> Optional[ProjectInfo]:
|
|
233
|
+
def get_project(self, source_root: Path) -> Optional[ProjectInfo]:
|
|
222
234
|
"""Get project information by source root.
|
|
223
235
|
|
|
224
236
|
Args:
|
|
@@ -226,10 +238,10 @@ class RegistryStore:
|
|
|
226
238
|
|
|
227
239
|
Returns:
|
|
228
240
|
ProjectInfo if found, None otherwise
|
|
229
|
-
"""
|
|
230
|
-
with self._lock:
|
|
231
|
-
conn = self._get_connection()
|
|
232
|
-
source_root_str =
|
|
241
|
+
"""
|
|
242
|
+
with self._lock:
|
|
243
|
+
conn = self._get_connection()
|
|
244
|
+
source_root_str = self._normalize_path_for_comparison(source_root.resolve())
|
|
233
245
|
|
|
234
246
|
row = conn.execute(
|
|
235
247
|
"SELECT * FROM projects WHERE source_root=?", (source_root_str,)
|
|
@@ -279,19 +291,19 @@ class RegistryStore:
|
|
|
279
291
|
|
|
280
292
|
return [self._row_to_project_info(row) for row in rows]
|
|
281
293
|
|
|
282
|
-
def update_project_stats(
|
|
283
|
-
self, source_root: Path, total_files: int, total_dirs: int
|
|
284
|
-
) -> None:
|
|
294
|
+
def update_project_stats(
|
|
295
|
+
self, source_root: Path, total_files: int, total_dirs: int
|
|
296
|
+
) -> None:
|
|
285
297
|
"""Update project statistics.
|
|
286
298
|
|
|
287
299
|
Args:
|
|
288
300
|
source_root: Source code root directory
|
|
289
301
|
total_files: Total number of indexed files
|
|
290
302
|
total_dirs: Total number of indexed directories
|
|
291
|
-
"""
|
|
292
|
-
with self._lock:
|
|
293
|
-
conn = self._get_connection()
|
|
294
|
-
source_root_str =
|
|
303
|
+
"""
|
|
304
|
+
with self._lock:
|
|
305
|
+
conn = self._get_connection()
|
|
306
|
+
source_root_str = self._normalize_path_for_comparison(source_root.resolve())
|
|
295
307
|
|
|
296
308
|
conn.execute(
|
|
297
309
|
"""
|
|
@@ -303,16 +315,16 @@ class RegistryStore:
|
|
|
303
315
|
)
|
|
304
316
|
conn.commit()
|
|
305
317
|
|
|
306
|
-
def set_project_status(self, source_root: Path, status: str) -> None:
|
|
318
|
+
def set_project_status(self, source_root: Path, status: str) -> None:
|
|
307
319
|
"""Set project status.
|
|
308
320
|
|
|
309
321
|
Args:
|
|
310
322
|
source_root: Source code root directory
|
|
311
323
|
status: Status string ('active', 'stale', 'removed')
|
|
312
|
-
"""
|
|
313
|
-
with self._lock:
|
|
314
|
-
conn = self._get_connection()
|
|
315
|
-
source_root_str =
|
|
324
|
+
"""
|
|
325
|
+
with self._lock:
|
|
326
|
+
conn = self._get_connection()
|
|
327
|
+
source_root_str = self._normalize_path_for_comparison(source_root.resolve())
|
|
316
328
|
|
|
317
329
|
conn.execute(
|
|
318
330
|
"UPDATE projects SET status=? WHERE source_root=?",
|
|
@@ -322,7 +334,7 @@ class RegistryStore:
|
|
|
322
334
|
|
|
323
335
|
# === Directory Mapping Operations ===
|
|
324
336
|
|
|
325
|
-
def register_dir(
|
|
337
|
+
def register_dir(
|
|
326
338
|
self,
|
|
327
339
|
project_id: int,
|
|
328
340
|
source_path: Path,
|
|
@@ -341,12 +353,12 @@ class RegistryStore:
|
|
|
341
353
|
|
|
342
354
|
Returns:
|
|
343
355
|
DirMapping for the registered directory
|
|
344
|
-
"""
|
|
345
|
-
with self._lock:
|
|
346
|
-
conn = self._get_connection()
|
|
347
|
-
source_path_str =
|
|
348
|
-
index_path_str = str(index_path.resolve())
|
|
349
|
-
now = time.time()
|
|
356
|
+
"""
|
|
357
|
+
with self._lock:
|
|
358
|
+
conn = self._get_connection()
|
|
359
|
+
source_path_str = self._normalize_path_for_comparison(source_path.resolve())
|
|
360
|
+
index_path_str = str(index_path.resolve())
|
|
361
|
+
now = time.time()
|
|
350
362
|
|
|
351
363
|
conn.execute(
|
|
352
364
|
"""
|
|
@@ -374,7 +386,7 @@ class RegistryStore:
|
|
|
374
386
|
|
|
375
387
|
return self._row_to_dir_mapping(row)
|
|
376
388
|
|
|
377
|
-
def unregister_dir(self, source_path: Path) -> bool:
|
|
389
|
+
def unregister_dir(self, source_path: Path) -> bool:
|
|
378
390
|
"""Remove a directory mapping.
|
|
379
391
|
|
|
380
392
|
Args:
|
|
@@ -382,10 +394,10 @@ class RegistryStore:
|
|
|
382
394
|
|
|
383
395
|
Returns:
|
|
384
396
|
True if directory was removed, False if not found
|
|
385
|
-
"""
|
|
386
|
-
with self._lock:
|
|
387
|
-
conn = self._get_connection()
|
|
388
|
-
source_path_str =
|
|
397
|
+
"""
|
|
398
|
+
with self._lock:
|
|
399
|
+
conn = self._get_connection()
|
|
400
|
+
source_path_str = self._normalize_path_for_comparison(source_path.resolve())
|
|
389
401
|
|
|
390
402
|
row = conn.execute(
|
|
391
403
|
"SELECT id FROM dir_mapping WHERE source_path=?", (source_path_str,)
|
|
@@ -398,7 +410,7 @@ class RegistryStore:
|
|
|
398
410
|
conn.commit()
|
|
399
411
|
return True
|
|
400
412
|
|
|
401
|
-
def find_index_path(self, source_path: Path) -> Optional[Path]:
|
|
413
|
+
def find_index_path(self, source_path: Path) -> Optional[Path]:
|
|
402
414
|
"""Find index path for a source directory (exact match).
|
|
403
415
|
|
|
404
416
|
Args:
|
|
@@ -406,10 +418,10 @@ class RegistryStore:
|
|
|
406
418
|
|
|
407
419
|
Returns:
|
|
408
420
|
Index path if found, None otherwise
|
|
409
|
-
"""
|
|
410
|
-
with self._lock:
|
|
411
|
-
conn = self._get_connection()
|
|
412
|
-
source_path_str =
|
|
421
|
+
"""
|
|
422
|
+
with self._lock:
|
|
423
|
+
conn = self._get_connection()
|
|
424
|
+
source_path_str = self._normalize_path_for_comparison(source_path.resolve())
|
|
413
425
|
|
|
414
426
|
row = conn.execute(
|
|
415
427
|
"SELECT index_path FROM dir_mapping WHERE source_path=?",
|
|
@@ -418,7 +430,7 @@ class RegistryStore:
|
|
|
418
430
|
|
|
419
431
|
return Path(row["index_path"]) if row else None
|
|
420
432
|
|
|
421
|
-
def find_nearest_index(self, source_path: Path) -> Optional[DirMapping]:
|
|
433
|
+
def find_nearest_index(self, source_path: Path) -> Optional[DirMapping]:
|
|
422
434
|
"""Find nearest indexed ancestor directory.
|
|
423
435
|
|
|
424
436
|
Searches for the closest parent directory that has an index.
|
|
@@ -437,15 +449,15 @@ class RegistryStore:
|
|
|
437
449
|
conn = self._get_connection()
|
|
438
450
|
source_path_resolved = source_path.resolve()
|
|
439
451
|
|
|
440
|
-
# Build list of all parent paths from deepest to shallowest
|
|
441
|
-
paths_to_check = []
|
|
442
|
-
current = source_path_resolved
|
|
443
|
-
while True:
|
|
444
|
-
paths_to_check.append(
|
|
445
|
-
parent = current.parent
|
|
446
|
-
if parent == current: # Reached filesystem root
|
|
447
|
-
break
|
|
448
|
-
current = parent
|
|
452
|
+
# Build list of all parent paths from deepest to shallowest
|
|
453
|
+
paths_to_check = []
|
|
454
|
+
current = source_path_resolved
|
|
455
|
+
while True:
|
|
456
|
+
paths_to_check.append(self._normalize_path_for_comparison(current))
|
|
457
|
+
parent = current.parent
|
|
458
|
+
if parent == current: # Reached filesystem root
|
|
459
|
+
break
|
|
460
|
+
current = parent
|
|
449
461
|
|
|
450
462
|
if not paths_to_check:
|
|
451
463
|
return None
|
|
@@ -462,7 +474,7 @@ class RegistryStore:
|
|
|
462
474
|
row = conn.execute(query, paths_to_check).fetchone()
|
|
463
475
|
return self._row_to_dir_mapping(row) if row else None
|
|
464
476
|
|
|
465
|
-
def find_by_source_path(self, source_path: str) -> Optional[Dict[str, str]]:
|
|
477
|
+
def find_by_source_path(self, source_path: str) -> Optional[Dict[str, str]]:
|
|
466
478
|
"""Find project by source path (exact or nearest match).
|
|
467
479
|
|
|
468
480
|
Searches for a project whose source_root matches or contains
|
|
@@ -473,15 +485,16 @@ class RegistryStore:
|
|
|
473
485
|
|
|
474
486
|
Returns:
|
|
475
487
|
Dict with project info including 'index_root', or None if not found
|
|
476
|
-
"""
|
|
477
|
-
with self._lock:
|
|
478
|
-
conn = self._get_connection()
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
488
|
+
"""
|
|
489
|
+
with self._lock:
|
|
490
|
+
conn = self._get_connection()
|
|
491
|
+
resolved_path = Path(source_path).resolve()
|
|
492
|
+
source_path_resolved = self._normalize_path_for_comparison(resolved_path)
|
|
493
|
+
|
|
494
|
+
# First try exact match on projects table
|
|
495
|
+
row = conn.execute(
|
|
496
|
+
"SELECT * FROM projects WHERE source_root=?", (source_path_resolved,)
|
|
497
|
+
).fetchone()
|
|
485
498
|
|
|
486
499
|
if row:
|
|
487
500
|
return {
|
|
@@ -491,16 +504,16 @@ class RegistryStore:
|
|
|
491
504
|
"status": row["status"] or "active",
|
|
492
505
|
}
|
|
493
506
|
|
|
494
|
-
# Try finding project that contains this path
|
|
495
|
-
# Build list of all parent paths
|
|
496
|
-
paths_to_check = []
|
|
497
|
-
current =
|
|
498
|
-
while True:
|
|
499
|
-
paths_to_check.append(
|
|
500
|
-
parent = current.parent
|
|
501
|
-
if parent == current:
|
|
502
|
-
break
|
|
503
|
-
current = parent
|
|
507
|
+
# Try finding project that contains this path
|
|
508
|
+
# Build list of all parent paths
|
|
509
|
+
paths_to_check = []
|
|
510
|
+
current = resolved_path
|
|
511
|
+
while True:
|
|
512
|
+
paths_to_check.append(self._normalize_path_for_comparison(current))
|
|
513
|
+
parent = current.parent
|
|
514
|
+
if parent == current:
|
|
515
|
+
break
|
|
516
|
+
current = parent
|
|
504
517
|
|
|
505
518
|
if paths_to_check:
|
|
506
519
|
placeholders = ','.join('?' * len(paths_to_check))
|
|
@@ -541,7 +554,7 @@ class RegistryStore:
|
|
|
541
554
|
|
|
542
555
|
return [self._row_to_dir_mapping(row) for row in rows]
|
|
543
556
|
|
|
544
|
-
def get_subdirs(self, source_path: Path) -> List[DirMapping]:
|
|
557
|
+
def get_subdirs(self, source_path: Path) -> List[DirMapping]:
|
|
545
558
|
"""Get direct subdirectory mappings.
|
|
546
559
|
|
|
547
560
|
Args:
|
|
@@ -549,10 +562,10 @@ class RegistryStore:
|
|
|
549
562
|
|
|
550
563
|
Returns:
|
|
551
564
|
List of DirMapping objects for direct children
|
|
552
|
-
"""
|
|
553
|
-
with self._lock:
|
|
554
|
-
conn = self._get_connection()
|
|
555
|
-
source_path_str =
|
|
565
|
+
"""
|
|
566
|
+
with self._lock:
|
|
567
|
+
conn = self._get_connection()
|
|
568
|
+
source_path_str = self._normalize_path_for_comparison(source_path.resolve())
|
|
556
569
|
|
|
557
570
|
# First get the parent's depth
|
|
558
571
|
parent_row = conn.execute(
|
|
@@ -578,16 +591,16 @@ class RegistryStore:
|
|
|
578
591
|
|
|
579
592
|
return [self._row_to_dir_mapping(row) for row in rows]
|
|
580
593
|
|
|
581
|
-
def update_dir_stats(self, source_path: Path, files_count: int) -> None:
|
|
594
|
+
def update_dir_stats(self, source_path: Path, files_count: int) -> None:
|
|
582
595
|
"""Update directory statistics.
|
|
583
596
|
|
|
584
597
|
Args:
|
|
585
598
|
source_path: Source directory path
|
|
586
599
|
files_count: Number of files in directory
|
|
587
|
-
"""
|
|
588
|
-
with self._lock:
|
|
589
|
-
conn = self._get_connection()
|
|
590
|
-
source_path_str =
|
|
600
|
+
"""
|
|
601
|
+
with self._lock:
|
|
602
|
+
conn = self._get_connection()
|
|
603
|
+
source_path_str = self._normalize_path_for_comparison(source_path.resolve())
|
|
591
604
|
|
|
592
605
|
conn.execute(
|
|
593
606
|
"""
|