vcase-auto-context 0.1.0__tar.gz

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.
Files changed (54) hide show
  1. vcase_auto_context-0.1.0/PKG-INFO +82 -0
  2. vcase_auto_context-0.1.0/pyproject.toml +36 -0
  3. vcase_auto_context-0.1.0/readme.md +64 -0
  4. vcase_auto_context-0.1.0/setup.cfg +4 -0
  5. vcase_auto_context-0.1.0/tests/test_api_validator.py +126 -0
  6. vcase_auto_context-0.1.0/tests/test_architecture_validator.py +96 -0
  7. vcase_auto_context-0.1.0/tests/test_ast_parser.py +39 -0
  8. vcase_auto_context-0.1.0/tests/test_detector.py +41 -0
  9. vcase_auto_context-0.1.0/tests/test_engine_python.py +46 -0
  10. vcase_auto_context-0.1.0/tests/test_llm_validator.py +69 -0
  11. vcase_auto_context-0.1.0/tests/test_resolver.py +66 -0
  12. vcase_auto_context-0.1.0/tests/test_workspace_scanner.py +106 -0
  13. vcase_auto_context-0.1.0/vcase/__init__.py +60 -0
  14. vcase_auto_context-0.1.0/vcase/api/__init__.py +1 -0
  15. vcase_auto_context-0.1.0/vcase/api/github_client.py +24 -0
  16. vcase_auto_context-0.1.0/vcase/api/npm_client.py +34 -0
  17. vcase_auto_context-0.1.0/vcase/api/pypi_client.py +36 -0
  18. vcase_auto_context-0.1.0/vcase/api/web_scraper.py +25 -0
  19. vcase_auto_context-0.1.0/vcase/cli.py +198 -0
  20. vcase_auto_context-0.1.0/vcase/main.py +21 -0
  21. vcase_auto_context-0.1.0/vcase/providers/__init__.py +1 -0
  22. vcase_auto_context-0.1.0/vcase/providers/base.py +132 -0
  23. vcase_auto_context-0.1.0/vcase/providers/node_provider.py +167 -0
  24. vcase_auto_context-0.1.0/vcase/providers/python_provider.py +218 -0
  25. vcase_auto_context-0.1.0/vcase/routers/__init__.py +1 -0
  26. vcase_auto_context-0.1.0/vcase/routers/health.py +17 -0
  27. vcase_auto_context-0.1.0/vcase/routers/proxy.py +241 -0
  28. vcase_auto_context-0.1.0/vcase/service/__init__.py +1 -0
  29. vcase_auto_context-0.1.0/vcase/service/api_index_cache.py +280 -0
  30. vcase_auto_context-0.1.0/vcase/service/api_validator.py +159 -0
  31. vcase_auto_context-0.1.0/vcase/service/architecture_validator.py +136 -0
  32. vcase_auto_context-0.1.0/vcase/service/ast_parser.py +234 -0
  33. vcase_auto_context-0.1.0/vcase/service/contract_extractor.py +67 -0
  34. vcase_auto_context-0.1.0/vcase/service/detector.py +133 -0
  35. vcase_auto_context-0.1.0/vcase/service/interaction_graph.py +128 -0
  36. vcase_auto_context-0.1.0/vcase/service/llm_validator.py +282 -0
  37. vcase_auto_context-0.1.0/vcase/service/orchestrator.py +211 -0
  38. vcase_auto_context-0.1.0/vcase/service/resolver.py +64 -0
  39. vcase_auto_context-0.1.0/vcase/service/retriever.py +45 -0
  40. vcase_auto_context-0.1.0/vcase/service/spec_builder.py +129 -0
  41. vcase_auto_context-0.1.0/vcase/service/workspace_scanner.py +101 -0
  42. vcase_auto_context-0.1.0/vcase/validator/__init__.py +1 -0
  43. vcase_auto_context-0.1.0/vcase/validator/adapters/python_ast.py +166 -0
  44. vcase_auto_context-0.1.0/vcase/validator/adapters/ts_ast.py +23 -0
  45. vcase_auto_context-0.1.0/vcase/validator/core/ir.py +37 -0
  46. vcase_auto_context-0.1.0/vcase/validator/core/rules_engine.py +122 -0
  47. vcase_auto_context-0.1.0/vcase/validator/specs/api_contracts.json +12 -0
  48. vcase_auto_context-0.1.0/vcase/validator/specs/architecture_rules.json +23 -0
  49. vcase_auto_context-0.1.0/vcase_auto_context.egg-info/PKG-INFO +82 -0
  50. vcase_auto_context-0.1.0/vcase_auto_context.egg-info/SOURCES.txt +52 -0
  51. vcase_auto_context-0.1.0/vcase_auto_context.egg-info/dependency_links.txt +1 -0
  52. vcase_auto_context-0.1.0/vcase_auto_context.egg-info/entry_points.txt +2 -0
  53. vcase_auto_context-0.1.0/vcase_auto_context.egg-info/requires.txt +7 -0
  54. vcase_auto_context-0.1.0/vcase_auto_context.egg-info/top_level.txt +1 -0
