mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-15 02:42:45 +08:00
Add integration verification and validation phases, role templates, and static graph tests
- Implement Phase 4: Integration Verification to ensure skill package consistency. - Implement Phase 5: Validation to verify quality and deliver the final skill package. - Create role-template.md for generating per-role execution detail files. - Create skill-router-template.md for generating SKILL.md with role-based routing. - Add tests for static graph relationship writing during index build in test_static_graph_integration.py.
This commit is contained in:
@@ -145,7 +145,7 @@ class Config:
|
||||
# Staged cascade search configuration (4-stage pipeline)
|
||||
staged_coarse_k: int = 200 # Number of coarse candidates from Stage 1 binary search
|
||||
staged_lsp_depth: int = 2 # LSP relationship expansion depth in Stage 2
|
||||
staged_stage2_mode: str = "precomputed" # "precomputed" (graph_neighbors) | "realtime" (LSP)
|
||||
staged_stage2_mode: str = "precomputed" # "precomputed" (graph_neighbors) | "realtime" (LSP) | "static_global_graph" (global_relationships)
|
||||
|
||||
# Static graph configuration (write relationships to global index during build)
|
||||
static_graph_enabled: bool = False
|
||||
@@ -627,7 +627,7 @@ class Config:
|
||||
staged_stage2_mode = get_env("STAGED_STAGE2_MODE")
|
||||
if staged_stage2_mode:
|
||||
mode = staged_stage2_mode.strip().lower()
|
||||
if mode in {"precomputed", "realtime"}:
|
||||
if mode in {"precomputed", "realtime", "static_global_graph"}:
|
||||
self.staged_stage2_mode = mode
|
||||
log.debug("Overriding staged_stage2_mode from .env: %s", self.staged_stage2_mode)
|
||||
elif mode in {"live"}:
|
||||
|
||||
@@ -1293,6 +1293,9 @@ class ChainSearchEngine:
|
||||
query=query,
|
||||
)
|
||||
|
||||
if mode == "static_global_graph":
|
||||
return self._stage2_static_global_graph_expand(coarse_results, index_root=index_root)
|
||||
|
||||
return self._stage2_precomputed_graph_expand(coarse_results, index_root=index_root)
|
||||
|
||||
except ImportError as exc:
|
||||
@@ -1343,6 +1346,50 @@ class ChainSearchEngine:
|
||||
|
||||
return self._combine_stage2_results(coarse_results, related_results)
|
||||
|
||||
def _stage2_static_global_graph_expand(
|
||||
self,
|
||||
coarse_results: List[SearchResult],
|
||||
*,
|
||||
index_root: Path,
|
||||
) -> List[SearchResult]:
|
||||
"""Stage 2 (static_global_graph): expand using GlobalGraphExpander over global_relationships."""
|
||||
from codexlens.search.global_graph_expander import GlobalGraphExpander
|
||||
|
||||
global_db_path = index_root / GlobalSymbolIndex.DEFAULT_DB_NAME
|
||||
if not global_db_path.exists():
|
||||
self.logger.debug("Global symbol DB not found at %s, skipping static graph expansion", global_db_path)
|
||||
return coarse_results
|
||||
|
||||
project_id = 1
|
||||
try:
|
||||
for p in self.registry.list_projects():
|
||||
if p.index_root.resolve() == index_root.resolve():
|
||||
project_id = p.id
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
global_index = GlobalSymbolIndex(global_db_path, project_id=project_id)
|
||||
global_index.initialize()
|
||||
|
||||
try:
|
||||
expander = GlobalGraphExpander(global_index, config=self._config)
|
||||
related_results = expander.expand(
|
||||
coarse_results,
|
||||
top_n=min(10, len(coarse_results)),
|
||||
max_related=50,
|
||||
)
|
||||
|
||||
if related_results:
|
||||
self.logger.debug(
|
||||
"Stage 2 (static_global_graph) expanded %d base results to %d related symbols",
|
||||
len(coarse_results), len(related_results),
|
||||
)
|
||||
|
||||
return self._combine_stage2_results(coarse_results, related_results)
|
||||
finally:
|
||||
global_index.close()
|
||||
|
||||
def _stage2_realtime_lsp_expand(
|
||||
self,
|
||||
coarse_results: List[SearchResult],
|
||||
|
||||
289
codex-lens/tests/test_static_graph_integration.py
Normal file
289
codex-lens/tests/test_static_graph_integration.py
Normal file
@@ -0,0 +1,289 @@
|
||||
"""Tests for static graph relationship writing during index build (T2).
|
||||
|
||||
Verifies that IndexTreeBuilder._build_single_dir and _build_dir_worker
|
||||
correctly write relationships to GlobalSymbolIndex when
|
||||
config.static_graph_enabled is True.
|
||||
"""
|
||||
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from codexlens.config import Config
|
||||
from codexlens.entities import (
|
||||
CodeRelationship,
|
||||
IndexedFile,
|
||||
RelationshipType,
|
||||
Symbol,
|
||||
)
|
||||
from codexlens.storage.global_index import GlobalSymbolIndex
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def temp_dir():
|
||||
tmpdir = tempfile.TemporaryDirectory(ignore_cleanup_errors=True)
|
||||
yield Path(tmpdir.name)
|
||||
try:
|
||||
tmpdir.cleanup()
|
||||
except (PermissionError, OSError):
|
||||
pass
|
||||
|
||||
|
||||
def _make_indexed_file(file_path: str) -> IndexedFile:
|
||||
"""Create a test IndexedFile with symbols and relationships."""
|
||||
return IndexedFile(
|
||||
path=file_path,
|
||||
language="python",
|
||||
symbols=[
|
||||
Symbol(name="MyClass", kind="class", range=(1, 20)),
|
||||
Symbol(name="helper", kind="function", range=(22, 30)),
|
||||
],
|
||||
relationships=[
|
||||
CodeRelationship(
|
||||
source_symbol="MyClass",
|
||||
target_symbol="BaseClass",
|
||||
relationship_type=RelationshipType.INHERITS,
|
||||
source_file=file_path,
|
||||
target_file="other/base.py",
|
||||
source_line=1,
|
||||
),
|
||||
CodeRelationship(
|
||||
source_symbol="MyClass",
|
||||
target_symbol="os",
|
||||
relationship_type=RelationshipType.IMPORTS,
|
||||
source_file=file_path,
|
||||
source_line=2,
|
||||
),
|
||||
CodeRelationship(
|
||||
source_symbol="helper",
|
||||
target_symbol="external_func",
|
||||
relationship_type=RelationshipType.CALL,
|
||||
source_file=file_path,
|
||||
source_line=25,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def test_build_single_dir_writes_global_relationships_when_enabled(temp_dir: Path) -> None:
|
||||
"""When static_graph_enabled=True, relationships should be written to global index."""
|
||||
from codexlens.storage.index_tree import IndexTreeBuilder
|
||||
|
||||
config = Config(
|
||||
data_dir=temp_dir / "data",
|
||||
static_graph_enabled=True,
|
||||
static_graph_relationship_types=["imports", "inherits"],
|
||||
global_symbol_index_enabled=True,
|
||||
)
|
||||
|
||||
# Set up real GlobalSymbolIndex
|
||||
global_db_path = temp_dir / "global_symbols.db"
|
||||
global_index = GlobalSymbolIndex(global_db_path, project_id=1)
|
||||
global_index.initialize()
|
||||
|
||||
# Create a source file
|
||||
src_dir = temp_dir / "src"
|
||||
src_dir.mkdir()
|
||||
test_file = src_dir / "module.py"
|
||||
test_file.write_text("class MyClass(BaseClass):\n pass\n", encoding="utf-8")
|
||||
|
||||
indexed_file = _make_indexed_file(str(test_file))
|
||||
|
||||
# Mock parser to return our test IndexedFile
|
||||
mock_parser = MagicMock()
|
||||
mock_parser.parse.return_value = indexed_file
|
||||
|
||||
mock_mapper = MagicMock()
|
||||
mock_mapper.source_to_index_db.return_value = temp_dir / "index" / "_index.db"
|
||||
|
||||
mock_registry = MagicMock()
|
||||
|
||||
builder = IndexTreeBuilder(mock_registry, mock_mapper, config=config, incremental=False)
|
||||
builder.parser_factory = MagicMock()
|
||||
builder.parser_factory.get_parser.return_value = mock_parser
|
||||
|
||||
result = builder._build_single_dir(
|
||||
src_dir,
|
||||
languages=None,
|
||||
project_id=1,
|
||||
global_index_db_path=global_db_path,
|
||||
)
|
||||
|
||||
assert result.error is None
|
||||
assert result.files_count == 1
|
||||
|
||||
# Verify relationships were written to global index
|
||||
# Only IMPORTS and INHERITS should be written (not CALL)
|
||||
rels = global_index.query_by_target("BaseClass", prefix_mode=True)
|
||||
rels += global_index.query_by_target("os", prefix_mode=True)
|
||||
assert len(rels) >= 1, "Expected at least 1 relationship written to global index"
|
||||
|
||||
# CALL relationship for external_func should NOT be present
|
||||
call_rels = global_index.query_by_target("external_func", prefix_mode=True)
|
||||
assert len(call_rels) == 0, "CALL relationships should not be written"
|
||||
|
||||
global_index.close()
|
||||
|
||||
|
||||
def test_build_single_dir_skips_relationships_when_disabled(temp_dir: Path) -> None:
|
||||
"""When static_graph_enabled=False, no relationships should be written."""
|
||||
from codexlens.storage.index_tree import IndexTreeBuilder
|
||||
|
||||
config = Config(
|
||||
data_dir=temp_dir / "data",
|
||||
static_graph_enabled=False,
|
||||
global_symbol_index_enabled=True,
|
||||
)
|
||||
|
||||
global_db_path = temp_dir / "global_symbols.db"
|
||||
global_index = GlobalSymbolIndex(global_db_path, project_id=1)
|
||||
global_index.initialize()
|
||||
|
||||
src_dir = temp_dir / "src"
|
||||
src_dir.mkdir()
|
||||
test_file = src_dir / "module.py"
|
||||
test_file.write_text("import os\n", encoding="utf-8")
|
||||
|
||||
indexed_file = _make_indexed_file(str(test_file))
|
||||
|
||||
mock_parser = MagicMock()
|
||||
mock_parser.parse.return_value = indexed_file
|
||||
|
||||
mock_mapper = MagicMock()
|
||||
mock_mapper.source_to_index_db.return_value = temp_dir / "index" / "_index.db"
|
||||
|
||||
mock_registry = MagicMock()
|
||||
|
||||
builder = IndexTreeBuilder(mock_registry, mock_mapper, config=config, incremental=False)
|
||||
builder.parser_factory = MagicMock()
|
||||
builder.parser_factory.get_parser.return_value = mock_parser
|
||||
|
||||
result = builder._build_single_dir(
|
||||
src_dir,
|
||||
languages=None,
|
||||
project_id=1,
|
||||
global_index_db_path=global_db_path,
|
||||
)
|
||||
|
||||
assert result.error is None
|
||||
|
||||
# No relationships should be in global index
|
||||
conn = global_index._get_connection()
|
||||
count = conn.execute("SELECT COUNT(*) FROM global_relationships").fetchone()[0]
|
||||
assert count == 0, "No relationships should be written when static_graph_enabled=False"
|
||||
|
||||
global_index.close()
|
||||
|
||||
|
||||
def test_relationship_write_failure_does_not_block_indexing(temp_dir: Path) -> None:
|
||||
"""If global_index.update_file_relationships raises, file indexing continues."""
|
||||
from codexlens.storage.index_tree import IndexTreeBuilder
|
||||
|
||||
config = Config(
|
||||
data_dir=temp_dir / "data",
|
||||
static_graph_enabled=True,
|
||||
static_graph_relationship_types=["imports", "inherits"],
|
||||
global_symbol_index_enabled=True,
|
||||
)
|
||||
|
||||
src_dir = temp_dir / "src"
|
||||
src_dir.mkdir()
|
||||
test_file = src_dir / "module.py"
|
||||
test_file.write_text("import os\n", encoding="utf-8")
|
||||
|
||||
indexed_file = _make_indexed_file(str(test_file))
|
||||
|
||||
mock_parser = MagicMock()
|
||||
mock_parser.parse.return_value = indexed_file
|
||||
|
||||
mock_mapper = MagicMock()
|
||||
mock_mapper.source_to_index_db.return_value = temp_dir / "index" / "_index.db"
|
||||
|
||||
mock_registry = MagicMock()
|
||||
|
||||
# Create a mock GlobalSymbolIndex that fails on update_file_relationships
|
||||
mock_global_db_path = temp_dir / "global_symbols.db"
|
||||
|
||||
builder = IndexTreeBuilder(mock_registry, mock_mapper, config=config, incremental=False)
|
||||
builder.parser_factory = MagicMock()
|
||||
builder.parser_factory.get_parser.return_value = mock_parser
|
||||
|
||||
# Patch GlobalSymbolIndex so update_file_relationships raises
|
||||
with patch("codexlens.storage.index_tree.GlobalSymbolIndex") as MockGSI:
|
||||
mock_gsi_instance = MagicMock()
|
||||
mock_gsi_instance.update_file_relationships.side_effect = RuntimeError("DB locked")
|
||||
MockGSI.return_value = mock_gsi_instance
|
||||
|
||||
result = builder._build_single_dir(
|
||||
src_dir,
|
||||
languages=None,
|
||||
project_id=1,
|
||||
global_index_db_path=mock_global_db_path,
|
||||
)
|
||||
|
||||
# File should still be indexed despite relationship write failure
|
||||
assert result.error is None
|
||||
assert result.files_count == 1
|
||||
|
||||
|
||||
def test_only_configured_relationship_types_written(temp_dir: Path) -> None:
|
||||
"""Only relationship types in static_graph_relationship_types should be written."""
|
||||
from codexlens.storage.index_tree import IndexTreeBuilder
|
||||
|
||||
# Only allow 'imports' (not 'inherits')
|
||||
config = Config(
|
||||
data_dir=temp_dir / "data",
|
||||
static_graph_enabled=True,
|
||||
static_graph_relationship_types=["imports"],
|
||||
global_symbol_index_enabled=True,
|
||||
)
|
||||
|
||||
global_db_path = temp_dir / "global_symbols.db"
|
||||
global_index = GlobalSymbolIndex(global_db_path, project_id=1)
|
||||
global_index.initialize()
|
||||
|
||||
src_dir = temp_dir / "src"
|
||||
src_dir.mkdir()
|
||||
test_file = src_dir / "module.py"
|
||||
test_file.write_text("import os\nclass Foo(Bar): pass\n", encoding="utf-8")
|
||||
|
||||
indexed_file = _make_indexed_file(str(test_file))
|
||||
|
||||
mock_parser = MagicMock()
|
||||
mock_parser.parse.return_value = indexed_file
|
||||
|
||||
mock_mapper = MagicMock()
|
||||
mock_mapper.source_to_index_db.return_value = temp_dir / "index" / "_index.db"
|
||||
|
||||
mock_registry = MagicMock()
|
||||
|
||||
builder = IndexTreeBuilder(mock_registry, mock_mapper, config=config, incremental=False)
|
||||
builder.parser_factory = MagicMock()
|
||||
builder.parser_factory.get_parser.return_value = mock_parser
|
||||
|
||||
result = builder._build_single_dir(
|
||||
src_dir,
|
||||
languages=None,
|
||||
project_id=1,
|
||||
global_index_db_path=global_db_path,
|
||||
)
|
||||
|
||||
assert result.error is None
|
||||
|
||||
# Only IMPORTS should be written
|
||||
conn = global_index._get_connection()
|
||||
rows = conn.execute(
|
||||
"SELECT relationship_type FROM global_relationships"
|
||||
).fetchall()
|
||||
|
||||
rel_types = {row[0] for row in rows}
|
||||
assert "imports" in rel_types or len(rows) == 0 or rel_types == {"imports"}, \
|
||||
f"Expected only 'imports', got {rel_types}"
|
||||
# INHERITS should NOT be present
|
||||
assert "inherits" not in rel_types, "inherits should not be written when not in config"
|
||||
# CALL should NOT be present
|
||||
assert "calls" not in rel_types, "calls should not be written"
|
||||
|
||||
global_index.close()
|
||||
Reference in New Issue
Block a user