Skip to content

Add GitHub Enterprise (GHE) hostname support to dependency parsing#8

Merged
danielmeppiel merged 4 commits intomicrosoft:mainfrom
richgo:main
Oct 28, 2025
Merged

Add GitHub Enterprise (GHE) hostname support to dependency parsing#8
danielmeppiel merged 4 commits intomicrosoft:mainfrom
richgo:main

Conversation

@richgo
Copy link
Copy Markdown
Contributor

@richgo richgo commented Oct 17, 2025

Add support for GitHub Enterprise (GHE) hostnames in dependency parsing so apm can correctly detect and normalize repository references hosted on self-managed GitHub instances.
What changed

Update the dependency parsing logic to accept and validate custom GHE hostnames in addition to github.com.
Preserve backwards compatibility for existing github.com parsing behavior.
Add unit tests covering:
standard github.com repo strings
GHE hostname variants (with and without ports)
edge cases and invalid hostnames
Why

Users running packages on self-hosted GitHub instances faced incorrect parsing of dependency references. This change allows apm to work in GHE environments.
How to test

Run the included unit tests: uv run pytest (test docs updated)
Manually verify parsing by providing dependency strings that reference a GHE hostname (e.g., acompany.ghe.com/owner/repo) and confirming correct repo extraction.
Notes

No behavior change for public GitHub users.
Backwards-compatible and covered by tests.

@richgo richgo mentioned this pull request Oct 17, 2025
@richgo richgo marked this pull request as draft October 17, 2025 16:50
remove all hard coded github.com and replace with parameterised
GPT5 mocking the docs... :)
@richgo richgo marked this pull request as ready for review October 17, 2025 18:59
@danielmeppiel danielmeppiel requested a review from Copilot October 27, 2025 10:48
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds GitHub Enterprise (GHE) hostname support to dependency parsing so apm can correctly detect and normalize repository references hosted on self-managed GitHub instances.

Key Changes:

  • Introduces a new github_host utility module that centralizes GitHub hostname validation and URL construction
  • Updates dependency parsing to accept custom GHE hostnames (e.g., orgname.ghe.com) in addition to github.com
  • Adds environment variable support (GITHUB_HOST, APM_GITHUB_HOSTS) for configuring custom GitHub instances
  • Updates all tests to use the new hostname utilities for consistency

Reviewed Changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/apm_cli/utils/github_host.py New utility module providing hostname validation, URL construction, and token sanitization for GitHub and GHE instances
src/apm_cli/models/apm_package.py Updates DependencyReference to support GHE hostnames, storing host information and using new utilities for validation
src/apm_cli/deps/github_downloader.py Refactors to use github_host utilities for URL construction and token sanitization, adds host tracking for enterprise repos
src/apm_cli/adapters/client/copilot.py Replaces hardcoded hostname allowlist with is_github_hostname() utility
tests/unit/test_github_host.py New test file covering default_host(), is_github_hostname(), and sanitize_token_url_in_message()
tests/test_apm_package_models.py Updates tests to use github_host utilities and adds GHE URL parsing test cases
tests/test_github_downloader_token_precedence.py Updates URL assertions to use github_host utilities for consistency
tests/unit/test_registry_integration.py Updates mock URLs to use default_host() for consistency
tests/unit/test_registry_client.py Updates mock URLs to use default_host() for consistency
CONTRIBUTING.md Updates testing instructions to recommend uv for running tests, adds fallback instructions for standard venv

class DependencyReference:
"""Represents a reference to an APM dependency."""
repo_url: str # e.g., "user/repo" or "github.com/user/repo"
host: Optional[str] = None # Optional host (github.com or enterprise host)
Copy link

Copilot AI Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment describes the host field but doesn't explain when it's None vs when it's populated. Consider clarifying that host is extracted from the parsed dependency string and defaults to default_host() if not explicitly specified.

Suggested change
host: Optional[str] = None # Optional host (github.com or enterprise host)
host: Optional[str] = None # Host (e.g., github.com or enterprise host); extracted from the dependency string if specified, otherwise None. If None, defaults to default_host() when needed.

Copilot uses AI. Check for mistakes.
Comment thread src/apm_cli/models/apm_package.py Outdated
Comment thread src/apm_cli/utils/github_host.py
Comment on lines +198 to +200
# Ensure host is set for enterprise repos
if getattr(dep_ref, 'host', None):
self.github_host = dep_ref.host
Copy link

Copilot AI Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern of checking and setting self.github_host is duplicated three times (lines 199-200, 213-214, 228-229). Consider extracting this into a helper method or setting it once before the conditional blocks to reduce duplication.

Copilot uses AI. Check for mistakes.
Comment thread src/apm_cli/deps/github_downloader.py
danielmeppiel added a commit that referenced this pull request Oct 28, 2025
The runtime manager was calling setup_runtime_environment() without
passing the env dict, which caused it to create a new os.environ.copy()
and lose any tokens set in the CI environment.

This fixes integration test failures where GITHUB_TOKEN and other
secrets configured in GitHub Actions weren't reaching the runtime
setup scripts, causing unauthenticated API requests and rate limiting.

Fixes integration test failures in PR #8 and similar CI environments.
@danielmeppiel
Copy link
Copy Markdown
Collaborator

We will need to document GITHUB_HOST and APM_GITHUB_HOSTS based on src/apm_cli/utils/github_host.py‎ for different use cases and scenarios (github.com, GHE Server, GHE DR Cloud)

@danielmeppiel danielmeppiel merged commit 245a4b3 into microsoft:main Oct 28, 2025
sergio-sisternes-epam added a commit to sergio-sisternes-epam/apm that referenced this pull request Mar 2, 2026
…osoft#8)