@@ -0,0 +1,82 @@
1
+ Metadata-Version: 2.4
2
+ Name: vcase-auto-context
3
+ Version: 0.1.0
4
+ Summary: Version-Constrained API Specification Engine (VCASE)
5
+ Author-email: Developer <developer@example.com>
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.8
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: fastapi>=0.115.0
12
+ Requires-Dist: uvicorn[standard]>=0.30.6
13
+ Requires-Dist: httpx>=0.27.2
14
+ Requires-Dist: pydantic>=2.9.2
15
+ Requires-Dist: pydantic-settings>=2.5.2
16
+ Requires-Dist: cachetools>=5.5.0
17
+ Requires-Dist: python-dotenv>=1.0.1
18
+
19
+ # Version-Constrained API Specification Engine
20
+
21
+ A deterministic, version-aware knowledge streaming infrastructure for Large Language Models (LLMs).
22
+
23
+ ## Why does this exist?
24
+ When LLMs generate code, they frequently rely on outdated training data or hallucinate API signatures for newer libraries. Traditional Retrieval-Augmented Generation (RAG) is often static and misses critical version-specific nuances or architectural constraints.
25
+
26
+ This engine replaces static RAG with a **five-layer defense system**. It dynamically extracts exact project dependencies, resolves them to specific versions, and streams deterministic API contracts and interaction rules directly into the LLM's system prompt.
27
+
28
+ ## Core Features
29
+ - **Zero-Hallucination API Contracts:** Injects exact function signatures based on the specific version of the library you are using.
30
+ - **Architectural Guardrails:** Enforces complex interaction patterns (e.g., "Temporal workflows cannot be executed directly inside FastAPI route handlers").
31
+ - **Language Agnostic Architecture:** Built on a provider interface allowing expansion beyond Python to Node, Rust, Go, etc.
32
+
33
+ ## Resolution Architecture
34
+
35
+ The engine uses a strict **Tiered Fallback Strategy** to guarantee dependency accuracy before building the specification:
36
+
37
+ 1. **Tier 1: Lock Files (Highest Priority)**
38
+ * Parses deterministic manifests (e.g., `poetry.lock`) to extract exact, pinned versions.
39
+ 2. **Tier 2: Declaration Files**
40
+ * Parses abstract declarations (e.g., `pyproject.toml`, `requirements.txt`).
41
+ * Connects to ecosystem registries (like PyPI) to resolve loose constraints (like `^1.2.0`) to the latest compatible version.
42
+ 3. **Tier 3: Containerized Introspection (Future)**
43
+ * Sandboxed subprocess execution (`pip freeze`, `npm ls`) for deeply nested or hidden dependencies.
44
+
45
+ ## Component Flow
46
+
47
+ 1. **`TechnologyDetector`**: Scans the repository or user prompt against a curated list of ~180 production-grade technologies to identify the stack.
48
+ 2. **`DependencyResolver`**: Orchestrates the tiered fallback logic across all available Ecosystem Providers.
49
+ 3. **`EcosystemProvider`** (e.g., `PythonProvider`): Parses the specific manifest syntax of the language.
50
+ 4. **`PypiClient`**: Makes async, defensive network requests to fetch current metadata and documentation sources.
51
+ 5. **`SpecBuilder`**: Assembles the final, non-negotiable System Prompt block, formatting parameters, return types, and deprecation warnings.
52
+
53
+ ## Usage / Integration
54
+
55
+ Currently, the engine provides internal APIs to assemble the System Prompt constraints.
56
+
57
+ ```python
58
+ # Example Internal Flow (Pseudocode)
59
+ from src.service.resolver import DependencyResolver
60
+ from src.service.spec_builder import SpecBuilder
61
+
62
+ # 1. Resolve exact versions used in the project
63
+ resolver = DependencyResolver(providers=[PythonProvider(...)])
64
+ resolved_deps = await resolver.resolve(Path("./my_project"))
65
+
66
+ # 2. Fetch the metadata/signatures internally
67
+ # ...
68
+
69
+ # 3. Build the strict constraints for the LLM
70
+ builder = SpecBuilder()
71
+ system_instruction = builder.build_system_prompt(api_indexes, patterns)
72
+
73
+ print(system_instruction)
74
+ # Outputs:
75
+ # ## litellm 1.40.0
76
+ # - completion(model: str, messages: list) -> ModelResponse
77
+ # ⚠ PATTERN: fastapi + temporalio
78
+ # Rule [NO_WORKFLOW_IN_HANDLER]: Workflows must run in workers.
79
+ ```
80
+
81
+ ## Contributing
82
+ When adding a new Ecosystem Provider (e.g., NodeProvider), you must adhere to the asynchronous contract defined in src/providers/base.py. All registry interactions must be defensive and non-blocking.
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "vcase-auto-context"
7
+ version = "0.1.0"
8
+ authors = [
9
+ { name = "Developer", email = "developer@example.com" }
10
+ ]
11
+ description = "Version-Constrained API Specification Engine (VCASE)"
12
+ readme = "readme.md"
13
+ requires-python = ">=3.8"
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ ]
19
+ dependencies = [
20
+ "fastapi>=0.115.0",
21
+ "uvicorn[standard]>=0.30.6",
22
+ "httpx>=0.27.2",
23
+ "pydantic>=2.9.2",
24
+ "pydantic-settings>=2.5.2",
25
+ "cachetools>=5.5.0",
26
+ "python-dotenv>=1.0.1",
27
+ ]
28
+
29
+ [project.scripts]
30
+ vcase = "vcase.cli:main"
31
+
32
+ [tool.setuptools]
33
+ packages = ["vcase", "vcase.api", "vcase.providers", "vcase.routers", "vcase.service", "vcase.validator", "vcase.validator.adapters", "vcase.validator.core", "vcase.validator.specs"]
34
+
35
+ [tool.setuptools.package-data]
36
+ vcase = ["validator/specs/*.json"]
@@ -0,0 +1,64 @@
1
+ # Version-Constrained API Specification Engine
2
+
3
+ A deterministic, version-aware knowledge streaming infrastructure for Large Language Models (LLMs).
4
+
5
+ ## Why does this exist?
6
+ When LLMs generate code, they frequently rely on outdated training data or hallucinate API signatures for newer libraries. Traditional Retrieval-Augmented Generation (RAG) is often static and misses critical version-specific nuances or architectural constraints.
7
+
8
+ This engine replaces static RAG with a **five-layer defense system**. It dynamically extracts exact project dependencies, resolves them to specific versions, and streams deterministic API contracts and interaction rules directly into the LLM's system prompt.
9
+
10
+ ## Core Features
11
+ - **Zero-Hallucination API Contracts:** Injects exact function signatures based on the specific version of the library you are using.
12
+ - **Architectural Guardrails:** Enforces complex interaction patterns (e.g., "Temporal workflows cannot be executed directly inside FastAPI route handlers").
13
+ - **Language Agnostic Architecture:** Built on a provider interface allowing expansion beyond Python to Node, Rust, Go, etc.
14
+
15
+ ## Resolution Architecture
16
+
17
+ The engine uses a strict **Tiered Fallback Strategy** to guarantee dependency accuracy before building the specification:
18
+
19
+ 1. **Tier 1: Lock Files (Highest Priority)**
20
+ * Parses deterministic manifests (e.g., `poetry.lock`) to extract exact, pinned versions.
21
+ 2. **Tier 2: Declaration Files**
22
+ * Parses abstract declarations (e.g., `pyproject.toml`, `requirements.txt`).
23
+ * Connects to ecosystem registries (like PyPI) to resolve loose constraints (like `^1.2.0`) to the latest compatible version.
24
+ 3. **Tier 3: Containerized Introspection (Future)**
25
+ * Sandboxed subprocess execution (`pip freeze`, `npm ls`) for deeply nested or hidden dependencies.
26
+
27
+ ## Component Flow
28
+
29
+ 1. **`TechnologyDetector`**: Scans the repository or user prompt against a curated list of ~180 production-grade technologies to identify the stack.
30
+ 2. **`DependencyResolver`**: Orchestrates the tiered fallback logic across all available Ecosystem Providers.
31
+ 3. **`EcosystemProvider`** (e.g., `PythonProvider`): Parses the specific manifest syntax of the language.
32
+ 4. **`PypiClient`**: Makes async, defensive network requests to fetch current metadata and documentation sources.
33
+ 5. **`SpecBuilder`**: Assembles the final, non-negotiable System Prompt block, formatting parameters, return types, and deprecation warnings.
34
+
35
+ ## Usage / Integration
36
+
37
+ Currently, the engine provides internal APIs to assemble the System Prompt constraints.
38
+
39
+ ```python
40
+ # Example Internal Flow (Pseudocode)
41
+ from src.service.resolver import DependencyResolver
42
+ from src.service.spec_builder import SpecBuilder
43
+
44
+ # 1. Resolve exact versions used in the project
45
+ resolver = DependencyResolver(providers=[PythonProvider(...)])
46
+ resolved_deps = await resolver.resolve(Path("./my_project"))
47
+
48
+ # 2. Fetch the metadata/signatures internally
49
+ # ...
50
+
51
+ # 3. Build the strict constraints for the LLM
52
+ builder = SpecBuilder()
53
+ system_instruction = builder.build_system_prompt(api_indexes, patterns)
54
+
55
+ print(system_instruction)
56
+ # Outputs:
57
+ # ## litellm 1.40.0
58
+ # - completion(model: str, messages: list) -> ModelResponse
59
+ # ⚠ PATTERN: fastapi + temporalio
60
+ # Rule [NO_WORKFLOW_IN_HANDLER]: Workflows must run in workers.
61
+ ```
62
+
63
+ ## Contributing
64
+ When adding a new Ecosystem Provider (e.g., NodeProvider), you must adhere to the asynchronous contract defined in src/providers/base.py. All registry interactions must be defensive and non-blocking.
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,126 @@
1
+ from __future__ import annotations
2
+
3
+ import pytest
4
+
5
+ from vcase.service.api_validator import ApiValidator
6
+ from vcase.providers.base import ApiIndex, ApiSignature, ApiParameter
7
+
8
+
9
+ class TestApiValidator:
10
+
11
+ def test_passes_when_all_calls_exist_in_index(self) -> None:
12
+ validator = ApiValidator()
13
+ code = "import litellm\nlitellm.completion(model='gpt-4o', messages=[])"
14
+ api_indexes = [
15
+ ApiIndex(
16
+ package="litellm",
17
+ version="1.40.0",
18
+ signatures=(
19
+ ApiSignature(
20
+ qualified_name="litellm.completion",
21
+ parameters=(
22
+ ApiParameter(name="model", type_hint="str", required=True, description=""),
23
+ ApiParameter(name="messages", type_hint="list", required=True, description="")
24
+ ),
25
+ return_type="ModelResponse",
26
+ available_since="",
27
+ deprecated=False
28
+ ),
29
+ )
30
+ )
31
+ ]
32
+ result = validator.validate(code, api_indexes)
33
+ assert result.passed is True
34
+ assert len(result.violations) == 0
35
+
36
+ def test_flags_call_not_present_in_api_index(self) -> None:
37
+ validator = ApiValidator()
38
+ code = "import litellm\nlitellm.non_existent(model='gpt-5-mini')"
39
+ api_indexes = [
40
+ ApiIndex(
41
+ package="litellm",
42
+ version="1.40.0",
43
+ signatures=()
44
+ )
45
+ ]
46
+ result = validator.validate(code, api_indexes)
47
+ assert result.passed is False
48
+ assert any(v.rule_name == "api-unknown" for v in result.violations)
49
+
50
+ def test_flags_deprecated_api_usage(self) -> None:
51
+ validator = ApiValidator()
52
+ code = "import litellm\nlitellm.completion(model='gpt-4o')"
53
+ api_indexes = [
54
+ ApiIndex(
55
+ package="litellm",
56
+ version="1.40.0",
57
+ signatures=(
58
+ ApiSignature(
59
+ qualified_name="litellm.completion",
60
+ parameters=(ApiParameter(name="model", type_hint="str", required=True, description=""),),
61
+ return_type="ModelResponse",
62
+ available_since="",
63
+ deprecated=True,
64
+ deprecation_note="Use acompletion instead."
65
+ ),
66
+ )
67
+ )
68
+ ]
69
+ result = validator.validate(code, api_indexes)
70
+ assert result.passed is True
71
+ assert any(v.rule_name == "api-deprecated" for v in result.violations)
72
+
73
+ def test_returns_empty_violations_for_empty_code(self) -> None:
74
+ validator = ApiValidator()
75
+ result = validator.validate("", [])
76
+ assert result.passed is True
77
+ assert len(result.violations) == 0
78
+
79
+ def test_passes_with_arbitrary_kwargs_when_signature_has_kwargs(self) -> None:
80
+ validator = ApiValidator()
81
+ code = "import litellm\nlitellm.completion(model='gpt-4o', messages=[], custom_kwarg=123)"
82
+ api_indexes = [
83
+ ApiIndex(
84
+ package="litellm",
85
+ version="1.40.0",
86
+ signatures=(
87
+ ApiSignature(
88
+ qualified_name="litellm.completion",
89
+ parameters=(
90
+ ApiParameter(name="model", type_hint="str", required=True, description=""),
91
+ ApiParameter(name="**kwargs", type_hint="Any", required=False, description=""),
92
+ ),
93
+ return_type="ModelResponse",
94
+ available_since="",
95
+ deprecated=False
96
+ ),
97
+ )
98
+ )
99
+ ]
100
+ result = validator.validate(code, api_indexes)
101
+ assert result.passed is True
102
+ assert len(result.violations) == 0
103
+
104
+ def test_passes_with_markdown_code_fences(self) -> None:
105
+ validator = ApiValidator()
106
+ code = "```python\nimport litellm\nlitellm.completion(model='gpt-4o')\n```"
107
+ api_indexes = [
108
+ ApiIndex(
109
+ package="litellm",
110
+ version="1.40.0",
111
+ signatures=(
112
+ ApiSignature(
113
+ qualified_name="litellm.completion",
114
+ parameters=(
115
+ ApiParameter(name="model", type_hint="str", required=True, description=""),
116
+ ),
117
+ return_type="ModelResponse",
118
+ available_since="",
119
+ deprecated=False
120
+ ),
121
+ )
122
+ )
123
+ ]
124
+ result = validator.validate(code, api_indexes)
125
+ assert result.passed is True
126
+ assert len(result.violations) == 0
@@ -0,0 +1,96 @@
1
+ from __future__ import annotations
2
+
3
+ import pytest
4
+
5
+ from vcase.service.architecture_validator import ArchitectureValidator
6
+ from vcase.providers.base import InteractionConstraint, TechnologyPattern
7
+
8
+
9
+ class TestArchitectureValidator:
10
+
11
+ def test_flags_temporal_workflow_called_inside_fastapi_handler(self) -> None:
12
+ validator = ArchitectureValidator()
13
+ code = """
14
+ import fastapi
15
+ import workflow
16
+
17
+ app = fastapi.FastAPI()
18
+
19
+ @app.post("/workflow")
20
+ async def run():
21
+ await workflow.run()
22
+ """
23
+ constraint = InteractionConstraint(
24
+ rule_name="NO_WORKFLOW_IN_FASTAPI_ROUTE",
25
+ allowed_in=(),
26
+ disallowed_in=("app.post", "app.get"),
27
+ description="Temporal workflows cannot run directly in FastAPI route handlers."
28
+ )
29
+ result = validator.validate(code, [], [constraint])
30
+ assert result.passed is False
31
+ assert any(v.rule_name == "NO_WORKFLOW_IN_FASTAPI_ROUTE" for v in result.violations)
32
+
33
+ def test_flags_langfuse_used_as_sequential_log_instead_of_callback(self) -> None:
34
+ validator = ArchitectureValidator()
35
+ code = """
36
+ import langfuse
37
+ import litellm
38
+
39
+ # Sequential log call
40
+ langfuse.log()
41
+ """
42
+ constraint = InteractionConstraint(
43
+ rule_name="NO_SEQUENTIAL_LANGFUSE_LOG",
44
+ allowed_in=(),
45
+ disallowed_in=("sequential_flow",),
46
+ description="Langfuse integration with LiteLLM should use callbacks."
47
+ )
48
+ result = validator.validate(code, [], [constraint])
49
+ assert result.passed is False
50
+ assert any(v.rule_name == "NO_SEQUENTIAL_LANGFUSE_LOG" for v in result.violations)
51
+
52
+ def test_flags_sqlalchemy_session_instantiated_inside_handler(self) -> None:
53
+ validator = ArchitectureValidator()
54
+ code = """
55
+ import fastapi
56
+ app = fastapi.FastAPI()
57
+
58
+ @app.post("/items")
59
+ def create_item():
60
+ db = Session()
61
+ """
62
+ constraint = InteractionConstraint(
63
+ rule_name="NO_MANUAL_DB_SESSION_IN_HANDLER",
64
+ allowed_in=(),
65
+ disallowed_in=("handler_body",),
66
+ description="Do not manually create DB session inside handlers."
67
+ )
68
+ result = validator.validate(code, [], [constraint])
69
+ assert result.passed is False
70
+ assert any(v.rule_name == "NO_MANUAL_DB_SESSION_IN_HANDLER" for v in result.violations)
71
+
72
+ def test_passes_valid_temporal_client_usage_outside_handler(self) -> None:
73
+ validator = ArchitectureValidator()
74
+ code = """
75
+ import temporalio
76
+ import workflow
77
+
78
+ async def run_worker():
79
+ await workflow.run()
80
+ """
81
+ constraint = InteractionConstraint(
82
+ rule_name="NO_WORKFLOW_IN_FASTAPI_ROUTE",
83
+ allowed_in=(),
84
+ disallowed_in=("app.post", "app.get"),
85
+ description="Temporal workflows cannot run directly in FastAPI route handlers."
86
+ )
87
+ result = validator.validate(code, [], [constraint])
88
+ assert result.passed is True
89
+ assert len(result.violations) == 0
90
+
91
+ def test_passes_when_no_constraints_apply_to_detected_stack(self) -> None:
92
+ validator = ArchitectureValidator()
93
+ code = "print('hello')"
94
+ result = validator.validate(code, [], [])
95
+ assert result.passed is True
96
+ assert len(result.violations) == 0
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ import tempfile
4
+ from pathlib import Path
5
+ import pytest
6
+
7
+ from vcase.service.ast_parser import AstParser
8
+
9
+
10
+ class TestAstParser:
11
+
12
+ def test_parses_vararg_and_kwarg_correctly(self) -> None:
13
+ parser = AstParser()
14
+ code = """
15
+ def test_func(a, b=1, *args, c=2, **kwargs):
16
+ pass
17
+ """
18
+ with tempfile.NamedTemporaryFile(suffix=".py", delete=False, mode="w", encoding="utf-8") as f:
19
+ f.write(code)
20
+ tmp_path = Path(f.name)
21
+
22
+ try:
23
+ api_index = parser.parse_file(package_name="testpkg", package_version="1.0.0", filepath=tmp_path)
24
+ assert api_index.package == "testpkg"
25
+ assert api_index.version == "1.0.0"
26
+ assert len(api_index.signatures) == 1
27
+ sig = api_index.signatures[0]
28
+ assert sig.qualified_name.endswith(".test_func")
29
+
30
+ param_names = [p.name for p in sig.parameters]
31
+ assert "a" in param_names
32
+ assert "*args" in param_names
33
+ assert "c" in param_names
34
+ assert "**kwargs" in param_names
35
+ finally:
36
+ try:
37
+ tmp_path.unlink()
38
+ except Exception:
39
+ pass
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ import pytest
6
+
7
+ from vcase.service.detector import TechnologyDetector
8
+ from vcase.providers.python_provider import PythonProvider
9
+ from vcase.providers.node_provider import NodeProvider
10
+
11
+
12
+ class TestTechnologyDetector:
13
+
14
+ def test_detects_python_ecosystem_from_lock_file(self, fixture_repo_path: Path) -> None:
15
+ (fixture_repo_path / "poetry.lock").write_text("", encoding="utf-8")
16
+ provider = PythonProvider(pypi_client=None, api_index_cache=None)
17
+ detector = TechnologyDetector([provider])
18
+ assert provider in detector.detect_ecosystems(fixture_repo_path)
19
+
20
+ def test_detects_node_ecosystem_from_package_json(self, fixture_repo_path: Path) -> None:
21
+ (fixture_repo_path / "package.json").write_text("{}", encoding="utf-8")
22
+ provider = NodeProvider(npm_client=None)
23
+ detector = TechnologyDetector([provider])
24
+ assert provider in detector.detect_ecosystems(fixture_repo_path)
25
+
26
+ def test_returns_empty_list_when_no_known_ecosystem_found(self, fixture_repo_path: Path) -> None:
27
+ detector = TechnologyDetector([])
28
+ assert detector.detect_ecosystems(fixture_repo_path) == []
29
+
30
+ def test_extracts_technology_names_from_prompt(self) -> None:
31
+ detector = TechnologyDetector([])
32
+ prompt = "Create a FastAPI app that queries celery tasks and uses redis."
33
+ techs = detector.detect_technologies_in_prompt(prompt)
34
+ assert "fastapi" in techs
35
+ assert "celery" in techs
36
+ assert "redis" in techs
37
+
38
+ def test_handles_prompt_with_no_known_technologies(self) -> None:
39
+ detector = TechnologyDetector([])
40
+ prompt = "Hello, write a hello world function."
41
+ assert detector.detect_technologies_in_prompt(prompt) == []
@@ -0,0 +1,46 @@
1
+ import asyncio
2
+ import logging
3
+ import sys
4
+ import os
5
+ from pathlib import Path
6
+ import httpx
7
+
8
+ # Fix the path before importing our internal modules
9
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
10
+
11
+ import pytest
12
+ # 1. Import exactly the names we gave our classes
13
+ from vcase.api.pypi_client import PypiClient
14
+ from vcase.providers.python_provider import PythonProvider
15
+ from vcase.service.resolver import DependencyResolver
16
+
17
+ @pytest.mark.asyncio
18
+ async def test_engine_python():
19
+ logging.basicConfig(level=logging.INFO)
20
+
21
+ # 2. Assign the client to a variable!
22
+ # (If you don't, you can't close it later!)
23
+ http_client=httpx.AsyncClient()
24
+ api_client = PypiClient(client=http_client)
25
+
26
+ # 3. Pass the client variable into the provider
27
+ provider = PythonProvider(api_client)
28
+
29
+ # 4. Our class was named DependencyResolver, and it takes providers in its initialization
30
+ resolver = DependencyResolver(providers=[provider])
31
+
32
+ repo_path = Path(__file__).parent
33
+
34
+ # 5. Call the actual method we built: "resolve"
35
+ print("Executing resolution pipeline...")
36
+ resolved_deps = await resolver.resolve(repo_path)
37
+
38
+ # 6. We need to print them to prove it worked!
39
+ for dep in resolved_deps:
40
+ print(f"{dep.name}: {dep.version} ({dep.resolution_tier.name})")
41
+
42
+ # 7. Now we safely close the variable we created in step 2
43
+ await http_client.aclose()
44
+
45
+ if __name__ == "__main__":
46
+ asyncio.run(test_engine_python())
@@ -0,0 +1,69 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from unittest.mock import AsyncMock, patch
5
+ import pytest
6
+ import httpx
7
+
8
+ from vcase.service.llm_validator import LlmValidator
9
+ from vcase.providers.base import ApiIndex, ApiSignature, ApiParameter
10
+
11
+
12
+ class TestLlmValidatorOpenRouter:
13
+
14
+ @pytest.mark.asyncio
15
+ @patch.dict(os.environ, {
16
+ "OPENROUTER_API_KEY": "sk-or-v1-test-key",
17
+ "OPENAI_API_KEY": "",
18
+ "GEMINI_API_KEY": ""
19
+ })
20
+ async def test_validate_calls_openrouter_correctly(self) -> None:
21
+ validator = LlmValidator("moonshotai/kimi-k2.6")
22
+
23
+ # Mocking the response of httpx client post call
24
+ mock_response = httpx.Response(
25
+ status_code=200,
26
+ json={"choices": [{"message": {"content": "PASS"}}]}
27
+ )
28
+
29
+ # We patch httpx.AsyncClient.post
30
+ with patch("httpx.AsyncClient.post", new_callable=AsyncMock) as mock_post:
31
+ mock_post.return_value = mock_response
32
+
33
+ api_indexes = [
34
+ ApiIndex(
35
+ package="litellm",
36
+ version="1.40.0",
37
+ signatures=(
38
+ ApiSignature(
39
+ qualified_name="litellm.completion",
40
+ parameters=(
41
+ ApiParameter(name="model", type_hint="str", required=True, description=""),
42
+ ),
43
+ return_type="ModelResponse",
44
+ available_since="",
45
+ deprecated=False
46
+ ),
47
+ )
48
+ )
49
+ ]
50
+
51
+ result = await validator.validate(
52
+ generated_code="import litellm\nlitellm.completion(model='gpt-4o')",
53
+ api_indexes=api_indexes,
54
+ violation_context="[api-invalid-kwargs] test violation"
55
+ )
56
+
57
+ assert result.passed is True
58
+ assert len(result.violations) == 0
59
+
60
+ # Verify the call payload
61
+ mock_post.assert_called_once()
62
+ called_url = mock_post.call_args[0][0]
63
+ called_kwargs = mock_post.call_args[1]
64
+
65
+ assert called_url == "https://openrouter.ai/api/v1/chat/completions"
66
+ assert called_kwargs["headers"]["Authorization"] == "Bearer sk-or-v1-test-key"
67
+ assert called_kwargs["headers"]["HTTP-Referer"] == "https://github.com/smuggypusss/Version-Constrained-API-Specification-Engine"
68
+ assert called_kwargs["json"]["model"] == "moonshotai/kimi-k2.6"
69
+ assert "You are a strict code validator" in called_kwargs["json"]["messages"][0]["content"]
@@ -0,0 +1,66 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ import pytest
5
+
6
+ from vcase.service.resolver import DependencyResolver
7
+ from vcase.providers.python_provider import PythonProvider
8
+ from vcase.providers.base import ResolutionTier, Ecosystem
9
+
10
+
11
+ class MockPypiClient:
12
+ async def get_latest_version(self, package: str) -> str:
13
+ if package == "langfuse":
14
+ return "2.5.1"
15
+ return "1.0.0"
16
+
17
+
18
+ class TestDependencyResolver:
19
+
20
+ @pytest.mark.asyncio
21
+ async def test_resolves_from_lock_file_as_tier_one(
22
+ self, fixture_repo_path: Path, sample_poetry_lock_content: str
23
+ ) -> None:
24
+ (fixture_repo_path / "poetry.lock").write_text(sample_poetry_lock_content, encoding="utf-8")
25
+ client = MockPypiClient()
26
+ provider = PythonProvider(pypi_client=client, api_index_cache=None)
27
+ resolver = DependencyResolver(providers=[provider])
28
+ resolved = await resolver.resolve(fixture_repo_path)
29
+ assert len(resolved) == 3
30
+ litellm = next(d for d in resolved if d.name == "litellm")
31
+ assert litellm.version == "1.40.0"
32
+ assert litellm.resolution_tier == ResolutionTier.LOCK_FILE
33
+
34
+ @pytest.mark.asyncio
35
+ async def test_falls_back_to_declaration_when_lock_file_missing(
36
+ self, fixture_repo_path: Path, sample_requirements_txt_content: str
37
+ ) -> None:
38
+ (fixture_repo_path / "requirements.txt").write_text(sample_requirements_txt_content, encoding="utf-8")
39
+ client = MockPypiClient()
40
+ provider = PythonProvider(pypi_client=client, api_index_cache=None)
41
+ resolver = DependencyResolver(providers=[provider])
42
+ resolved = await resolver.resolve(fixture_repo_path)
43
+ assert len(resolved) == 3
44
+ langfuse = next(d for d in resolved if d.name == "langfuse")
45
+ assert langfuse.version == "2.5.1"
46
+ assert langfuse.resolution_tier == ResolutionTier.DECLARATION
47
+
48
+ @pytest.mark.asyncio
49
+ async def test_logs_warning_when_no_manifest_found(self, fixture_repo_path: Path) -> None:
50
+ provider = PythonProvider(pypi_client=None, api_index_cache=None)
51
+ resolver = DependencyResolver(providers=[provider])
52
+ resolved = await resolver.resolve(fixture_repo_path)
53
+ assert resolved == []
54
+
55
+ @pytest.mark.asyncio
56
+ async def test_normalized_output_contains_resolution_tier(
57
+ self, fixture_repo_path: Path, sample_poetry_lock_content: str
58
+ ) -> None:
59
+ (fixture_repo_path / "poetry.lock").write_text(sample_poetry_lock_content, encoding="utf-8")
60
+ client = MockPypiClient()
61
+ provider = PythonProvider(pypi_client=client, api_index_cache=None)
62
+ resolver = DependencyResolver(providers=[provider])
63
+ resolved = await resolver.resolve(fixture_repo_path)
64
+ for dep in resolved:
65
+ assert dep.resolution_tier in (ResolutionTier.LOCK_FILE, ResolutionTier.DECLARATION)
66
+ assert dep.ecosystem == Ecosystem.PYTHON