Skip to content

feat(skills): support package namespaces for skills (+ doc accuracy fixes) - #2649

Open
Sergio Sisternes (sergio-sisternes-epam) wants to merge 21 commits into
mainfrom
fix/namespace-doc-accuracy
Open

feat(skills): support package namespaces for skills (+ doc accuracy fixes)#2649
Sergio Sisternes (sergio-sisternes-epam) wants to merge 21 commits into
mainfrom
fix/namespace-doc-accuracy

Conversation

@sergio-sisternes-epam

@sergio-sisternes-epam Sergio Sisternes (sergio-sisternes-epam) commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

TL;DR

This PR supersedes #1028. It carries the full namespace feature from @shreejaykurhade plus two blocking doc accuracy fixes identified by the advisory panel review.

Problem

PR #1028 introduced opt-in package namespaces for skill deployment but had two blocking-severity doc accuracy errors:

  1. docs/src/content/docs/guides/skills.md -- before/after trees and description paragraph referenced .github/skills/ (legacy path). The default routing for converged clients is .agents/skills/.
  2. packages/apm-guide/.apm/skills/apm-usage/package-authoring.md -- described namespace layout as skills/<namespace>/<skill-name>/ (nested subdirectory). The implementation deploys flat: skills/<namespace>-<skill-name>/ (direct child of skills/ root).

Changes

All changes from #1028 (namespace feature, tests, docs) plus:

  • docs/src/content/docs/guides/skills.md: updated description paragraph and before/after directory trees to show .agents/skills/ as default target.
  • packages/apm-guide/.apm/skills/apm-usage/package-authoring.md: corrected all three path references from nested skills/<ns>/<name>/ to flat skills/<ns>-<name>/; updated install-output example to match actual format.

Related

Validation

  • uv run --extra dev ruff check src/ tests/ && uv run --extra dev ruff format --check src/ tests/ -- passes clean

Add an optional namespace field to apm.yml, deploy package-owned
skills and promoted sub-skills under skills/<namespace>/<skill-name>,
and persist the namespace in apm.lock.yaml.

Also document the namespace schema and add coverage for validation,
namespaced deployment, lockfile round-tripping, and lockfile assembly.
…ge-namespaces

# Conflicts:
#	src/apm_cli/install/phases/lockfile.py
#	src/apm_cli/integration/skill_integrator.py
#	src/apm_cli/models/apm_package.py
Resolve conflicts: keep namespace fields and modern PEP 604 union syntax;
align skill_integrator collision tracking with namespace-aware owner keys.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
_build_ownership_maps now keys by skill identity (path under skills/),
not just leaf SKILL.md, so the test fixture's expected key changes from
'SKILL.md' to 'remote-skill/SKILL.md'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
 round 2)

Folds 8 panel-required findings from the round-2 verdict. Code beneath
namespace routing was sound but the feature was invisible at every CLI
and docs surface. This commit closes that visibility gap.

- CHANGELOG [Unreleased]: add namespace feature entry and BREAKING
  entry for the removal of .github/copilot-instructions.md generation
  (findings 3, 7, 8).
- skill_integrator: emit verbose_detail per skill when namespace
  routing changes the deploy path (finding 1).
- services (install summary): preserve the namespace segment in the
  tree-output label and add per-skill confirmation 'skill ns/name
  integrated -> .github/skills/ns/' so namespaced installs are visible
  without --verbose (findings 2, 4).
- templates/hello-world/apm.yml: commented namespace field for
  discoverability via 'apm init' (finding 5).
- tests/unit/install/test_mcp_warnings.py: restored from origin/main;
  the file was deleted on this branch with no replacement, regressing
  SSRF and shell-metachar coverage on the MCP trust surface (finding 6).
- migration.md: note that APM no longer writes
  .github/copilot-instructions.md so the 'leaves untouched' contract
  remains accurate (finding 8).
- packages/apm-guide/.apm/skills/apm-usage/package-authoring.md:
  document the namespace field and the user-visible install signals
  (Rule 4 alignment).

Regression tests:
- verbose log surfaces namespace and is silent when absent
- IntegrationResult.target_paths preserves the namespace segment
- install tree label preserves namespace and per-skill ns/name label

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Address all 3 required findings from PR #1028 round 3 review panel:

1. supply-chain-security: add runtime ensure_path_within containment
   guard inside _skill_dir (skill_integrator.py:163). Parse-time regex
   blocks traversal in namespace values, but a symlink planted at
   skills/<namespace> by a prior malicious install would otherwise let
   shutil.copytree write outside target_skills_root. Guard covers both
   namespaced and flat branches at the single chokepoint.

2. cli-logging: normalise capital-S in namespaced per-skill confirmation
   message (services.py:266) so namespaced and non-namespaced events
   share identical casing.

3. cli-logging: drop the redundant '(namespace: ...)' suffix from the
   sub-skills integrated message (services.py:272-274). The target path
   already encodes the namespace; the parenthetical was pure duplication.

Adds TestSkillDirContainmentGuard regression tests (3) exercising the
exact symlink-planted attack vector. Full unit suite (6904 tests) green.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- guides/skills.md: before/after trees showed .github/skills/ (legacy
  path); change to .agents/skills/ which is the default routing for
  converged clients (Copilot, Cursor, OpenCode, Codex, Gemini).
