From baf98c7af704031d3ff8fbc3b2dadc8b67f6c9f3 Mon Sep 17 00:00:00 2001 From: colombod Date: Mon, 22 Jun 2026 18:02:27 +0000 Subject: [PATCH] fix(modules): uniform @main bundle ref + amplifier-core>=1.6.0 across all modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete dependency fix combining the bundle-reference fix (PR #38) with the amplifier-core pinning cleanup (found in PR #27). Two classes of inconsistent dependency sourcing caused uv to abort the Resolve worker's single-command co-install with "conflicting URLs": 1. Bundle self-reference: hook/upload used a git tag (@v0.1.1) while graph-query/blob-read used a [tool.uv.sources] path = "../.." override. 2. amplifier-core: hook pinned an unsatisfiable rev = "v1.4.1" while the tools used branch = "main" — a second conflicting/unsatisfiable source. All four modules now reference the bundle identically as a PEP 508 direct git reference @main (survives `uv pip install --no-sources`, per #36) with allow-direct-references enabled. The amplifier-core [tool.uv.sources] overrides are removed and the dev pin unified to amplifier-core>=1.6.0. Guard tests added to blob-read and graph-query (mirroring the hook's test_hook_dependencies.py) lock the bundle as a direct git ref so it cannot regress to a uv path source. Validation: - guard tests: hook 4, blob-read 4, graph-query 4, upload 26 — all pass - co-install `uv pip install -e hook -e tool-graph-query -e tool-blob-read` (the original failing command): exit 0, 12 packages, no conflicting-URLs error - standalone `uv pip install --no-sources ./tool-blob-read`: exit 0 - `uv build --wheel ./tool-graph-query` (proves allow-direct-references): exit 0 Supersedes #38 (carries its fix verbatim plus the core-pin cleanup). 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> --- .../hook-context-intelligence/pyproject.toml | 11 +-- modules/tool-blob-read/pyproject.toml | 12 +-- .../tests/test_tool_dependencies.py | 96 +++++++++++++++++++ .../pyproject.toml | 2 +- modules/tool-graph-query/pyproject.toml | 12 +-- .../tests/test_tool_dependencies.py | 96 +++++++++++++++++++ 6 files changed, 207 insertions(+), 22 deletions(-) create mode 100644 modules/tool-blob-read/tests/test_tool_dependencies.py create mode 100644 modules/tool-graph-query/tests/test_tool_dependencies.py diff --git a/modules/hook-context-intelligence/pyproject.toml b/modules/hook-context-intelligence/pyproject.toml index 79e6974..164af89 100644 --- a/modules/hook-context-intelligence/pyproject.toml +++ b/modules/hook-context-intelligence/pyproject.toml @@ -9,7 +9,7 @@ dependencies = [ "httpx>=0.28.1", "idna>=3.15", "pathspec>=0.12,<2", - "amplifier-bundle-context-intelligence @ git+https://github.com/microsoft/amplifier-bundle-context-intelligence@v0.1.1", + "amplifier-bundle-context-intelligence @ git+https://github.com/microsoft/amplifier-bundle-context-intelligence@main", ] [project.entry-points."amplifier.modules"] @@ -31,7 +31,7 @@ allow-direct-references = true [dependency-groups] dev = [ - "amplifier-core>=1.4.1", + "amplifier-core>=1.6.0", "pytest>=9.0.3", "pytest-asyncio>=0.24", "pyyaml>=6.0", @@ -39,13 +39,6 @@ dev = [ "ruff>=0.4", ] -[tool.uv.sources] -amplifier-core = { git = "https://github.com/microsoft/amplifier-core", rev = "v1.4.1" } -# 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" asyncio_default_fixture_loop_scope = "function" diff --git a/modules/tool-blob-read/pyproject.toml b/modules/tool-blob-read/pyproject.toml index d5c9219..c6e935f 100644 --- a/modules/tool-blob-read/pyproject.toml +++ b/modules/tool-blob-read/pyproject.toml @@ -6,7 +6,7 @@ requires-python = ">=3.11" license = "MIT" dependencies = [ - "amplifier-bundle-context-intelligence", + "amplifier-bundle-context-intelligence @ git+https://github.com/microsoft/amplifier-bundle-context-intelligence@main", "httpx>=0.28.1", "idna>=3.15", ] @@ -24,19 +24,19 @@ package = true [tool.hatch.build.targets.wheel] packages = ["amplifier_module_tool_blob_read"] +[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", + "amplifier-core>=1.6.0", "pytest>=9.0.3", "pytest-asyncio>=0.24", "pyright>=1.1", "ruff>=0.4", ] -[tool.uv.sources] -amplifier-bundle-context-intelligence = { path = "../.." } -amplifier-core = { git = "https://github.com/microsoft/amplifier-core", branch = "main" } - [tool.pytest.ini_options] asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "function" diff --git a/modules/tool-blob-read/tests/test_tool_dependencies.py b/modules/tool-blob-read/tests/test_tool_dependencies.py new file mode 100644 index 0000000..147bf00 --- /dev/null +++ b/modules/tool-blob-read/tests/test_tool_dependencies.py @@ -0,0 +1,96 @@ +"""Test that the blob-read tool pyproject.toml declares the bundle dependency in a +form that installs standalone (outside the monorepo). + +The tool imports from the `context_intelligence` package shipped by the parent +bundle. For the tool 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). + +This guard mirrors hook-context-intelligence/tests/test_hook_dependencies.py so +that every module references the parent bundle uniformly and cannot regress to a +uv path source (the original cause of the uv conflicting-URLs co-install failure). +""" + +from __future__ import annotations + +import tomllib +from pathlib import Path + +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 TestToolDependencies: + """Verify the tool 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]. + + 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"] + 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_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. + Two modules referencing the bundle via path while others use a git URL is + what produced uv's "conflicting URLs" abort on a single co-install command. + """ + data = _load_pyproject() + sources: dict = data.get("tool", {}).get("uv", {}).get("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}" + ) + + def test_dependencies_list_has_httpx_and_bundle(self) -> None: + """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(_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/tool-context-intelligence-upload/pyproject.toml b/modules/tool-context-intelligence-upload/pyproject.toml index 6e3cf3a..baf3ece 100644 --- a/modules/tool-context-intelligence-upload/pyproject.toml +++ b/modules/tool-context-intelligence-upload/pyproject.toml @@ -5,7 +5,7 @@ requires-python = ">=3.11" license = "MIT" dependencies = [ - "amplifier-bundle-context-intelligence @ git+https://github.com/microsoft/amplifier-bundle-context-intelligence@v0.1.1", + "amplifier-bundle-context-intelligence @ git+https://github.com/microsoft/amplifier-bundle-context-intelligence@main", "httpx>=0.28.1", "idna>=3.15", "amplifier-module-hook-context-intelligence", diff --git a/modules/tool-graph-query/pyproject.toml b/modules/tool-graph-query/pyproject.toml index 346bc50..389f9b4 100644 --- a/modules/tool-graph-query/pyproject.toml +++ b/modules/tool-graph-query/pyproject.toml @@ -6,7 +6,7 @@ requires-python = ">=3.11" license = "MIT" dependencies = [ - "amplifier-bundle-context-intelligence", + "amplifier-bundle-context-intelligence @ git+https://github.com/microsoft/amplifier-bundle-context-intelligence@main", "httpx>=0.28.1", "idna>=3.15", ] @@ -24,19 +24,19 @@ package = true [tool.hatch.build.targets.wheel] packages = ["amplifier_module_tool_graph_query"] +[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", + "amplifier-core>=1.6.0", "pytest>=9.0.3", "pytest-asyncio>=0.24", "pyright>=1.1", "ruff>=0.4", ] -[tool.uv.sources] -amplifier-bundle-context-intelligence = { path = "../.." } -amplifier-core = { git = "https://github.com/microsoft/amplifier-core", branch = "main" } - [tool.pytest.ini_options] asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "function" diff --git a/modules/tool-graph-query/tests/test_tool_dependencies.py b/modules/tool-graph-query/tests/test_tool_dependencies.py new file mode 100644 index 0000000..f8952e6 --- /dev/null +++ b/modules/tool-graph-query/tests/test_tool_dependencies.py @@ -0,0 +1,96 @@ +"""Test that the graph-query tool pyproject.toml declares the bundle dependency in a +form that installs standalone (outside the monorepo). + +The tool imports from the `context_intelligence` package shipped by the parent +bundle. For the tool 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). + +This guard mirrors hook-context-intelligence/tests/test_hook_dependencies.py so +that every module references the parent bundle uniformly and cannot regress to a +uv path source (the original cause of the uv conflicting-URLs co-install failure). +""" + +from __future__ import annotations + +import tomllib +from pathlib import Path + +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 TestToolDependencies: + """Verify the tool 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]. + + 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"] + 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_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. + Two modules referencing the bundle via path while others use a git URL is + what produced uv's "conflicting URLs" abort on a single co-install command. + """ + data = _load_pyproject() + sources: dict = data.get("tool", {}).get("uv", {}).get("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}" + ) + + def test_dependencies_list_has_httpx_and_bundle(self) -> None: + """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(_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}" + )