diff --git a/modules/hook-context-intelligence/pyproject.toml b/modules/hook-context-intelligence/pyproject.toml index 375e53b..d8954ad 100644 --- a/modules/hook-context-intelligence/pyproject.toml +++ b/modules/hook-context-intelligence/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "amplifier-module-hook-context-intelligence" -version = "0.1.1" +version = "0.1.2" description = "Context intelligence hook — event-driven property graph builder for Amplifier sessions" requires-python = ">=3.11" license = "MIT" @@ -8,7 +8,7 @@ license = "MIT" dependencies = [ "httpx>=0.28.1", "idna>=3.15", - "amplifier-bundle-context-intelligence", + "amplifier-bundle-context-intelligence @ git+https://github.com/microsoft/amplifier-bundle-context-intelligence@v0.1.1", ] [project.entry-points."amplifier.modules"] @@ -24,6 +24,10 @@ package = true [tool.hatch.build.targets.wheel] packages = ["amplifier_module_hook_context_intelligence"] +[tool.hatch.metadata] +# Required to build a wheel that carries a PEP 508 direct-reference (git+https) dependency. +allow-direct-references = true + [dependency-groups] dev = [ "amplifier-core>=1.4.1", @@ -36,7 +40,10 @@ dev = [ [tool.uv.sources] amplifier-core = { git = "https://github.com/microsoft/amplifier-core", rev = "v1.4.1" } -amplifier-bundle-context-intelligence = { path = "../.." } +# Note: the bundle dependency is declared as a PEP 508 direct git reference in +# [project.dependencies] above (survives `uv pip install --no-sources`). It is +# intentionally NOT given a `path = "../.."` source here, so the module installs +# identically inside the monorepo and standalone. [tool.pytest.ini_options] asyncio_mode = "auto" diff --git a/modules/hook-context-intelligence/tests/test_hook_dependencies.py b/modules/hook-context-intelligence/tests/test_hook_dependencies.py index 3a94569..56bb2fb 100644 --- a/modules/hook-context-intelligence/tests/test_hook_dependencies.py +++ b/modules/hook-context-intelligence/tests/test_hook_dependencies.py @@ -1,6 +1,13 @@ -"""Test that the hook module pyproject.toml declares the context_intelligence dependency. +"""Test that the hook module pyproject.toml declares the bundle dependency in a +form that installs standalone (outside the monorepo). -TDD: This test is written FIRST and will FAIL until pyproject.toml is updated. +The hook imports from the `context_intelligence` package shipped by the parent +bundle. For the hook to install standalone under the Amplifier agent's +`uv pip install --no-sources` policy, the bundle MUST be referenced as a PEP 508 +direct git reference inside [project.dependencies] (which survives --no-sources), +NOT via a [tool.uv.sources] `path = "../.."` entry (which --no-sources strips). + +See microsoft-amplifier/amplifier-support#269 for the full root-cause analysis. """ from __future__ import annotations @@ -11,43 +18,75 @@ MODULE_ROOT = Path(__file__).parent.parent PYPROJECT = MODULE_ROOT / "pyproject.toml" +BUNDLE = "amplifier-bundle-context-intelligence" + def _load_pyproject() -> dict: return tomllib.loads(PYPROJECT.read_text()) +def _dep_name(dep: str) -> str: + """Extract the bare package name from a requirement string. + + Handles version specifiers (>=, ==) and PEP 508 direct references + (`name @ git+https://...`). + """ + return dep.split("@")[0].split(">=")[0].split("==")[0].strip() + + class TestHookDependencies: - """Verify hook module's pyproject.toml declares amplifier-bundle-context-intelligence.""" + """Verify the hook declares the bundle as a standalone-installable dependency.""" + + def test_bundle_declared_as_direct_git_reference(self) -> None: + """The bundle must be a PEP 508 direct git reference in [project.dependencies]. - def test_amplifier_bundle_context_intelligence_in_dependencies(self) -> None: - """amplifier-bundle-context-intelligence must appear in the dependencies list.""" + A direct `name @ git+https://...` reference survives `--no-sources`, + unlike a bare name (only resolvable from PyPI) or a [tool.uv.sources] entry. + """ data = _load_pyproject() deps: list[str] = data["project"]["dependencies"] - dep_names = [d.split(">=")[0].split("==")[0].strip() for d in deps] - assert "amplifier-bundle-context-intelligence" in dep_names, ( - f"Expected 'amplifier-bundle-context-intelligence' in dependencies, got: {deps}" + bundle_deps = [d for d in deps if _dep_name(d) == BUNDLE] + assert bundle_deps, f"Expected '{BUNDLE}' in dependencies, got: {deps}" + assert "git+https://" in bundle_deps[0], ( + f"Bundle dependency must be a direct git+https reference so it survives " + f"`uv pip install --no-sources`, got: {bundle_deps[0]!r}" ) - def test_uv_sources_has_path_entry_for_bundle(self) -> None: - """[tool.uv.sources] must have path = '../..' for amplifier-bundle-context-intelligence.""" + def test_bundle_is_not_a_uv_path_source(self) -> None: + """The bundle must NOT be a [tool.uv.sources] path entry. + + The `path = '../..'` assumption is exactly what breaks standalone install: + --no-sources strips [tool.uv.sources], leaving an unresolvable reference. + """ data = _load_pyproject() sources: dict = data.get("tool", {}).get("uv", {}).get("sources", {}) - assert "amplifier-bundle-context-intelligence" in sources, ( - f"Expected 'amplifier-bundle-context-intelligence' in [tool.uv.sources], got: {sources}" + assert BUNDLE not in sources, ( + f"'{BUNDLE}' must not be a [tool.uv.sources] entry (breaks standalone " + f"install under --no-sources); declare it as a direct git reference in " + f"[project.dependencies] instead. Got sources: {sources}" ) - entry = sources["amplifier-bundle-context-intelligence"] - assert entry.get("path") == "../..", f"Expected path = '../..', got: {entry}" def test_dependencies_list_has_httpx_and_bundle(self) -> None: - """The production dependencies must include httpx and amplifier-bundle-context-intelligence. - amplifier-core is NOT a production dep — it is runtime-provided by the Amplifier CLI. + """Production deps must include httpx and the bundle. + + amplifier-core is NOT a production dep — it is runtime-provided by the + Amplifier CLI. """ data = _load_pyproject() deps: list[str] = data["project"]["dependencies"] assert any("httpx" in d for d in deps), f"httpx not found in {deps}" - assert any("amplifier-bundle-context-intelligence" in d for d in deps), ( - f"amplifier-bundle-context-intelligence not found in {deps}" - ) - assert not any("amplifier-core" in d for d in deps), ( + assert any(_dep_name(d) == BUNDLE for d in deps), f"{BUNDLE} not found in {deps}" + assert not any(_dep_name(d) == "amplifier-core" for d in deps), ( f"amplifier-core must not be a production dep (runtime-provided): {deps}" ) + + def test_allow_direct_references_enabled(self) -> None: + """Building a wheel that carries a direct reference requires this hatch flag.""" + data = _load_pyproject() + allow = ( + data.get("tool", {}).get("hatch", {}).get("metadata", {}).get("allow-direct-references") + ) + assert allow is True, ( + "tool.hatch.metadata.allow-direct-references must be true to build a wheel " + f"carrying the direct git reference, got: {allow!r}" + ) diff --git a/modules/hook-context-intelligence/uv.lock b/modules/hook-context-intelligence/uv.lock index b945d5f..d5ae18a 100644 --- a/modules/hook-context-intelligence/uv.lock +++ b/modules/hook-context-intelligence/uv.lock @@ -5,20 +5,7 @@ requires-python = ">=3.11" [[package]] name = "amplifier-bundle-context-intelligence" version = "0.1.1" -source = { directory = "../../" } - -[package.metadata] - -[package.metadata.requires-dev] -dev = [ - { name = "httpx", specifier = ">=0.25" }, - { name = "idna", specifier = ">=3.15" }, - { name = "pyright", specifier = ">=1.1" }, - { name = "pytest", specifier = ">=9.0.3" }, - { name = "pytest-asyncio", specifier = ">=0.24" }, - { name = "pyyaml", specifier = ">=6.0" }, - { name = "ruff", specifier = ">=0.4" }, -] +source = { git = "https://github.com/microsoft/amplifier-bundle-context-intelligence?rev=v0.1.1#b722074f17a354816ebf5adcf0881b1562a2cbc5" } [[package]] name = "amplifier-core" @@ -34,7 +21,7 @@ dependencies = [ [[package]] name = "amplifier-module-hook-context-intelligence" -version = "0.1.1" +version = "0.1.2" source = { editable = "." } dependencies = [ { name = "amplifier-bundle-context-intelligence" }, @@ -54,7 +41,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "amplifier-bundle-context-intelligence", directory = "../../" }, + { name = "amplifier-bundle-context-intelligence", git = "https://github.com/microsoft/amplifier-bundle-context-intelligence?rev=v0.1.1" }, { name = "httpx", specifier = ">=0.28.1" }, { name = "idna", specifier = ">=3.15" }, ] diff --git a/modules/tool-context-intelligence-upload/pyproject.toml b/modules/tool-context-intelligence-upload/pyproject.toml index e33c97b..6e3cf3a 100644 --- a/modules/tool-context-intelligence-upload/pyproject.toml +++ b/modules/tool-context-intelligence-upload/pyproject.toml @@ -1,11 +1,11 @@ [project] name = "amplifier-module-tool-context-intelligence-upload" -version = "0.1.0" +version = "0.1.1" requires-python = ">=3.11" license = "MIT" dependencies = [ - "amplifier-bundle-context-intelligence", + "amplifier-bundle-context-intelligence @ git+https://github.com/microsoft/amplifier-bundle-context-intelligence@v0.1.1", "httpx>=0.28.1", "idna>=3.15", "amplifier-module-hook-context-intelligence", @@ -24,6 +24,10 @@ package = true [tool.hatch.build.targets.wheel] packages = ["amplifier_module_tool_context_intelligence_upload"] +[tool.hatch.metadata] +# Required to build a wheel that carries a PEP 508 direct-reference (git+https) dependency. +allow-direct-references = true + [dependency-groups] dev = [ "pytest>=9.0.3", @@ -33,7 +37,8 @@ dev = [ ] [tool.uv.sources] -amplifier-bundle-context-intelligence = { path = "../.." } +# The bundle is declared as a PEP 508 direct git reference in [project.dependencies] +# (survives `uv pip install --no-sources`); it is intentionally NOT a path source here. amplifier-module-hook-context-intelligence = { path = "../hook-context-intelligence", editable = true } [tool.pytest.ini_options] diff --git a/modules/tool-context-intelligence-upload/tests/test_pyproject_toml.py b/modules/tool-context-intelligence-upload/tests/test_pyproject_toml.py index 447bc62..b0ca63c 100644 --- a/modules/tool-context-intelligence-upload/tests/test_pyproject_toml.py +++ b/modules/tool-context-intelligence-upload/tests/test_pyproject_toml.py @@ -24,7 +24,7 @@ def test_project_name(self): def test_project_version(self): data = _load() - assert data["project"]["version"] == "0.1.0" + assert data["project"]["version"] == "0.1.1" def test_requires_python(self): data = _load() diff --git a/modules/tool-context-intelligence-upload/uv.lock b/modules/tool-context-intelligence-upload/uv.lock index 30be24e..6097820 100644 --- a/modules/tool-context-intelligence-upload/uv.lock +++ b/modules/tool-context-intelligence-upload/uv.lock @@ -5,24 +5,11 @@ requires-python = ">=3.11" [[package]] name = "amplifier-bundle-context-intelligence" version = "0.1.1" -source = { directory = "../../" } - -[package.metadata] - -[package.metadata.requires-dev] -dev = [ - { name = "httpx", specifier = ">=0.25" }, - { name = "idna", specifier = ">=3.15" }, - { name = "pyright", specifier = ">=1.1" }, - { name = "pytest", specifier = ">=9.0.3" }, - { name = "pytest-asyncio", specifier = ">=0.24" }, - { name = "pyyaml", specifier = ">=6.0" }, - { name = "ruff", specifier = ">=0.4" }, -] +source = { git = "https://github.com/microsoft/amplifier-bundle-context-intelligence?rev=v0.1.1#b722074f17a354816ebf5adcf0881b1562a2cbc5" } [[package]] name = "amplifier-module-hook-context-intelligence" -version = "0.1.1" +version = "0.1.2" source = { editable = "../hook-context-intelligence" } dependencies = [ { name = "amplifier-bundle-context-intelligence" }, @@ -32,7 +19,7 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "amplifier-bundle-context-intelligence", directory = "../../" }, + { name = "amplifier-bundle-context-intelligence", git = "https://github.com/microsoft/amplifier-bundle-context-intelligence?rev=v0.1.1" }, { name = "httpx", specifier = ">=0.28.1" }, { name = "idna", specifier = ">=3.15" }, ] @@ -49,7 +36,7 @@ dev = [ [[package]] name = "amplifier-module-tool-context-intelligence-upload" -version = "0.1.0" +version = "0.1.1" source = { editable = "." } dependencies = [ { name = "amplifier-bundle-context-intelligence" }, @@ -68,7 +55,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "amplifier-bundle-context-intelligence", directory = "../../" }, + { name = "amplifier-bundle-context-intelligence", git = "https://github.com/microsoft/amplifier-bundle-context-intelligence?rev=v0.1.1" }, { name = "amplifier-module-hook-context-intelligence", editable = "../hook-context-intelligence" }, { name = "httpx", specifier = ">=0.28.1" }, { name = "idna", specifier = ">=3.15" },