diff --git a/amplifier_web/host/mentions.py b/amplifier_web/host/mentions.py index b429019b2..d3d89d05e 100644 --- a/amplifier_web/host/mentions.py +++ b/amplifier_web/host/mentions.py @@ -31,6 +31,10 @@ def __init__(self, foundation, workspace): self.workspace = Path(workspace) def resolve(self, mention): + return self.resolve_relative(mention, self.workspace) + + def resolve_relative(self, mention, relative_to): + """Scope local references without retargeting project/user shortcuts.""" if not mention.startswith('@') or '..' in mention: return None body = mention[1:] @@ -46,8 +50,8 @@ def resolve(self, mention): return self.foundation.resolve(mention) if self.foundation else None else: # Retain Foundation's plain-path and omitted-.md behavior used by - # filesystem tools, scoped to this session's workspace. - return BaseMentionResolver(base_path=self.workspace).resolve(mention) + # filesystem tools, scoped to the workspace or referring file. + return BaseMentionResolver(base_path=relative_to).resolve(mention) if not relative: return None path = root / relative diff --git a/tests/test_runtime_mentions.py b/tests/test_runtime_mentions.py index b9c29f814..0d99a597b 100644 --- a/tests/test_runtime_mentions.py +++ b/tests/test_runtime_mentions.py @@ -19,6 +19,23 @@ def coordinator(workspace, bundles=None): return value +def test_per_call_relative_scope_preserves_session_shortcuts(tmp_path, monkeypatch): + workspace, nested, user, bundle = (tmp_path / name for name in ('workspace', 'nested', 'user', 'bundle')) + for path in (workspace / '.amplifier', nested, user, bundle): + path.mkdir(parents=True) + (path / 'rules.md').write_text(str(path)) + monkeypatch.setenv('AMPLIFIER_HOME', str(user)) + owner = coordinator(workspace, {'bundle': Bundle(name='bundle', base_path=bundle)}) + resolver = owner.get_capability('mention_resolver') + assert resolver.resolve_relative('@./rules.md', nested) == nested / 'rules.md' + assert resolver.resolve_relative('@project:rules.md', nested) == workspace / '.amplifier/rules.md' + assert resolver.resolve_relative('@user:rules.md', nested) == user / 'rules.md' + assert resolver.resolve_relative('@bundle:rules.md', nested) == bundle / 'rules.md' + assert resolver.resolve_relative('@../rules.md', nested) is None + assert resolver.workspace == workspace + assert resolver.resolve('@./rules.md') is None + + async def test_workspace_shortcuts_and_composed_namespace_use_shared_expander(tmp_path, monkeypatch): workspace = tmp_path / 'workspace' bundle = tmp_path / 'bundle'