- Drop unittest.TestCase from all 6 test classes
- Replace self.assert* with bare assert statements
- Replace tempfile.TemporaryDirectory with tmp_path fixture
- Remove unused imports (tempfile, unittest)
danielmeppiel added a commit that referenced this pull request Mar 31, 2026
- Narrow except Exception to except ImportError for lazy marketplace import (comment #1)
- Fix provenance key mismatch: use dep identity instead of canonical for lockfile lookup (comment #2)
- Include subdir in git-subdir source resolution with path traversal validation (comment #3)
- Include relative path in relative source resolution with traversal validation (comment #4)
- Sanitize marketplace name in cache file paths to prevent path traversal (comment #5)
- Fix docs: stale-if-error, not stale-while-revalidate (comment #6)
- Consolidate CHANGELOG entries into single line with (#503) (comment #7)
- Remove unused _SUPPORTED_SOURCE_TYPES set (comment #8)
- Let auth errors propagate in _auto_detect_path instead of swallowing (comment #9)
- Validate marketplace --name against [a-zA-Z0-9._-]+ charset (comment #10)
- Fix doc examples to use identifier-compatible names (comments #11, #12)
- Update tests to match corrected resolver behavior, add traversal tests

Co-authored-by: Copilot <[email protected]>
danielmeppiel added a commit that referenced this pull request Mar 31, 2026
…covery + governance (#503)

* Initial plan

* Initial plan for marketplace integration

Agent-Logs-Url: https://github.com/microsoft/apm/sessions/12a9b016-7930-41b8-a340-c64f11486b71

Co-authored-by: danielmeppiel <[email protected]>

* feat: marketplace integration core implementation

- Add marketplace/ package: models, errors, registry, client, resolver
- Add marketplace CLI commands: add, list, browse, update, remove, search
- Add lockfile provenance fields: discovered_via, marketplace_plugin_name
- Add install hook for NAME@MARKETPLACE syntax pre-parse intercept
- Wire marketplace commands in cli.py

Agent-Logs-Url: https://github.com/microsoft/apm/sessions/12a9b016-7930-41b8-a340-c64f11486b71

Co-authored-by: danielmeppiel <[email protected]>

* docs: add marketplace integration guide and CLI reference

- Create guides/marketplaces.md covering marketplace concepts,
  registration, browsing, search, install syntax, provenance tracking,
  and cache behavior
- Add apm marketplace and apm search command sections to cli-commands.md
- Update apm install arguments to include NAME@MARKETPLACE syntax
- Update plugins.md Finding Plugins section with marketplace cross-refs

Co-authored-by: danielmeppiel <[email protected]>

* docs: fix marketplace.json format and lockfile field names to match implementation

- Use array-based plugins format matching models.py parser expectations
- Use discovered_via and marketplace_plugin_name matching lockfile.py fields
- Document both Copilot CLI (repository/ref) and Claude Code (source) formats

Co-authored-by: danielmeppiel <[email protected]>

* docs: fix git-subdir and relative source descriptions to match resolver

- git-subdir uses separate repo and subdir fields
- Relative string sources resolve to marketplace repo subdirectory

Co-authored-by: danielmeppiel <[email protected]>

* feat: add marketplace unit tests and docs

- 114 unit tests across 8 test files covering all marketplace modules
- New marketplace guide at docs/src/content/docs/guides/marketplaces.md
- Updated CLI reference with marketplace and search commands
- Updated plugins guide with marketplace integration section
- CHANGELOG entry for marketplace feature

Agent-Logs-Url: https://github.com/microsoft/apm/sessions/12a9b016-7930-41b8-a340-c64f11486b71

Co-authored-by: danielmeppiel <[email protected]>

* refactor: address code review feedback

- Use List[MarketplacePlugin] from typing instead of lowercase generic
- Eliminate duplicated condition in install.py marketplace intercept
- Restructure control flow for clarity

Agent-Logs-Url: https://github.com/microsoft/apm/sessions/12a9b016-7930-41b8-a340-c64f11486b71

Co-authored-by: danielmeppiel <[email protected]>

* fix: address all 12 PR review comments on marketplace integration

- Narrow except Exception to except ImportError for lazy marketplace import (comment #1)
- Fix provenance key mismatch: use dep identity instead of canonical for lockfile lookup (comment #2)
- Include subdir in git-subdir source resolution with path traversal validation (comment #3)
- Include relative path in relative source resolution with traversal validation (comment #4)
- Sanitize marketplace name in cache file paths to prevent path traversal (comment #5)
- Fix docs: stale-if-error, not stale-while-revalidate (comment #6)
- Consolidate CHANGELOG entries into single line with (#503) (comment #7)
- Remove unused _SUPPORTED_SOURCE_TYPES set (comment #8)
- Let auth errors propagate in _auto_detect_path instead of swallowing (comment #9)
- Validate marketplace --name against [a-zA-Z0-9._-]+ charset (comment #10)
- Fix doc examples to use identifier-compatible names (comments #11, #12)
- Update tests to match corrected resolver behavior, add traversal tests

Co-authored-by: Copilot <[email protected]>

* fix: Copilot CLI format compatibility and marketplace provenance bugs

Bug #1 - Format incompatibility with awesome-copilot marketplace:
  - Parser now accepts 'source' key (Copilot CLI) as type discriminator
    fallback when 'type' key is absent, normalizing to 'type' for resolvers
  - GitHub source resolver now accepts 'path' field (Copilot CLI) as
    virtual subdirectory, same as 'subdir' in git-subdir sources
  - Path traversal validation applied to 'path' field
  - Fixes: 8 of 62 plugins in awesome-copilot that use github source
    objects with 'source'+'path' keys instead of 'type'+'subdir'

Bug #2 - Lockfile provenance never written:
  - Root cause: install passed raw marketplace refs (NAME@MARKETPLACE)
    as only_packages, but DependencyReference.parse() can't parse those,
    so identity filtering removed all deps -> 'already installed'
  - Fix: use validated_packages (canonical owner/repo strings) instead
    of raw click argument for only_pkgs

Both bugs verified fixed via E2E tests against real marketplaces:
  - github/awesome-copilot (62 plugins)
  - anthropics/skills (3 plugins)
  - microsoft/azure-skills (1 plugin)

Co-authored-by: Copilot <[email protected]>

* feat: scope marketplace search to QUERY@MARKETPLACE format

Search now requires QUERY@MARKETPLACE (e.g. apm search security@skills)
to eliminate name collisions across marketplaces. Added search_marketplace()
client function for single-marketplace search.

- Rejects bare queries without @ — clear error with usage example
- Validates marketplace exists before searching
- Updated docs/guides/marketplaces.md with new syntax
- 7 test cases: format validation, unknown marketplace, results, no results

Co-authored-by: Copilot <[email protected]>

* docs: update CLI reference and plugins guide for scoped search syntax

Align all documentation with QUERY@MARKETPLACE search format.

Co-authored-by: Copilot <[email protected]>

* refactor: use centralized path_security for marketplace traversal checks

Replace 3 ad-hoc '..' in x.split('/') checks in marketplace/resolver.py
with validate_path_segments() from utils/path_security.py. Add
defense-in-depth validate_path_segments() call to _sanitize_cache_name()
in client.py.

This ensures marketplace code uses the same cross-platform path safety
utilities (backslash normalization, single-dot rejection) as the rest
of APM.

Co-authored-by: Copilot <[email protected]>

* docs: add path safety rule to copilot-instructions.md

Directs contributors to use validate_path_segments() and
ensure_path_within() from utils/path_security.py instead of
ad-hoc traversal checks.

Co-authored-by: Copilot <[email protected]>

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: danielmeppiel <[email protected]>
Co-authored-by: danielmeppiel <[email protected]>
Co-authored-by: Copilot <[email protected]>
danielmeppiel pushed a commit that referenced this pull request Apr 29, 2026
Promotes [Unreleased] -> [0.11.0] - 2026-04-29 and bumps
pyproject.toml + uv.lock to 0.11.0.

Version-bump rationale: 0.11.0 (minor bump) chosen over 0.10.1 because
this release ships one BREAKING removal (`apm marketplace build` -> exits 2,
use `apm pack`) plus several net-new features (Dev Container Feature,
Codex project-scoped MCP, `marketplace:` block in apm.yml, `apm pack`
unification, multi-org `apps[]`). Strict semver in 0.x: minor for
features-with-break, patch only for bugfixes.

Milestone admin (done out-of-band):
- Renamed milestone #8 `0.10.1` -> `0.11.0`
- Created milestone #9 `0.12.0` as next-up bucket
- Moved 43 open items (42 issues + 1 open PR #999) from `0.11.0` -> `0.12.0`
- 6 closed items stay in `0.11.0`

PRs shipping in 0.11.0 (22 commits since v0.10.0):

User-facing features:
- #1042/#722 `apm pack` unifies bundle + marketplace.json
                   (BREAKING: `apm marketplace build` removed)
- #1038       `marketplace:` block in apm.yml + `apm marketplace migrate`
- #803  /#502 Codex project-scoped MCP (`.codex/config.toml`) + user-scope primitives
- #861        Dev Container Feature `ghcr.io/microsoft/apm/apm-cli`
- #982/#984   shared/apm.md `apps:` array for cross-org private packages
- #820        `target:` in apm.yml validates at parse time
- #1032       `apm marketplace add` honors manifest.name (Claude Code parity)
- #1000/#998/#994 unified `--policy` / `--policy-source` accepted forms

User-facing fixes:
- #1015 ADO Entra ID auth + `apm install --update` pre-flight abort
- #1019/#1020 GEMINI.md only created when target requested
- #1008 marketplace producer respects GITHUB_HOST + multi-host URL forms
- #1018 POSIX paths in auto-discovery output (Windows compat)
- #996  drop stray 'specify' from generated file footer

Maintainer tooling:
- #1043 NOTICE.md per CELA template
- #1045/#1044 NOTICE drift gate + license-policy gate in CI
- #1033 shared/apm.md `[a b]` import-input repair (gh-aw#29076 paper-cut)
- #1030 panel workflows skip-don't-fail on unmatched labels; gh-aw v0.71.1
- #1026 shared/apm.md recompiled to apm-action v1.5.0 + bundles-file
- #1022 review-panel: true fan-out + binary verdict + label automation
- #918  complexity audit + benchmarks suite
- #1002 CodeQL clear-text-storage false-positive resolved (token -> placeholder)

Files changed:
- pyproject.toml: 0.10.0 -> 0.11.0
- uv.lock:        regenerated (version field only)
- CHANGELOG.md:   [Unreleased] promoted to [0.11.0] - 2026-04-29

NOTICE drift check passes against the bumped lockfile.

Co-authored-by: Copilot <[email protected]>
danielmeppiel pushed a commit that referenced this pull request Apr 29, 2026
Promotes [Unreleased] -> [0.11.0] - 2026-04-29 and bumps
pyproject.toml + uv.lock to 0.11.0.

Version-bump rationale: 0.11.0 (minor bump) chosen over 0.10.1 because
this release ships one BREAKING removal (`apm marketplace build` -> exits 2,
use `apm pack`) plus several net-new features (Dev Container Feature,
Codex project-scoped MCP, `marketplace:` block in apm.yml, `apm pack`
unification, multi-org `apps[]`). Strict semver in 0.x: minor for
features-with-break, patch only for bugfixes.

Milestone admin (done out-of-band):
- Renamed milestone #8 `0.10.1` -> `0.11.0`
- Created milestone #9 `0.12.0` as next-up bucket
- Moved 43 open items (42 issues + 1 open PR #999) from `0.11.0` -> `0.12.0`
- 6 closed items stay in `0.11.0`

PRs shipping in 0.11.0 (22 commits since v0.10.0):

User-facing features:
- #1042/#722 `apm pack` unifies bundle + marketplace.json
                   (BREAKING: `apm marketplace build` removed)
- #1038       `marketplace:` block in apm.yml + `apm marketplace migrate`
- #803  /#502 Codex project-scoped MCP (`.codex/config.toml`) + user-scope primitives
- #861        Dev Container Feature `ghcr.io/microsoft/apm/apm-cli`
- #982/#984   shared/apm.md `apps:` array for cross-org private packages
- #820        `target:` in apm.yml validates at parse time
- #1032       `apm marketplace add` honors manifest.name (Claude Code parity)
- #1000/#998/#994 unified `--policy` / `--policy-source` accepted forms

User-facing fixes:
- #1015 ADO Entra ID auth + `apm install --update` pre-flight abort
- #1019/#1020 GEMINI.md only created when target requested
- #1008 marketplace producer respects GITHUB_HOST + multi-host URL forms
- #1018 POSIX paths in auto-discovery output (Windows compat)
- #996  drop stray 'specify' from generated file footer

Maintainer tooling:
- #1043 NOTICE.md per CELA template
- #1045/#1044 NOTICE drift gate + license-policy gate in CI
- #1033 shared/apm.md `[a b]` import-input repair (gh-aw#29076 paper-cut)
- #1030 panel workflows skip-don't-fail on unmatched labels; gh-aw v0.71.1
- #1026 shared/apm.md recompiled to apm-action v1.5.0 + bundles-file
- #1022 review-panel: true fan-out + binary verdict + label automation
- #918  complexity audit + benchmarks suite
- #1002 CodeQL clear-text-storage false-positive resolved (token -> placeholder)

Files changed:
- pyproject.toml: 0.10.0 -> 0.11.0
- uv.lock:        regenerated (version field only)
- CHANGELOG.md:   [Unreleased] promoted to [0.11.0] - 2026-04-29

NOTICE drift check passes against the bumped lockfile.

Co-authored-by: Copilot <[email protected]>
danielmeppiel added a commit that referenced this pull request Apr 29, 2026
* chore(release): cut 0.11.0

Promotes [Unreleased] -> [0.11.0] - 2026-04-29 and bumps
pyproject.toml + uv.lock to 0.11.0.

Version-bump rationale: 0.11.0 (minor bump) chosen over 0.10.1 because
this release ships one BREAKING removal (`apm marketplace build` -> exits 2,
use `apm pack`) plus several net-new features (Dev Container Feature,
Codex project-scoped MCP, `marketplace:` block in apm.yml, `apm pack`
unification, multi-org `apps[]`). Strict semver in 0.x: minor for
features-with-break, patch only for bugfixes.

Milestone admin (done out-of-band):
- Renamed milestone #8 `0.10.1` -> `0.11.0`
- Created milestone #9 `0.12.0` as next-up bucket
- Moved 43 open items (42 issues + 1 open PR #999) from `0.11.0` -> `0.12.0`
- 6 closed items stay in `0.11.0`

PRs shipping in 0.11.0 (22 commits since v0.10.0):

User-facing features:
- #1042/#722 `apm pack` unifies bundle + marketplace.json
                   (BREAKING: `apm marketplace build` removed)
- #1038       `marketplace:` block in apm.yml + `apm marketplace migrate`
- #803  /#502 Codex project-scoped MCP (`.codex/config.toml`) + user-scope primitives
- #861        Dev Container Feature `ghcr.io/microsoft/apm/apm-cli`
- #982/#984   shared/apm.md `apps:` array for cross-org private packages
- #820        `target:` in apm.yml validates at parse time
- #1032       `apm marketplace add` honors manifest.name (Claude Code parity)
- #1000/#998/#994 unified `--policy` / `--policy-source` accepted forms

User-facing fixes:
- #1015 ADO Entra ID auth + `apm install --update` pre-flight abort
- #1019/#1020 GEMINI.md only created when target requested
- #1008 marketplace producer respects GITHUB_HOST + multi-host URL forms
- #1018 POSIX paths in auto-discovery output (Windows compat)
- #996  drop stray 'specify' from generated file footer

Maintainer tooling:
- #1043 NOTICE.md per CELA template
- #1045/#1044 NOTICE drift gate + license-policy gate in CI
- #1033 shared/apm.md `[a b]` import-input repair (gh-aw#29076 paper-cut)
- #1030 panel workflows skip-don't-fail on unmatched labels; gh-aw v0.71.1
- #1026 shared/apm.md recompiled to apm-action v1.5.0 + bundles-file
- #1022 review-panel: true fan-out + binary verdict + label automation
- #918  complexity audit + benchmarks suite
- #1002 CodeQL clear-text-storage false-positive resolved (token -> placeholder)

Files changed:
- pyproject.toml: 0.10.0 -> 0.11.0
- uv.lock:        regenerated (version field only)
- CHANGELOG.md:   [Unreleased] promoted to [0.11.0] - 2026-04-29

NOTICE drift check passes against the bumped lockfile.

Co-authored-by: Copilot <[email protected]>

* chore(changelog): tighten 0.11.0 entries to lead with user impact

Co-authored-by: Copilot <[email protected]>

* chore(changelog): move Dev Container Feature to Maintainer tooling (not yet published)

Co-authored-by: Copilot <[email protected]>

* chore(changelog): de-dupe within 0.11.0 (combine #722 Removed bullets, drop #820 Fixed pointer)

Co-authored-by: Copilot <[email protected]>

---------

Co-authored-by: Copilot <[email protected]>
danielmeppiel added a commit that referenced this pull request Apr 30, 2026
…#1073)

* docs(notice): rename NOTICE.md -> NOTICE; add CLA third-party section

Two changes, one file rename:

1. Rename NOTICE.md -> NOTICE, matching the Apache / CNCF convention used
   by upstream third-party-attribution files (kubernetes-sigs/kro,
   kubernetes-sigs/headlamp, etc.). The .md extension was non-idiomatic
   for a generated legal artifact -- NOTICE files are read by tooling
   (license scanners, SBOM generators) that match on the bare filename.
   Generator (scripts/generate-notice.py), Makefile target, and the
   NOTICE Drift Check workflow are all updated to operate on the
   extension-less path.

2. Add a 'Submitted on behalf of a third-party' section to NOTICE,
   crediting five contributors whose pull requests landed before the
   microsoft-github-policy-service CLA bot recorded a signature on
   file. The repo transferred from danielmeppiel/awd-cli to the
   microsoft org; some early PRs predate CLA enforcement, and we
   could not retroactively reach all contributors. Mirrors section 7
   of common CLA texts (the wording adopted by CNCF NOTICE files).

   Driven by a new _third_party_submissions block in
   scripts/notice-metadata.yaml -- legally-significant wording stays
   alongside the per-component data, not buried in code.

   Contributors named (verified via Check Runs API against the
   microsoft-github-policy-service app, license/cla check on every
   merged PR by each suspected author):
     - @pofallon  (PR #4)
     - @richgo    (PRs #8, #25, #26, #33, #34)
     - @ryanfk    (PR #92 -- bot ran with conclusion=null,
                  output: 'Contributor License Agreement is not agreed yet.')
     - @foutoucour (PR #108)
     - @Jah-yee   (PR #184)

   Listed contributors who later sign the CLA (or who were signed
   under a different GitHub account at the time) can request removal
   via issue.

Co-authored-by: Copilot <[email protected]>

* docs(notice): trim third-party section preamble

Strip the historical/CNCF-citation paragraph and the verbatim CLA-section-7
quote. Keep only the active sentence (what the listing means + how to
request removal).

Co-authored-by: Copilot <[email protected]>

* docs(notice): address PR #1073 review

Three fixes from copilot-pull-request-reviewer:

1. Drop spurious leading '---' separator in the third-party-submissions
   renderer. render_component already ends each component with '---\n\n',
   so prepending another '---' produced two consecutive separators in
   NOTICE. Verified: separator count dropped from 17 to 16.

2. Sweep stale 'NOTICE.md' references in scripts/generate-notice.py
   (top-level docstring, Modes section, ComponentMeta and DepSpec field
   docstrings). The constant was renamed; the docs lagged.

3. Append (#1073) PR refs to both CHANGELOG entries; ASCII-correct the
   arrow ('->' instead of '->').

Co-authored-by: Copilot <[email protected]>

---------

Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
danielmeppiel pushed a commit to stbenjam/apm that referenced this pull request Apr 30, 2026
…e-commands

Address the 5 required findings from the APM Review Panel on PR microsoft#1046
(round 2) by replacing the hardcoded cursor branch and the warn()-only
notification with a unified consent gate.

Changes:
- targets.py: add TargetProfile.requires_executable_consent (default
  False); set True on the cursor entry only. Documents the asymmetric-
  consent risk for claude/opencode/gemini in the docstring (panel
  finding microsoft#5: deferred to avoid breaking existing installs).
- command_integrator.py: introduce should_deploy_executable_commands()
  helper. integrate_commands_for_target now accepts
  allow_executable_commands and skips deployment with a warn-level
  diagnostic naming the exact CLI flag when consent is required but
  absent. Replaces the cursor-branded warn() block (Threat microsoft#8: warn
  is notification, not consent).
- install.py: add --allow-executable-commands click option and field
  on the local InstallContext; thread to _install_apm_dependencies
  and through InstallRequest -> InstallService -> run_install_pipeline.
- services.py / pipeline.py / context.py / template.py / phases:
  plumb allow_executable_commands through the install pipeline. The
  dispatch loop only forwards the kwarg to the commands integrator.

Tests:
- New TestExecutableConsentGate class covering: helper short-circuit
  for non-consent targets, helper block/allow on cursor, gated-skip
  result + warning when flag absent, deploy when flag present, and
  parametrized regression that claude/opencode/gemini are unaffected.
- Existing cursor deployment tests updated to pass
  allow_executable_commands=True.
- test_install_context.py: include allow_executable_commands in the
  expected fields tuple.

Docs:
- cli-commands.md and packages/apm-guide commands.md: document the new
  flag and the cursor opt-in.
- CHANGELOG.md: reframe the cursor entry around the user-visible
  opt-in flag and Threat microsoft#8 rationale.

Closes panel findings 1, 3, 4 from PR microsoft#1046 round 2. Finding 5
(asymmetric consent across slash-command targets) is documented in
the TargetProfile docstring as deferred -- flipping the flag for
claude/opencode/gemini would silently break existing installs.

Co-authored-by: Copilot <[email protected]>
danielmeppiel pushed a commit to stbenjam/apm that referenced this pull request Apr 30, 2026
…efault, --allow-cursor-commands

Round 4 panel raised 8 required findings against the cursor-commands
gate. Round 5 addresses all of them:

Architecture (python-architect)
- Add frozen IntegrateOptions dataclass with extra_kwargs(prim) helper
  in install/services.py. Dispatch loop is now branchless — no more
  inline 'if _prim_name == "commands"' special case.
- Hoist pkg_name once at top of integrate_commands_for_target.
- Move executable-commands gate before find_prompt_files in the happy
  path; keep one find call inside the gate-skipped branch so the
  user-facing skip warning includes an exact count.

CLI logging (cli-logging-expert)
- _post_install_summary surfaces the consent flag in --verbose output:
  '  allow-cursor-commands: true|false' (gated on logger.verbose).
- Add _should_emit_passthrough_notice helper + _passthrough_notified
  instance set on CommandIntegrator. One-shot info diagnostic fires
  once per (install, target) on Cursor installs explaining that
  Cursor command files keep the Claude-compatible frontmatter subset
  intentionally for cross-tool compatibility.
- Combine missing-.cursor/-dir hint with consent-flag mention so users
  see both signals in one diagnostic.

DevX UX (devx-ux-expert)
- Rename CLI flag --allow-executable-commands -> --allow-cursor-commands
  (target-scoped, user-friendly). Internal pipeline parameter remains
  allow_executable_commands so future targets can share the gate
  plumbing via their own --allow-<target>-commands flags. Mapping
  happens at the CLI boundary in install().
- Update skip warning to name the new flag.

Supply-chain security (supply-chain-security-expert)
- Flip TargetProfile.requires_executable_consent default to True at
  the dataclass level. New targets now refuse command deployment unless
  an opt-out is explicit. Add explicit =False with rationale comment
  on each existing target (claude, opencode, gemini), each referencing
  enterprise/security.md.
- Add new '## Post-install code execution (Threat microsoft#8)' section to
  docs/src/content/docs/enterprise/security.md with the per-target
  posture matrix (cursor=gated, claude/opencode/gemini=opt-out),
  rationale for the asymmetry, npm --ignore-scripts mental model,
  and future-targets guidance.
- Remove dead bool() cast in should_deploy_executable_commands.
- Remove inline TODO comments referencing PR URL.

OSS growth (oss-growth-hacker)
- Rewrite CHANGELOG entry to lead with user outcome ('Cursor users
  can install APM package slash commands with one flag…'). Security
  rationale moved to end. Cursor 1.6+ lifecycle note included inline.
- Add Cursor 1.6+ lifecycle caveat to:
  - docs/.../integrations/ide-tool-integration.md
  - docs/.../reference/cli-commands.md
  - packages/apm-guide/.apm/skills/apm-usage/commands.md
  - packages/apm-guide/.apm/skills/apm-usage/package-authoring.md

Tests
- Update test assertions to use new flag name.
- Add requires_executable_consent=False to the synthetic test profile
  in tests/unit/integration/test_data_driven_dispatch.py (consequence
  of fail-closed default).
- Full unit suite: 6924 passed.

Round 5 panel: 6/6 specialists APPROVE; CEO synthesizer APPROVE.

Co-authored-by: Copilot <[email protected]>
danielmeppiel pushed a commit to stbenjam/apm that referenced this pull request Apr 30, 2026
…fault

Cursor slash commands have identical invocation semantics to
Claude/OpenCode/Gemini -- the user must type /cmdname to invoke. They
do not auto-execute on disk-write or IDE startup, so the npm
--ignore-scripts / Threat microsoft#8 framing was incorrect. The gate created
artificial asymmetry between targets with identical UX and violated
portable-by-manifest.

Removed:
- --allow-cursor-commands CLI flag
- TargetProfile.requires_executable_consent field
- should_deploy_executable_commands() helper + gate block
- IntegrateOptions per-primitive routing (only consumer was the gate)
- allow_executable_commands plumbing through 7 layers
- Threat microsoft#8 framing from docs + CHANGELOG

Kept:
- Cursor commands PrimitiveMapping (the actual feature)
- Lossy-frontmatter diagnostics.warn() per file (real value: surfaces
  dropped Cursor-specific keys to package authors)
- Cursor 1.6+ lifecycle note (Cursor de-emphasizing commands)

Co-authored-by: Copilot <[email protected]>
danielmeppiel added a commit that referenced this pull request May 3, 2026
* feat(cursor): add slash command support for Cursor 1.6+

Register the "commands" primitive in the Cursor target profile so
apm install deploys .prompt.md files to .cursor/commands/*.md
when a .cursor/ directory is present.

Cursor 1.6 introduced custom slash commands [1] stored as plain
Markdown in .cursor/commands/. Cursor has since de-emphasized
commands in favor of skills/rules [2], but as long as it supports
the commands surface it makes sense to deploy to it.

Cursor itself does not consume YAML frontmatter, but we
deliberately emit Claude-compatible frontmatter (description,
allowed-tools, arguments, argument-hint) rather than stripping it.
The frontmatter is harmless to Cursor -- the file is just a
context-to-LLM routing mechanism -- and in practice, leaving
structured input hints in the file makes the commands noticeably
more successful at inferring required inputs from the user.

This reuses the existing integrate_command() path (the else branch
in CommandIntegrator.integrate_commands_for_target), so the only
production code change is three lines in targets.py.

Testing strategy:
- Unit tests: opt-in guard, single/multi deploy, frontmatter
  preservation, sync removal, missing-dir graceful no-op
- Integration tests: target gating dispatch (mocked integrators)
  and full end-to-end through integrate_package_primitives with
  real integrators verifying file output

[1] https://cursor.com/changelog/1-6
[2] https://cursor.com/docs/plugins

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(cursor): address review feedback on command integration

- Docs: change "frontmatter preserved" to "normalized to supported
  command frontmatter" -- the shared command transformer keeps only
  description, allowed-tools, model, and argument-hint, dropping
  unknown keys like author or custom fields
- Tests: rename test_frontmatter_preserved to
  test_frontmatter_normalized_to_supported_subset and assert that
  non-whitelisted fields (author, custom-field) are dropped
- Tests: fix test_sync_removes_apm_commands to use manifest-based
  sync (managed_files set) instead of the legacy *-apm.md glob
  fallback, matching how Cursor commands are actually deployed

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* style: ruff import organization in cursor command tests

Co-authored-by: Copilot <[email protected]>

* test/integration: address Copilot review on Cursor commands (#1046)

- targets.py: switch Cursor commands format_id from cursor_command to
  claude_command and document why. The shared command transformer is
  what actually runs today; the previous tag mis-advertised a Cursor-
  specific writer that does not exist and would silently lie about
  preserving Cursor-only frontmatter (author/input/mcp/parameters).
  Switch back to a dedicated cursor_command tag only when an integrator
  branch is added that preserves full Cursor metadata verbatim.
- test_command_integrator.py: drop redundant per-test reimports of
  shutil/tempfile (PEP 8 nit, both are imported at module top).

Other Copilot comments were already covered:
- docs already say 'normalized to supported command frontmatter'
- test_sync_removes_managed_commands exercises Cursor manifest mode
- test_frontmatter_normalized_to_supported_subset asserts non-whitelisted
  fields (author, custom-field) are dropped.

Co-authored-by: Copilot <[email protected]>

* style: ruff format test_command_integrator.py

Co-authored-by: Copilot <[email protected]>

* fix(cursor-commands): warn on dropped frontmatter, traversal guards, target-aware diagnostics

Address all 10 required findings from APM Review Panel on PR #1046:

- Extract _make_package() to module-level test helper (DRY across 6 classes)
- Make integrate_command() target-aware via target_name kwarg; emit
  'command arguments' instead of Claude-branded message for Cursor installs
- Detect frontmatter keys not preserved by the shared claude_command
  transformer (author, mcp, parameters, ...) and surface diagnostics.warn()
  per file so users see the lossy transform at install time
- Add install-time info note when .cursor/ is missing instead of silently
  skipping deployment (improves CI-vs-dev discoverability)
- Validate base_name with validate_path_segments() and ensure_path_within()
  before joining into commands_dir to close path-traversal vector via
  malicious package filenames
- Harden find_files_by_glob() to reject hardlinks/files whose resolved path
  escapes package_path (symlink check did not catch hardlinks)
- Surface a one-time IDE consent warning per package when Cursor commands
  are deployed since they become invokable as IDE slash commands
- Add TODO(cursor-command-format) with tracking issue URL on both targets.py
  and integrate_command() to make the shared-transformer debt trackable
- Add commands_cursor identity entry to _BUCKET_ALIASES for explicit
  documentation parity with commands_opencode
- CHANGELOG entry under [Unreleased] and ide-tool-integration.md note
  about the frontmatter subset limitation
- New TestCursorCommandPanelFindings regression class covers all five
  panel-driven behaviours (dropped-key warn, traversal rejection,
  target-aware diagnostics, IDE consent, skip note)

Verified: 6939 unit tests pass; ruff format + ruff check clean.

Co-authored-by: Copilot <[email protected]>

* fix(install): gate cursor command deployment behind --allow-executable-commands

Address the 5 required findings from the APM Review Panel on PR #1046
(round 2) by replacing the hardcoded cursor branch and the warn()-only
notification with a unified consent gate.

Changes:
- targets.py: add TargetProfile.requires_executable_consent (default
  False); set True on the cursor entry only. Documents the asymmetric-
  consent risk for claude/opencode/gemini in the docstring (panel
  finding #5: deferred to avoid breaking existing installs).
- command_integrator.py: introduce should_deploy_executable_commands()
  helper. integrate_commands_for_target now accepts
  allow_executable_commands and skips deployment with a warn-level
  diagnostic naming the exact CLI flag when consent is required but
  absent. Replaces the cursor-branded warn() block (Threat #8: warn
  is notification, not consent).
- install.py: add --allow-executable-commands click option and field
  on the local InstallContext; thread to _install_apm_dependencies
  and through InstallRequest -> InstallService -> run_install_pipeline.
- services.py / pipeline.py / context.py / template.py / phases:
  plumb allow_executable_commands through the install pipeline. The
  dispatch loop only forwards the kwarg to the commands integrator.

Tests:
- New TestExecutableConsentGate class covering: helper short-circuit
  for non-consent targets, helper block/allow on cursor, gated-skip
  result + warning when flag absent, deploy when flag present, and
  parametrized regression that claude/opencode/gemini are unaffected.
- Existing cursor deployment tests updated to pass
  allow_executable_commands=True.
- test_install_context.py: include allow_executable_commands in the
  expected fields tuple.

Docs:
- cli-commands.md and packages/apm-guide commands.md: document the new
  flag and the cursor opt-in.
- CHANGELOG.md: reframe the cursor entry around the user-visible
  opt-in flag and Threat #8 rationale.

Closes panel findings 1, 3, 4 from PR #1046 round 2. Finding 5
(asymmetric consent across slash-command targets) is documented in
the TargetProfile docstring as deferred -- flipping the flag for
claude/opencode/gemini would silently break existing installs.

Co-authored-by: Copilot <[email protected]>

* fix(install): address round 4 panel — IntegrateOptions, fail-closed default, --allow-cursor-commands

Round 4 panel raised 8 required findings against the cursor-commands
gate. Round 5 addresses all of them:

Architecture (python-architect)
- Add frozen IntegrateOptions dataclass with extra_kwargs(prim) helper
  in install/services.py. Dispatch loop is now branchless — no more
  inline 'if _prim_name == "commands"' special case.
- Hoist pkg_name once at top of integrate_commands_for_target.
- Move executable-commands gate before find_prompt_files in the happy
  path; keep one find call inside the gate-skipped branch so the
  user-facing skip warning includes an exact count.

CLI logging (cli-logging-expert)
- _post_install_summary surfaces the consent flag in --verbose output:
  '  allow-cursor-commands: true|false' (gated on logger.verbose).
- Add _should_emit_passthrough_notice helper + _passthrough_notified
  instance set on CommandIntegrator. One-shot info diagnostic fires
  once per (install, target) on Cursor installs explaining that
  Cursor command files keep the Claude-compatible frontmatter subset
  intentionally for cross-tool compatibility.
- Combine missing-.cursor/-dir hint with consent-flag mention so users
  see both signals in one diagnostic.

DevX UX (devx-ux-expert)
- Rename CLI flag --allow-executable-commands -> --allow-cursor-commands
  (target-scoped, user-friendly). Internal pipeline parameter remains
  allow_executable_commands so future targets can share the gate
  plumbing via their own --allow-<target>-commands flags. Mapping
  happens at the CLI boundary in install().
- Update skip warning to name the new flag.

Supply-chain security (supply-chain-security-expert)
- Flip TargetProfile.requires_executable_consent default to True at
  the dataclass level. New targets now refuse command deployment unless
  an opt-out is explicit. Add explicit =False with rationale comment
  on each existing target (claude, opencode, gemini), each referencing
  enterprise/security.md.
- Add new '## Post-install code execution (Threat #8)' section to
  docs/src/content/docs/enterprise/security.md with the per-target
  posture matrix (cursor=gated, claude/opencode/gemini=opt-out),
  rationale for the asymmetry, npm --ignore-scripts mental model,
  and future-targets guidance.
- Remove dead bool() cast in should_deploy_executable_commands.
- Remove inline TODO comments referencing PR URL.

OSS growth (oss-growth-hacker)
- Rewrite CHANGELOG entry to lead with user outcome ('Cursor users
  can install APM package slash commands with one flag…'). Security
  rationale moved to end. Cursor 1.6+ lifecycle note included inline.
- Add Cursor 1.6+ lifecycle caveat to:
  - docs/.../integrations/ide-tool-integration.md
  - docs/.../reference/cli-commands.md
  - packages/apm-guide/.apm/skills/apm-usage/commands.md
  - packages/apm-guide/.apm/skills/apm-usage/package-authoring.md

Tests
- Update test assertions to use new flag name.
- Add requires_executable_consent=False to the synthetic test profile
  in tests/unit/integration/test_data_driven_dispatch.py (consequence
  of fail-closed default).
- Full unit suite: 6924 passed.

Round 5 panel: 6/6 specialists APPROVE; CEO synthesizer APPROVE.

Co-authored-by: Copilot <[email protected]>

* fix(cursor): drop --allow-cursor-commands gate, deploy commands by default

Cursor slash commands have identical invocation semantics to
Claude/OpenCode/Gemini -- the user must type /cmdname to invoke. They
do not auto-execute on disk-write or IDE startup, so the npm
--ignore-scripts / Threat #8 framing was incorrect. The gate created
artificial asymmetry between targets with identical UX and violated
portable-by-manifest.

Removed:
- --allow-cursor-commands CLI flag
- TargetProfile.requires_executable_consent field
- should_deploy_executable_commands() helper + gate block
- IntegrateOptions per-primitive routing (only consumer was the gate)
- allow_executable_commands plumbing through 7 layers
- Threat #8 framing from docs + CHANGELOG

Kept:
- Cursor commands PrimitiveMapping (the actual feature)
- Lossy-frontmatter diagnostics.warn() per file (real value: surfaces
  dropped Cursor-specific keys to package authors)
- Cursor 1.6+ lifecycle note (Cursor de-emphasizing commands)

Co-authored-by: Copilot <[email protected]>

* fix(cursor-commands): act on PR #1046 panel review

Apply the actionable findings from the apm-review-panel critique on
PR #1046:

* docs: fix `.opencode/command/*.md` -> `.opencode/commands/*.md`
  typo in the security guide (factual error in IDE integration map).

* CHANGELOG: rewrite Cursor entry user-first; lifecycle caveat moves
  to a trailing sentence so the value prop reads first.

* integrator: gate the one-shot 'cross-tool compatibility' notice on
  whether at least one file in the batch had dropped frontmatter
  keys.  Previously fired on every Cursor install -> noise on the
  happy path.  Cursor installs whose prompts only use the cross-tool
  subset now emit zero notice.  Other targets (claude, opencode)
  unaffected.

* integrator: rewrite the dropped-keys warn message to drop internal
  jargon ('shared command transformer'); use target-name framing
  ('not supported for cursor commands') and surface the canonical
  kebab-case key list (no camelCase aliases).

* integrator: upgrade post-transform scan_text from WARN_POLICY to
  BLOCK_POLICY.  When a critical finding is detected (e.g. a hidden
  tag char introduced by link resolution), skip the write and account
  for it in result.files_skipped.  Pre-install BLOCK gate already
  scans source files, so this is defense-in-depth for the
  transform-introduced edge case; no regression for legitimate
  packages.

* base_integrator: actually close the hardlink containment gap in
  find_files_by_glob.  The prior is_relative_to() check did NOT
  catch hardlinks -- a hardlink resolves to its own path inside the
  package root.  Add an st_nlink > 1 reject to prevent a malicious
  package shipping a hardlink to (e.g.) /etc/passwd from being read
  via integration.  Update the inline comment to be accurate about
  what each guard actually does.

Tests:

* tests/unit/integration/test_base_integrator.py: new
  test_hardlink_escaping_package_root_is_excluded covering the
  st_nlink reject; cleaned up F821 noqa via real pytest import.

* tests/unit/integration/test_command_integrator.py: 4 new tests in
  TestCursorCommandPanelFindings:
  - test_passthrough_notice_suppressed_on_clean_install
  - test_passthrough_notice_emitted_when_any_file_drops_keys
  - test_dropped_keys_warn_uses_user_facing_wording
  - test_critical_security_finding_blocks_write
  Updated the existing test_dropped_frontmatter_keys_warn assertion
  to match the new wording.

* tests/integration/test_marketplace_plugin_integration.py: 2 new
  integration tests locking in cursor 1.6+ command deployment via
  the install pipeline (deploy when .cursor/ exists; respect opt-in
  by NOT creating .cursor/ when missing).

Verification:
  uv run --extra dev ruff check src/ tests/        -> All checks passed!
  uv run --extra dev ruff format --check src/ tests/ -> 627 files OK
  uv run --extra dev pytest tests/unit ...         -> 7222 passed

Refs: #1046 (comment)

Co-authored-by: Copilot <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Daniel Meppiel <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants