Skip to content

Commit 9e1af7d

Browse files
Merge branch 'main' into fix/preserve-ssh-url-bitbucket-datacenter
2 parents ecff195 + 53cdee3 commit 9e1af7d

3 files changed

Lines changed: 86 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
- Preserve `ssh://` dependency URLs with custom ports for Bitbucket Datacenter repositories instead of silently falling back to HTTPS (#661)
1818
- Pin codex setup to `rust-v0.118.0` for security and reproducibility; update config to `wire_api = "responses"` (#663)
1919
- Propagate headers and environment variables through OpenCode MCP adapter with defensive copies to prevent mutation (#622)
20+
- Fix `apm compile --target claude` silently skipping dependency instructions stored in `.github/instructions/` (#631)
2021
### Changed
2122

2223
- `apm marketplace browse/search/add/update` now route through the registry proxy when `PROXY_REGISTRY_URL` is set; `PROXY_REGISTRY_ONLY=1` blocks direct GitHub API calls (#506)

src/apm_cli/primitives/discovery.py

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,20 @@
5555
]
5656
}
5757

58+
# Dependency primitive patterns for .github directory within dependencies.
59+
# Some packages store primitives in .github/ instead of (or in addition to) .apm/.
60+
DEPENDENCY_GITHUB_PRIMITIVE_PATTERNS: Dict[str, List[str]] = {
61+
'chatmode': [
62+
"agents/*.agent.md",
63+
"chatmodes/*.chatmode.md",
64+
],
65+
'instruction': ["instructions/*.instructions.md"],
66+
'context': [
67+
"context/*.context.md",
68+
"memory/*.memory.md",
69+
]
70+
}
71+
5872

5973
def discover_primitives(
6074
base_dir: str = ".",
@@ -297,37 +311,48 @@ def get_dependency_declaration_order(base_dir: str) -> List[str]:
297311
return []
298312

299313

300-
def scan_directory_with_source(directory: Path, collection: PrimitiveCollection, source: str) -> None:
301-
"""Scan a directory for primitives with a specific source tag.
302-
314+
def _scan_patterns(base_dir: Path, patterns: Dict[str, List[str]], collection: PrimitiveCollection, source: str) -> None:
315+
"""Glob-scan-parse loop for one base directory and one patterns dict.
316+
303317
Args:
304-
directory (Path): Directory to scan (e.g., apm_modules/package_name).
318+
base_dir (Path): Directory to scan (e.g., dep/.apm or dep/.github).
319+
patterns (Dict[str, List[str]]): Primitive-type → glob-pattern mapping.
305320
collection (PrimitiveCollection): Collection to add primitives to.
306321
source (str): Source identifier for discovered primitives.
307322
"""
308-
# Look for .apm directory within the dependency
309-
apm_dir = directory / ".apm"
310-
if not apm_dir.exists():
311-
# Even without .apm, check for SKILL.md (Claude Skills support)
312-
_discover_skill_in_directory(directory, collection, source)
313-
return
314-
315-
# Find and parse files for each primitive type
316-
for primitive_type, patterns in DEPENDENCY_PRIMITIVE_PATTERNS.items():
317-
for pattern in patterns:
318-
full_pattern = str(apm_dir / pattern)
319-
matching_files = glob.glob(full_pattern, recursive=True)
320-
321-
for file_path_str in matching_files:
323+
for _primitive_type, type_patterns in patterns.items():
324+
for pattern in type_patterns:
325+
for file_path_str in glob.glob(str(base_dir / pattern), recursive=True):
322326
file_path = Path(file_path_str)
323327
if file_path.is_file() and _is_readable(file_path):
324328
try:
325329
primitive = parse_primitive_file(file_path, source=source)
326330
collection.add_primitive(primitive)
327331
except Exception as e:
328332
print(f"Warning: Failed to parse dependency primitive {file_path}: {e}")
329-
330-
# Also check for SKILL.md in the dependency root
333+
334+
335+
def scan_directory_with_source(directory: Path, collection: PrimitiveCollection, source: str) -> None:
336+
"""Scan a directory for primitives with a specific source tag.
337+
338+
Args:
339+
directory (Path): Directory to scan (e.g., apm_modules/package_name).
340+
collection (PrimitiveCollection): Collection to add primitives to.
341+
source (str): Source identifier for discovered primitives.
342+
"""
343+
# Scan .apm directory within the dependency
344+
apm_dir = directory / ".apm"
345+
if apm_dir.exists():
346+
_scan_patterns(apm_dir, DEPENDENCY_PRIMITIVE_PATTERNS, collection, source)
347+
348+
# Also scan .github directory — some packages store primitives there instead of (or
349+
# in addition to) .apm/. Without this, dependency instructions in .github/instructions/
350+
# are silently skipped in the normal compile path (issue #631).
351+
github_dir = directory / ".github"
352+
if github_dir.exists():
353+
_scan_patterns(github_dir, DEPENDENCY_GITHUB_PRIMITIVE_PATTERNS, collection, source)
354+
355+
# Check for SKILL.md in the dependency root
331356
_discover_skill_in_directory(directory, collection, source)
332357

333358

tests/unit/primitives/test_discovery_parser.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,46 @@ def test_parse_error_in_dep_primitive_warns_and_continues(self):
331331
self.assertIn("Warning", buf.getvalue())
332332
self.assertEqual(collection.count(), 0)
333333

334+
def test_github_instructions_discovered_when_no_apm_dir(self):
335+
"""Regression test for issue #631.
336+
337+
Dependency instructions stored in .github/instructions/ must be
338+
included in compile --target claude without --local-only.
339+
"""
340+
dep_dir = Path(self.tmp) / "chkp-roniz" / "cc-rubber-duck"
341+
_write(
342+
dep_dir / ".github" / "instructions" / "rubber-duck.instructions.md",
343+
INSTRUCTION_CONTENT,
344+
)
345+
collection = PrimitiveCollection()
346+
scan_directory_with_source(
347+
dep_dir, collection, source="dependency:chkp-roniz/cc-rubber-duck"
348+
)
349+
self.assertEqual(len(collection.instructions), 1)
350+
self.assertEqual(
351+
collection.instructions[0].source,
352+
"dependency:chkp-roniz/cc-rubber-duck",
353+
)
354+
355+
def test_github_instructions_discovered_alongside_apm_dir(self):
356+
"""Regression test for issue #631.
357+
358+
When a dependency has both .apm/instructions/ and .github/instructions/,
359+
primitives from both directories must be discovered.
360+
"""
361+
dep_dir = Path(self.tmp) / "owner" / "mixed-pkg"
362+
_write(
363+
dep_dir / ".apm" / "instructions" / "from-apm.instructions.md",
364+
INSTRUCTION_CONTENT,
365+
)
366+
_write(
367+
dep_dir / ".github" / "instructions" / "from-github.instructions.md",
368+
INSTRUCTION_CONTENT,
369+
)
370+
collection = PrimitiveCollection()
371+
scan_directory_with_source(dep_dir, collection, source="dependency:owner/mixed-pkg")
372+
self.assertEqual(len(collection.instructions), 2)
373+
334374

335375
class TestGetDependencyDeclarationOrder(unittest.TestCase):
336376
"""Tests for get_dependency_declaration_order."""

0 commit comments

Comments
 (0)