Skip to content

Commit f25c6b3

Browse files
Merge origin/main into chore/prepare-v0.9.0
Resolve CHANGELOG conflict: keep [0.8.12] release section (which already incorporates #616/#671/#681 from main's Unreleased), and add #766 entry for the codex/opencode --target compile fix that landed via #769. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2 parents d4c4d75 + 893a668 commit f25c6b3

File tree

8 files changed

+362
-74
lines changed

8 files changed

+362
-74
lines changed

.editorconfig

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# EditorConfig helps maintain consistent coding styles across different editors.
2+
# See https://editorconfig.org for details.
3+
root = true
4+
5+
[*]
6+
charset = utf-8
7+
end_of_line = lf
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true
10+
indent_style = space
11+
indent_size = 4
12+
13+
[*.{yml,yaml}]
14+
indent_size = 2
15+
16+
[*.json]
17+
indent_size = 2
18+
19+
[*.md]
20+
indent_size = 2
21+
# Trailing whitespace is significant in Markdown (line breaks), so leave it.
22+
trim_trailing_whitespace = false
23+
24+
[Makefile]
25+
indent_style = tab

.github/workflows/daily-test-improver.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,17 @@ Always do Task 7 (Update Monthly Activity Summary Issue) every run. In all comme
246246

247247
Maintain a single open issue titled `[Test Improver] Monthly Activity {YYYY}-{MM}` as a rolling summary of all Test Improver activity for the current month.
248248

249-
1. Search for an open `[Test Improver] Monthly Activity` issue with label `testing`. If it's for the current month, update it. If for a previous month, close it and create a new one. Read any maintainer comments - they may contain instructions or priorities; note them in memory.
249+
1. **Find the existing monthly issue (MANDATORY before any create)**:
250+
- Determine the current month string as `YYYY-MM` (e.g. `2025-04`).
251+
- Search for open issues using: `gh search issues --repo ${{ github.repository }} --state open --label testing "[Test Improver] Monthly Activity" --json number,title`
252+
- From the results, collect all open issues whose title **contains** the current `YYYY-MM` string.
253+
- **If exactly one matching issue for the current month exists: UPDATE it. Do NOT create a new issue.**
254+
- **If multiple matching issues for the current month exist: treat the lowest-numbered issue as the canonical monthly issue, UPDATE it, and close every other current-month match as a duplicate of that canonical issue.**
255+
- Before closing duplicate current-month issues, read any maintainer comments on each of them and preserve any instructions or priorities in memory, then consolidate any still-relevant details into the canonical issue update.
256+
- If no matching issue exists for the current month but one exists for a previous month: close the old one, then create a new issue for the current month.
257+
- If no matching issue exists at all: create a new issue for the current month.
258+
- Read any maintainer comments on the canonical issue - they may contain instructions or priorities; note them in memory.
259+
- **NEVER create a new issue if any open issue with the current month's `YYYY-MM` already exists in its title; update the canonical issue and close duplicates instead.**
250260
2. **Issue body format** - use **exactly** this structure:
251261

252262
```markdown

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4343
- `_count_package_files` in `apm deps list` now reads the canonical `.apm/context/` (singular) directory; previously it scanned `.apm/contexts/` and always reported `0 context files` per package (#748)
4444
- `apm pack --format plugin` no longer emits duplicated `skills/skills/` nesting for bare-skill dependencies referenced through virtual paths like `skills/<name>` (#738)
4545
- Provide an ADO-specific authentication error message for `dev.azure.com` remotes so users get actionable guidance instead of a generic GitHub-flavored hint (#742)
46+
- Fix `apm compile --target codex` (and `opencode`, `minimal`) being a silent no-op; `AgentsCompiler.compile()` now routes these through the AGENTS.md compiler instead of returning an empty success result that left stale `AGENTS.md` files (#766)
4647
- Support `codeload.github.com`-style archive URLs in Artifactory archive URL generation, unblocking JFrog Artifactory proxies configured against `codeload.github.com` (#712)
4748
- `_parse_artifactory_base_url()` now reads `PROXY_REGISTRY_URL` first (with `ARTIFACTORY_BASE_URL` fallback + `DeprecationWarning`), and the virtual-subdirectory download path checks `dep_ref.is_artifactory()` before falling back to env-var detection, fixing lockfile reinstall failures when proxy config is only on the lockfile entry (#616)
4849
- Fall back to SSH URLs when validating git remotes for generic / self-hosted hosts so `apm install` no longer fails the pre-install validation step against private SSH-only servers (#584)

src/apm_cli/commands/compile/cli.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -380,23 +380,20 @@ def compile(
380380

381381
# Handle distributed vs single-file compilation
382382
if config.strategy == "distributed" and not single_agents:
383-
# Show target-aware message with detection reason
383+
# Show target-aware message with detection reason. Use
384+
# get_target_description() so any future target added to
385+
# target_detection shows up here automatically.
384386
if detected_target == "minimal":
385387
logger.progress(f"Compiling for AGENTS.md only ({detection_reason})")
386388
logger.progress(
387-
" Create .github/ or .claude/ folder for full integration",
389+
" Create .github/, .claude/, .codex/, .opencode/ or .cursor/ folder for full integration",
388390
symbol="light_bulb",
389391
)
390-
elif detected_target == "vscode" or detected_target == "agents":
391-
logger.progress(
392-
f"Compiling for AGENTS.md (VSCode/Copilot) - {detection_reason}"
393-
)
394-
elif detected_target == "claude":
392+
else:
393+
description = get_target_description(detected_target)
395394
logger.progress(
396-
f"Compiling for CLAUDE.md (Claude Code) - {detection_reason}"
395+
f"Compiling for {description} - {detection_reason}"
397396
)
398-
else: # "all"
399-
logger.progress(f"Compiling for AGENTS.md + CLAUDE.md - {detection_reason}")
400397

401398
if dry_run:
402399
logger.dry_run_notice(

src/apm_cli/compilation/agents_compiler.py

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@
2020
)
2121
from .link_resolver import resolve_markdown_links, validate_link_targets
2222
from ..utils.paths import portable_relpath
23+
from ..core.target_detection import should_compile_agents_md, should_compile_claude_md
24+
25+
26+
# User-facing target aliases that map to the canonical "vscode" target.
27+
# Kept in sync with target_detection.detect_target().
28+
_VSCODE_TARGET_ALIASES = ("copilot", "agents")
29+
_KNOWN_TARGETS = (
30+
"vscode", "claude", "cursor", "opencode", "codex", "all", "minimal",
31+
) + _VSCODE_TARGET_ALIASES
2332

2433

2534
@dataclass
@@ -199,17 +208,52 @@ def compile(self, config: CompilationConfig, primitives: Optional[PrimitiveColle
199208
exclude_patterns=config.exclude,
200209
)
201210

202-
# Route to targets based on config.target
211+
# Route to targets based on config.target.
212+
# Use target_detection helpers as the single source of truth so
213+
# new targets (codex, opencode, cursor, minimal, ...) route
214+
# correctly without touching this method again.
215+
routing_target = (
216+
"vscode" if config.target in _VSCODE_TARGET_ALIASES else config.target
217+
)
218+
219+
if routing_target not in _KNOWN_TARGETS and config.target not in _KNOWN_TARGETS:
220+
self.errors.append(
221+
f"Unknown compilation target: {config.target!r}. "
222+
f"Expected one of: {', '.join(sorted(set(_KNOWN_TARGETS)))}"
223+
)
224+
return CompilationResult(
225+
success=False,
226+
output_path="",
227+
content="",
228+
warnings=self.warnings.copy(),
229+
errors=self.errors.copy(),
230+
stats={},
231+
)
232+
203233
results: List[CompilationResult] = []
204-
205-
# AGENTS.md target (vscode/agents)
206-
if config.target in ("vscode", "agents", "all"):
234+
235+
if should_compile_agents_md(routing_target):
207236
results.append(self._compile_agents_md(config, primitives))
208-
209-
# CLAUDE.md target
210-
if config.target in ("claude", "all"):
237+
238+
if should_compile_claude_md(routing_target):
211239
results.append(self._compile_claude_md(config, primitives))
212-
240+
241+
# Defensive: should never happen for a known target, but guards
242+
# against future target_detection drift silently producing no-ops.
243+
if not results:
244+
self.errors.append(
245+
f"Target {config.target!r} did not route to any compiler. "
246+
"This is an internal bug in target routing."
247+
)
248+
return CompilationResult(
249+
success=False,
250+
output_path="",
251+
content="",
252+
warnings=self.warnings.copy(),
253+
errors=self.errors.copy(),
254+
stats={},
255+
)
256+
213257
# Merge results from all targets
214258
return self._merge_results(results)
215259

src/apm_cli/deps/github_downloader.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -412,20 +412,16 @@ def _should_use_artifactory_proxy(self, dep_ref: 'DependencyReference') -> bool:
412412
return is_github_hostname(host)
413413

414414
def _parse_artifactory_base_url(self) -> Optional[tuple]:
415-
"""Parse ARTIFACTORY_BASE_URL into (host, prefix, scheme)."""
416-
import urllib.parse as urlparse
417-
base_url = os.environ.get('ARTIFACTORY_BASE_URL', '').strip().rstrip('/')
418-
if not base_url:
419-
return None
420-
parsed = urlparse.urlparse(base_url)
421-
if parsed.scheme not in ('https', 'http'):
422-
_debug(f"ARTIFACTORY_BASE_URL has unsupported scheme: {parsed.scheme}")
423-
return None
424-
host = parsed.hostname
425-
path = parsed.path.strip('/')
426-
if not host or not path:
415+
"""Return ``(host, prefix, scheme)`` from the registry proxy config, or ``None``.
416+
417+
Delegates to :meth:`~apm_cli.deps.registry_proxy.RegistryConfig.from_env`
418+
so that env-var precedence and deprecation warnings are handled in one place.
419+
"""
420+
from .registry_proxy import RegistryConfig
421+
cfg = RegistryConfig.from_env()
422+
if cfg is None:
427423
return None
428-
return (host, path, parsed.scheme)
424+
return (cfg.host, cfg.prefix, cfg.scheme)
429425

430426
def _resolve_dep_token(self, dep_ref: Optional[DependencyReference] = None) -> Optional[str]:
431427
"""Resolve the per-dependency auth token via AuthResolver.
@@ -2125,8 +2121,13 @@ def download_package(
21252121
elif dep_ref.is_virtual_collection():
21262122
return self.download_collection_package(dep_ref, target_path, progress_task_id, progress_obj)
21272123
elif dep_ref.is_virtual_subdirectory():
2128-
# When PROXY_REGISTRY_ONLY is set, download full archive and extract subdir
2129-
art_proxy = self._parse_artifactory_base_url()
2124+
# Mode 1: explicit Artifactory FQDN from lockfile
2125+
if dep_ref.is_artifactory():
2126+
proxy_info = (dep_ref.host, dep_ref.artifactory_prefix, "https")
2127+
return self._download_subdirectory_from_artifactory(
2128+
dep_ref, target_path, proxy_info, progress_task_id, progress_obj
2129+
)
2130+
# Mode 2: transparent proxy via env var (art_proxy computed above)
21302131
if self._is_artifactory_only() and art_proxy:
21312132
return self._download_subdirectory_from_artifactory(
21322133
dep_ref, target_path, art_proxy, progress_task_id, progress_obj

tests/unit/compilation/test_compile_target_flag.py

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,14 +166,71 @@ def test_target_all_generates_both(self, temp_project, sample_primitives):
166166
dry_run=True,
167167
single_agents=True # Use single-file for AGENTS.md
168168
)
169-
169+
170170
compiler = AgentsCompiler(str(temp_project))
171171
result = compiler.compile(config, sample_primitives)
172-
172+
173173
assert result.success
174174
# Output path should mention both targets
175175
assert "AGENTS.md" in result.output_path or "CLAUDE" in result.output_path
176176

177+
def test_target_codex_generates_agents_md(self, temp_project, sample_primitives):
178+
"""Regression for issue #766: --target codex must produce AGENTS.md, not a silent no-op."""
179+
config = CompilationConfig(
180+
target="codex",
181+
dry_run=True,
182+
single_agents=True,
183+
)
184+
185+
compiler = AgentsCompiler(str(temp_project))
186+
result = compiler.compile(config, sample_primitives)
187+
188+
assert result.success
189+
assert result.output_path, "codex target must route to a compiler, not return empty"
190+
assert "AGENTS.md" in result.output_path
191+
192+
def test_target_opencode_generates_agents_md(self, temp_project, sample_primitives):
193+
"""target='opencode' must route to AGENTS.md (same universal format as codex)."""
194+
config = CompilationConfig(
195+
target="opencode",
196+
dry_run=True,
197+
single_agents=True,
198+
)
199+
200+
compiler = AgentsCompiler(str(temp_project))
201+
result = compiler.compile(config, sample_primitives)
202+
203+
assert result.success
204+
assert "AGENTS.md" in result.output_path
205+
206+
def test_target_minimal_generates_agents_md(self, temp_project, sample_primitives):
207+
"""target='minimal' must route to AGENTS.md-only."""
208+
config = CompilationConfig(
209+
target="minimal",
210+
dry_run=True,
211+
single_agents=True,
212+
)
213+
214+
compiler = AgentsCompiler(str(temp_project))
215+
result = compiler.compile(config, sample_primitives)
216+
217+
assert result.success
218+
assert "AGENTS.md" in result.output_path
219+
220+
def test_unknown_target_returns_failure(self, temp_project, sample_primitives):
221+
"""Unknown target must fail explicitly instead of silently succeeding."""
222+
config = CompilationConfig(
223+
target="not-a-real-target",
224+
dry_run=True,
225+
single_agents=True,
226+
)
227+
228+
compiler = AgentsCompiler(str(temp_project))
229+
result = compiler.compile(config, sample_primitives)
230+
231+
assert result.success is False
232+
assert any("Unknown compilation target" in e for e in result.errors)
233+
177234

178235
class TestMergeResults:
179236
"""Tests for _merge_results() method."""

0 commit comments

Comments
 (0)