Skip to content

[BUG] applyTo comma-separated globs are not split ? Claude target emits broken paths: and compiler warns "matches no files" #1366

@smuxdev

Description

@smuxdev

Summary

apm install (and the placement optimizer at compile time) treats the applyTo frontmatter field as a single glob even when it contains a comma-separated list of globs. The docs explicitly state that applyTo accepts "Glob (or comma-separated globs)" (instructions-and-agents.md), so this is a regression vs documented behavior.

Two visible symptoms:

  1. Broken Claude target output. .claude/rules/<name>.md gets paths: as a single-item YAML list with the literal comma-string — Claude Code can't filter by path, so the rule never auto-attaches:

    paths:
      - "**/src/**,**/app/**,**/api/**,**/services/**"   # ← one literal glob with commas
  2. Noisy compile-time warnings with no actionable signal:

    [!] Warning: Pattern '**/src/**,**/app/**,**/api/**,**/services/**,...' matches no files - placing at project root
    [!] Warning: Pattern 'tasks/**/*.md,**/proposal.md,**/tasks.md,**/design.md,**/docs/**/*.md' matches no files - placing in intended directory 'tasks'
    

    The patterns above all match plenty of files when split correctly. The warnings only fire because the matcher is fed the joined comma-string.

Reproduction

Minimal source file under any package, e.g. packages/foo/.apm/instructions/foo.instructions.md:

---
description: Backend standards
applyTo: "**/src/**,**/api/**,**/services/**"
---

# Foo
body...

Run apm install in a project that consumes this package and has matching files under src/, api/, services/.

Expected

  • .claude/rules/foo.md frontmatter:

    paths:
      - "**/src/**"
      - "**/api/**"
      - "**/services/**"
  • No "matches no files" warning during compile.

Actual

  • .claude/rules/foo.md frontmatter:

    paths:
      - "**/src/**,**/api/**,**/services/**"
  • Warning emitted: Pattern '**/src/**,**/api/**,**/services/**' matches no files - placing at project root.

Root Cause

A) Claude target conversion — src/apm_cli/integration/instruction_integrator.py::_convert_to_claude_rules

The function reads applyTo as a single string and wraps it as a single list item without splitting on commas:

for line in fm_block.splitlines():
    line_stripped = line.strip()
    if line_stripped.startswith("applyTo:"):
        apply_to = line_stripped[len("applyTo:"):].strip().strip("'\"")

if apply_to:
    parts = ["---"]
    parts.append("paths:")
    parts.append(f'  - "{apply_to}"')   # ← no comma-split
    parts.append("---")

B) Placement optimizer — src/apm_cli/compilation/context_optimizer.py::_solve_placement_optimization

The optimizer feeds the unsplit string straight to glob.glob() / fnmatch.fnmatch(), then emits a misleading warning when the resulting match set is empty:

pattern = instruction.apply_to     # ← single string, even when comma-separated
# ...
matches = self._cached_glob(expanded_pattern)
# ...
"Pattern '{pattern}' matches no files - placing at project root"

C) Test gap — tests/unit/integration/test_instruction_integrator.py::TestConvertToClaudeRules

All existing tests pass a single glob to applyTo ('src/**/*.py', '**/*.ts', …). No test covers comma-separated input, so the behavior is unconstrained.

D) Docs promise multi-glob

docs/src/content/docs/producer/author-primitives/instructions-and-agents.md documents applyTo as "Glob (or comma-separated globs) the rule binds to". The implementation contradicts this.

Impact

  • Claude target is effectively broken for any source using comma-separated globs. The paths: filter never matches → the rule does not auto-attach to relevant files. The author thinks the rule is scope-attached when it is silently inert.
  • Cursor and Windsurf targets likely have the same problem (they also convert applyToglobs: per the targets matrix); needs verification.
  • Compile-time warnings are noise: every comma-separated applyTo in the source tree produces a "matches no files" warning that the author cannot fix without changing the source format — which the docs say should be valid.

Proposed Fix

Fix A — _convert_to_claude_rules

if apply_to:
    items = [s.strip() for s in apply_to.split(",") if s.strip()]
    parts = ["---", "paths:"]
    parts.extend(f'  - "{item}"' for item in items)
    parts.append("---")

Fix B — _solve_placement_optimization

Split pattern on commas before glob/fnmatch. Consider the placement "matched" if any sub-pattern matches; emit the warning only when all sub-patterns are empty (and quote the offending sub-pattern, not the joined string, for actionable diagnostics).

Fix C — Cursor / Windsurf targets

Apply the same comma-split to the globs: emission so behavior is consistent across targets.

Fix D — Tests

Add to TestConvertToClaudeRules:

def test_maps_comma_separated_apply_to_to_paths_list():
    fm = "---\napplyTo: '**/*.py,**/*.ts,**/*.js'\n---\n\n# body"
    result = _convert_to_claude_rules(fm)
    assert 'paths:\n  - "**/*.py"\n  - "**/*.ts"\n  - "**/*.js"' in result

def test_handles_whitespace_around_commas():
    fm = "---\napplyTo: '**/*.py , **/*.ts'\n---\n\n# body"
    result = _convert_to_claude_rules(fm)
    assert '  - "**/*.py"' in result
    assert '  - "**/*.ts"' in result

And an integration-level test for the placement optimizer verifying that a comma-separated applyTo matching real files does not produce the warning.

Workaround (consumers)

Until upstream lands, run a post-install script to split the comma-string into a real list:

import re
from pathlib import Path

p = re.compile(r'^paths:\n  - "([^"]+)"$', re.MULTILINE)
for f in Path(".claude/rules").glob("*.md"):
    text = f.read_text()
    m = p.search(text)
    if not m or "," not in m.group(1):
        continue
    items = [s.strip() for s in m.group(1).split(",") if s.strip()]
    new_block = "paths:\n" + "\n".join(f'  - "{i}"' for i in items)
    f.write_text(text[:m.start()] + new_block + text[m.end():])

This is brittle (re-run on every apm install); a real fix upstream is preferable.

Environment

  • apm CLI version: 0.13.0
  • Python: 3.13.13
  • OS: Linux 6.8 (Ubuntu)
  • Targets in apm.yml: claude, opencode

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status

    In Progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions