mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
feat: Add templates for epics, product brief, and requirements PRD
- Introduced a comprehensive template for generating epics and stories, including an index and individual epic files. - Created a product brief template to outline product vision, problem statements, and target users. - Developed a requirements PRD template to structure functional and non-functional requirements, including traceability and prioritization. - Implemented ast-grep processors for JavaScript and TypeScript to extract relationships such as imports and inheritance. - Added corresponding patterns for JavaScript and TypeScript to support relationship extraction. - Established comparison tests to validate the accuracy of relationship extraction between tree-sitter and ast-grep methods.
This commit is contained in:
140
codex-lens/tests/parsers/test_comparison_js_ts.py
Normal file
140
codex-lens/tests/parsers/test_comparison_js_ts.py
Normal file
@@ -0,0 +1,140 @@
|
||||
"""Comparison tests for tree-sitter vs ast-grep JS/TS relationship extraction.
|
||||
|
||||
These tests focus on stable, high-signal relationship types used by the
|
||||
static graph pipeline:
|
||||
- IMPORTS
|
||||
- INHERITS
|
||||
|
||||
If ast-grep-py is not installed, tests are skipped.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import List, Set, Tuple
|
||||
|
||||
import pytest
|
||||
|
||||
from codexlens.config import Config
|
||||
from codexlens.entities import CodeRelationship, RelationshipType
|
||||
from codexlens.parsers.treesitter_parser import TreeSitterSymbolParser
|
||||
|
||||
|
||||
SAMPLE_JS_CODE = """
|
||||
import React, { useEffect as useEf } from "react";
|
||||
import { foo } from "./foo";
|
||||
import "./styles.css";
|
||||
const fs = require("fs");
|
||||
|
||||
class Base {}
|
||||
class Child extends Base {
|
||||
method() {
|
||||
console.log("hi");
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
SAMPLE_TS_CODE = """
|
||||
import type { Foo } from "pkg";
|
||||
import { bar as baz } from "./bar";
|
||||
|
||||
interface MyInterface extends Foo {}
|
||||
|
||||
class Base {}
|
||||
class Child extends Base {}
|
||||
"""
|
||||
|
||||
|
||||
def extract_relationship_tuples(
|
||||
relationships: List[CodeRelationship],
|
||||
*,
|
||||
only_types: Set[RelationshipType],
|
||||
) -> Set[Tuple[str, str, str]]:
|
||||
return {
|
||||
(rel.source_symbol, rel.target_symbol, rel.relationship_type.value)
|
||||
for rel in relationships
|
||||
if rel.relationship_type in only_types
|
||||
}
|
||||
|
||||
|
||||
def _skip_if_astgrep_unavailable(parser: TreeSitterSymbolParser) -> None:
|
||||
if parser._astgrep_processor is None or not parser._astgrep_processor.is_available(): # type: ignore[attr-defined]
|
||||
pytest.skip("ast-grep-py not installed or language not supported")
|
||||
|
||||
|
||||
def test_js_imports_and_inherits_match(tmp_path: Path) -> None:
|
||||
js_file = tmp_path / "sample.js"
|
||||
js_file.write_text(SAMPLE_JS_CODE, encoding="utf-8")
|
||||
source = js_file.read_text(encoding="utf-8")
|
||||
|
||||
config_default = Config()
|
||||
config_default.use_astgrep = False
|
||||
ts_default = TreeSitterSymbolParser("javascript", js_file, config=config_default)
|
||||
|
||||
config_ast = Config()
|
||||
config_ast.use_astgrep = True
|
||||
ts_ast = TreeSitterSymbolParser("javascript", js_file, config=config_ast)
|
||||
_skip_if_astgrep_unavailable(ts_ast)
|
||||
|
||||
result_ts = ts_default.parse(source, js_file)
|
||||
result_ast = ts_ast.parse(source, js_file)
|
||||
|
||||
assert result_ts is not None
|
||||
assert result_ast is not None
|
||||
|
||||
ts_rel = extract_relationship_tuples(
|
||||
result_ts.relationships,
|
||||
only_types={RelationshipType.IMPORTS, RelationshipType.INHERITS},
|
||||
)
|
||||
ast_rel = extract_relationship_tuples(
|
||||
result_ast.relationships,
|
||||
only_types={RelationshipType.IMPORTS, RelationshipType.INHERITS},
|
||||
)
|
||||
|
||||
assert ast_rel == ts_rel
|
||||
|
||||
|
||||
def test_ts_imports_match_and_inherits_superset(tmp_path: Path) -> None:
|
||||
ts_file = tmp_path / "sample.ts"
|
||||
ts_file.write_text(SAMPLE_TS_CODE, encoding="utf-8")
|
||||
source = ts_file.read_text(encoding="utf-8")
|
||||
|
||||
config_default = Config()
|
||||
config_default.use_astgrep = False
|
||||
ts_default = TreeSitterSymbolParser("typescript", ts_file, config=config_default)
|
||||
|
||||
config_ast = Config()
|
||||
config_ast.use_astgrep = True
|
||||
ts_ast = TreeSitterSymbolParser("typescript", ts_file, config=config_ast)
|
||||
_skip_if_astgrep_unavailable(ts_ast)
|
||||
|
||||
result_ts = ts_default.parse(source, ts_file)
|
||||
result_ast = ts_ast.parse(source, ts_file)
|
||||
|
||||
assert result_ts is not None
|
||||
assert result_ast is not None
|
||||
|
||||
ts_imports = extract_relationship_tuples(
|
||||
result_ts.relationships,
|
||||
only_types={RelationshipType.IMPORTS},
|
||||
)
|
||||
ast_imports = extract_relationship_tuples(
|
||||
result_ast.relationships,
|
||||
only_types={RelationshipType.IMPORTS},
|
||||
)
|
||||
assert ast_imports == ts_imports
|
||||
|
||||
ts_inherits = extract_relationship_tuples(
|
||||
result_ts.relationships,
|
||||
only_types={RelationshipType.INHERITS},
|
||||
)
|
||||
ast_inherits = extract_relationship_tuples(
|
||||
result_ast.relationships,
|
||||
only_types={RelationshipType.INHERITS},
|
||||
)
|
||||
# Ast-grep may include additional TypeScript inheritance edges (e.g., interface extends).
|
||||
assert ts_inherits.issubset(ast_inherits)
|
||||
# But at minimum, class inheritance should be present.
|
||||
assert ("Child", "Base", "inherits") in ast_inherits
|
||||
|
||||
@@ -84,6 +84,21 @@ class TestConfigCascadeDefaults:
|
||||
# Should keep the default "binary" strategy
|
||||
assert config.cascade_strategy == "binary"
|
||||
|
||||
def test_hybrid_cascade_strategy_alias_maps_to_binary_rerank(self, temp_config_dir):
|
||||
"""Hybrid is a backward-compat alias for binary_rerank."""
|
||||
config = Config(data_dir=temp_config_dir)
|
||||
settings = {"cascade": {"strategy": "hybrid"}}
|
||||
|
||||
settings_path = config.settings_path
|
||||
settings_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(settings_path, "w", encoding="utf-8") as f:
|
||||
json.dump(settings, f)
|
||||
|
||||
with patch.object(config, "_apply_env_overrides"):
|
||||
config.load_settings()
|
||||
|
||||
assert config.cascade_strategy == "binary_rerank"
|
||||
|
||||
def test_staged_config_defaults(self, temp_config_dir):
|
||||
"""Staged cascade settings should have correct defaults."""
|
||||
config = Config(data_dir=temp_config_dir)
|
||||
|
||||
@@ -115,3 +115,22 @@ def test_staged_env_overrides_invalid_ignored(temp_config_dir: Path) -> None:
|
||||
assert config.staged_stage2_mode == "precomputed"
|
||||
assert config.staged_clustering_strategy == "auto"
|
||||
assert config.staged_realtime_lsp_timeout_s == 30.0
|
||||
|
||||
|
||||
def test_cascade_strategy_hybrid_alias_env_override(temp_config_dir: Path) -> None:
|
||||
config = Config(data_dir=temp_config_dir)
|
||||
|
||||
env_path = temp_config_dir / ".env"
|
||||
env_path.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
"CASCADE_STRATEGY=hybrid",
|
||||
"",
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
config.load_settings()
|
||||
|
||||
assert config.cascade_strategy == "binary_rerank"
|
||||
|
||||
Reference in New Issue
Block a user