- package-authoring.md: described nested skills/<namespace>/<skill-name>/
  layout but implementation uses flat skills/<namespace>-<skill-name>/
  (direct child of skills root). Fix all three references and update the
  install-output example to match actual format.

Addresses blocking findings from APM review panel (PR #1028).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 21, 2026 11:01
@sergio-sisternes-epam Sergio Sisternes (sergio-sisternes-epam) changed the title docs(namespace): fix blocking doc accuracy errors in namespace skill paths feat(skills): support package namespaces for skills (+ doc accuracy fixes) Aug 21, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 updates APM’s “package namespaces” feature end-to-end (manifest parsing, skill deployment routing, lockfile persistence, install output, and docs) so that namespaced skills deploy as direct children of the target skills/ root using the flat <namespace>-<skill-name> directory format.

Changes:

  • Add/propagate namespace: in apm.yml through package models, install context, lockfile serialization, and apm init --namespace.
  • Update skill integration to deploy namespaced skills as skills/<namespace>-<skill-name>/ and surface <namespace>/<skill-name> identity in install output.
  • Update docs/specs/templates and extend unit/integration tests to cover namespace behavior and security guards.

Reviewed changes

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

Show a summary per file
File Description
tests/unit/test_self_entry_caller_guards.py Updates ownership-map expectations for deployed skill keying.
tests/unit/test_init_command.py Adds apm init --namespace tests and updates interactive input sequences.
tests/unit/integration/test_skill_integrator.py Adds namespace deployment tests and a containment-guard regression suite.
tests/unit/install/test_services.py Adds install-tree output regression tests for namespaced skill labels.
tests/unit/install/test_mcp_warnings.py Refactors tests (logger setup + comment cleanup) while preserving URL-parse assertions.
tests/test_lockfile.py Adds lockfile namespace round-trip + builder attachment tests.
tests/test_apm_package_models.py Adds manifest parsing/validation tests for namespace:.
templates/hello-world/apm.yml Documents the optional namespace: field in the template.
src/apm_cli/models/apm_package.py Implements validate_namespace() and parses namespace: from apm.yml.
src/apm_cli/integration/skill_integrator.py Routes deployment to namespace-skill dirs, rewrites SKILL frontmatter name, and adjusts ownership key logic.
src/apm_cli/install/sources.py Captures package namespaces into install context for later lockfile assembly.
src/apm_cli/install/services.py Updates install summary labeling/collapse logic to show namespace identity.
src/apm_cli/install/phases/lockfile.py Attaches recorded namespaces into lockfile dependencies during assembly.
src/apm_cli/install/context.py Adds package_namespaces to install context state.
src/apm_cli/drift.py Hardens drift replay override logic with explicit attribute extraction/typing checks.
src/apm_cli/deps/lockfile.py Adds namespace field to LockedDependency with validation-on-load and conditional serialization.
src/apm_cli/commands/init.py Adds --namespace option and writes namespace: to generated apm.yml in --yes mode.
src/apm_cli/commands/_helpers.py Ensures minimal manifest writer includes namespace when present.
packages/apm-guide/.apm/skills/apm-usage/package-authoring.md Documents the flat skills/<namespace>-<skill-name>/ layout and install output format.
docs/src/content/docs/reference/manifest-schema.md Adds namespace to manifest schema docs with pattern/semantics.
docs/src/content/docs/reference/lockfile-spec.md Documents optional namespace in lockfile dependency entries.
docs/src/content/docs/reference/cli-commands.md Documents apm init --namespace (but currently overstates interactive prompting behavior).
docs/src/content/docs/guides/skills.md Adds a “Package Namespaces” section and updates directory layout examples.
docs/src/content/docs/getting-started/migration.md Adds guidance for the unreleased compile --target vscode/all behavior change.
CHANGELOG.md Adds changelog entries for namespaces and a breaking compile output change.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +669 to +672
target = _skill_dir(target_skills_root, sub_name, namespace)
deployed_name = _deployed_skill_name(sub_name, namespace)
owner_key = _skill_owner_key(sub_name, namespace)
rel_path = f"{rel_prefix}/{owner_key}"
owner_key = _skill_owner_key(sub_name, namespace)
rel_path = f"{rel_prefix}/{owner_key}"
if target.exists():
# Content-identical → skip entirely (no copy, no warning)


# =============================================================================
# Path-containment guard (PR #1028 round 4 — supply-chain-security finding)
Comment on lines 68 to 70
- **Interactive mode**: Prompts for project details unless `--yes` specified
- **Namespace** (`--namespace`): Writes `namespace:` to `apm.yml`; interactive mode also prompts for it and allows an empty value
- **Auto-detection**: Automatically detects author from `git config user.name` and description from project context
Comment on lines +60 to +75
def validate_namespace(namespace: str, *, field_name: str = "namespace") -> str:
"""Return a normalized package namespace or raise ``ValueError``."""
if not isinstance(namespace, str):
raise ValueError(
f"Invalid '{field_name}' field: expected string, got {type(namespace).__name__}"
)

normalized = namespace.strip()
if not normalized:
raise ValueError(f"'{field_name}' must not be empty")
if "/" in normalized or "\\" in normalized:
raise ValueError(f"'{field_name}' must be a single path segment")
if len(normalized) > 64:
raise ValueError(f"'{field_name}' must be 1-64 characters")
if "--" in normalized:
raise ValueError(f"'{field_name}' must not contain consecutive hyphens")
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.

[FEATURE] Support package namespaces

4 participants