diff --git a/extensions/agent-context/scripts/python/update_agent_context.py b/extensions/agent-context/scripts/python/update_agent_context.py index 15c0dceef9..fc8894ee14 100644 --- a/extensions/agent-context/scripts/python/update_agent_context.py +++ b/extensions/agent-context/scripts/python/update_agent_context.py @@ -11,8 +11,9 @@ When ``plan_path`` is omitted, the script derives it from ``.specify/feature.json`` (written by /speckit-specify). Falls back to the most -recently modified ``specs/*/plan.md`` only when feature.json is absent or its -plan does not exist yet. +recently modified ``plan.md`` anywhere under ``specs/`` (including nested scoped +layouts such as ``specs///plan.md``) only when feature.json is +absent or its plan does not exist yet. """ from __future__ import annotations @@ -173,7 +174,7 @@ def _resolve_plan_path(project_root: str) -> str: if not plan_path: root = Path(project_root).resolve() plans = sorted( - (root / "specs").glob("*/plan.md"), + (root / "specs").rglob("plan.md"), key=lambda p: p.stat().st_mtime, reverse=True, ) diff --git a/tests/extensions/test_update_agent_context_python_parity.py b/tests/extensions/test_update_agent_context_python_parity.py index 92e8e20f8b..51d1831662 100644 --- a/tests/extensions/test_update_agent_context_python_parity.py +++ b/tests/extensions/test_update_agent_context_python_parity.py @@ -317,6 +317,27 @@ def test_python_mtime_fallback_matching_bash(tmp_path: Path) -> None: assert b"at specs/001-new/plan.md" in content +@requires_posix_bash +def test_python_mtime_fallback_finds_nested_plan_matching_bash(tmp_path: Path) -> None: + # Regression: the mtime fallback must discover plan.md in nested scoped + # layouts (specs///plan.md), matching the Bash/PowerShell + # ports and the documented recursive-discovery contract (see #3024). A + # one-level scan (specs/*/plan.md) would miss this and omit the plan link. + repo_a, repo_b = twin_projects(tmp_path, context_file="AGENTS.md") + for repo in (repo_a, repo_b): + plan = repo / "specs" / "scope-a" / "002-nested" / "plan.md" + plan.parent.mkdir(parents=True, exist_ok=True) + plan.write_text("# plan\n", encoding="utf-8") + + bash = run_bash(repo_a) + py = run_python(repo_b) + + assert_parity(bash, py, repo_a, repo_b) + content = (repo_b / "AGENTS.md").read_bytes() + assert content == (repo_a / "AGENTS.md").read_bytes() + assert b"at specs/scope-a/002-nested/plan.md" in content + + @requires_posix_bash def test_python_prefers_feature_json_over_mtime_matching_bash(tmp_path: Path) -> None: repo_a, repo_b = twin_projects(tmp_path, context_file="AGENTS.md")