From fc6cd90e7a073496d2ae8118f0a207f55d655279 Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Sun, 12 Jul 2026 08:15:49 +0200 Subject: [PATCH 1/5] chore: release v0.25.0 Bump apm-cli and the lockfile to 0.25.0, move sanitized user-facing changes into the dated changelog section, and confirm the CI lint mirror passes. Post-merge: tag v0.25.0 to trigger the release workflow. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 425fcf34-8659-4f23-a91e-a5a590208239 --- CHANGELOG.md | 95 +++++++++++++++++++------------------------------- pyproject.toml | 2 +- uv.lock | 4 +-- 3 files changed, 38 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cb5e142d..698ecb620 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,76 +7,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.25.0] - 2026-07-12 + +### Added + +- The Contributor Dashboard now includes triaged-issue views, inline comments, + bulk pull-request actions, and direct navigation to active Copilot sessions. + (by @sergio-sisternes-epam, #2043) +- GitHub policy discovery now checks `.github-private` before `.github`, so + organizations can keep `apm-policy.yml` private without changing existing + fallbacks. (by @sergio-sisternes-epam, #2058) + ### Changed -- Hardened command behavior: invalid lockfiles and incomplete policy chains - fail closed before mutation; failed installs skip post-install hooks; - registry routes do not fall back to Git; machine output keeps notices on - stderr; unauthenticated Git retries scrub inherited credentials; `$schema` - selects the manifest contract; and timed-out runtimes are reaped. (#2155) -- MCP audit, install, and uninstall now share one lockfile-bounded current-source - view, so `config-consistency` detects changed or removed transitive declarations - and fails on unreadable package manifests. (#2155) -- `apm compile --target ` now accepts every project target from the - canonical catalog, emits its native context artifact when applicable, and - treats targets without compile output as successful no-ops. (#2155) -- Merged hook configs now contain only each target's native upstream fields; - APM keeps reconciliation ownership in an external `apm-hooks.json` sidecar. - (#2155) +- Core install, uninstall, audit, compile, policy, MCP, hook, lockfile, and target + lifecycle paths now fail closed and reconcile through canonical owners, + preventing partial state and drift across repeated operations. `apm compile + --target` also accepts every canonical project target and treats targets + without compile output as successful no-ops. (#2155) +- Homebrew formula updates now use the tap's daily poller instead of an obsolete + PAT-backed dispatch, restoring release propagation without cross-repository + credentials. (#2088) ### Fixed -- Regenerating the lockfile now records each transitive local dependency's - identity as a project-root-relative POSIX path instead of an absolute machine - path, so a committed `apm.lock.yaml` stays portable and deterministic across - machines and CI. (#2155) -- Positional `apm install ` now exits non-zero when all packages fail - validation, matching manifest installs for reliable CI scripting. (#2131) -- A failed first `apm install` no longer leaves behind an `apm.yml` it created, - and the final completion summary now reflects the committed outcome instead of - rendering before commit or rollback. Successful dry-run bootstrap keeps the - generated manifest and explicit targets for the next run. (#2155) -- Manifest and policy parsers now reject wrong-typed native schema values and - report unknown policy keys. Migration: quote numeric `apm.yml` versions, use - non-empty string identities, and use mappings/lists for policy blocks. (closes #2137) (#2143) -- `apm uninstall` now transfers shared deployed-file ownership to a surviving - package, preserving lockfile content-integrity coverage. (#2148) -- Semver resolution now preserves each dependency's authentication scheme, so - Azure DevOps bearer credentials are used consistently for tag listing, and a - rejected `ADO_APM_PAT` retries ref resolution with the Azure CLI bearer. (#2155) -- Contracting the active target set now removes APM-managed MCP server - entries from dropped targets while preserving user-authored servers and - unrelated native config, including safe adoption of self-defined servers from - legacy lockfiles when the native entry exactly matches its stored baseline. - (#2155) -- `apm uninstall` now reconciles dependency removal, survivor ownership, hashes, - MCP state, and the deployment ledger before one atomic lockfile write. (#2155) -- `apm install` exception and rollback diagnostics now render through the - `CommandLogger` owner, keeping recovery hints and warning severity consistent. - (#2155) -- Fresh checkouts with declared consumer targets no longer remain - `apm audit --ci`-red for files those targets cannot restore: `apm install` - now removes stale `deployed_files` entries outside the legitimate target - set. The [OpenAPM v0.1 specification](docs/src/content/docs/specs/openapm-v0.1.md) - now defines the same fail-safe reconciliation contract. (by @edenfunf; - closes #2059) (#2114) +- `apm prune` and `apm deps list` now treat nested `apm.yml` files as part of + their installed parent package instead of exposing or deleting them as + top-level orphans. (#2092) +- `apm pack` now selects `.apm/` as the authoritative source only when it exists, + preserves root plugin directories after `apm init`, and fails closed on invalid + explicit `includes:` paths. (#2122) +- Azure DevOps marketplace checks now preserve suffix-free `/_git/` URLs + and pass Azure CLI bearer authentication through to `git ls-remote`. + (closes #2119, #2121) +- `apm install` now removes stale deployment records for inactive targets, so + fresh checkouts can return `apm audit --ci` to green. (by @edenfunf, closes + #2059, #2114) - `apm install --target intellij` now configures JetBrains Copilot MCP support while routing package file primitives through the Copilot profile. - (by @sergio-sisternes-epam; closes #1957) (#2041) -- Global Claude installs now support an absolute `CLAUDE_CONFIG_DIR` outside - `HOME` without leaving a partial deployment. (closes #2129) (#2135) -- Azure DevOps marketplace checks now preserve suffix-free `/_git/` URLs - and pass Azure CLI bearer authentication through to `git ls-remote`. (closes #2119) -- `apm pack` now treats `.apm/` as the authoritative local source, warns on - mixed layouts, and enforces explicit `includes:` lists exhaustively while - preserving root-only Claude plugin directories, including after `apm init`. - (#2122) + (by @sergio-sisternes-epam, closes #1957, #2041) ### Performance -- Deployment manifest reconciliation and uninstall ownership handoff now use - precomputed path indexes, avoiding quadratic scans on large deployment - ledgers. (#2155) +- Dependency lookup, HTTP cache enforcement, marketplace ref selection, and + host classification now use indexed or cached paths, avoiding repeated linear + scans at scale. (by @sergio-sisternes-epam, #2124) ## [0.24.1] - 2026-07-10 diff --git a/pyproject.toml b/pyproject.toml index 8c0ab8f3d..0d346b5a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "apm-cli" -version = "0.24.1" +version = "0.25.0" description = "MCP configuration tool" readme = "README.md" requires-python = ">=3.10" diff --git a/uv.lock b/uv.lock index a32da8d35..2ef696cbd 100644 --- a/uv.lock +++ b/uv.lock @@ -201,7 +201,7 @@ wheels = [ [[package]] name = "apm-cli" -version = "0.24.1" +version = "0.25.0" source = { editable = "." } dependencies = [ { name = "click" }, @@ -554,7 +554,7 @@ name = "exceptiongroup" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } wheels = [ From 28d398bced18962b674b92cdaf947d7ec142b3b7 Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Sun, 12 Jul 2026 08:32:06 +0200 Subject: [PATCH 2/5] docs: design community-fix release notes Define the evidence-backed grouping and validation contract for restoring every issue closed by PR #2155 to the v0.25.0 changelog. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 425fcf34-8659-4f23-a91e-a5a590208239 --- ...elease-changelog-community-fixes-design.md | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-12-release-changelog-community-fixes-design.md diff --git a/docs/superpowers/specs/2026-07-12-release-changelog-community-fixes-design.md b/docs/superpowers/specs/2026-07-12-release-changelog-community-fixes-design.md new file mode 100644 index 000000000..62c93efc6 --- /dev/null +++ b/docs/superpowers/specs/2026-07-12-release-changelog-community-fixes-design.md @@ -0,0 +1,100 @@ +# Release changelog community-fix coverage design + +## Problem + +The v0.25.0 release draft compressed PR #2155 into one broad changelog entry. +That removed the direct connection between the merged work and the 24 issues +that #2155 closed. It also hid distinct user-visible fixes behind architecture +language. + +The release notes must let a community reporter answer two questions without +reading the pull request: + +1. Was my issue fixed in this release? +2. What changed in the product? + +## Scope + +This correction changes the v0.25.0 section in `CHANGELOG.md` and synchronizes +the release PR description. It does not change runtime code, version numbers, +release workflows, or the post-merge tagging gate. + +## Source of truth + +GitHub PR #2155 `closingIssuesReferences` is the authoritative closure set: + +`#2062`, `#2071`, `#2103`, `#2116`, `#2126`, `#2127`, `#2128`, `#2129`, +`#2130`, `#2136`, `#2137`, `#2138`, `#2139`, `#2140`, `#2147`, `#2148`, +`#2149`, `#2150`, `#2156`, `#2157`, `#2158`, `#2159`, `#2160`, and `#2161`. + +Issue titles and PR #2155's consolidated-fix sections provide the wording +evidence. Closed, unmerged point PRs are supporting history, not changelog +authorities. + +## Changelog structure + +Replace the broad #2155 summary with 11 user-outcome entries. Every entry names +the affected command or contract, explains the observable result, and ends with +`(closes #, ...; #2155)`. This keeps the merged PR distinct from the +issues it resolved. + +### Changed + +1. Canonical target acceptance and help: `#2138`, `#2147`. +2. Claude, Kiro, and Copilot hook contracts and provenance: `#2062`, `#2071`, + `#2128`, `#2157`. + +### Fixed + +3. Missing plugin or skill selection and total install failure semantics: + `#2103`, `#2116`, `#2126`. +4. Install rollback, resumability, and owned diagnostics: `#2129`, `#2140`, + `#2161`. +5. Local-path MCP source and declaration drift detection: `#2127`, `#2136`. +6. Target contraction and legacy lockfile adoption: `#2139`, `#2149`, `#2158`. +7. Manifest and policy schema rejection: `#2137`. +8. Final compile orphan cleanup: `#2130`. +9. Shared-file ownership and atomic uninstall persistence: `#2148`, `#2160`. +10. Azure DevOps bearer preservation and stale-PAT fallback: `#2150`, `#2156`. + +### Performance + +11. Indexed deployment-ledger mutation: `#2159`. + +## Data flow + +1. Read #2155's closure set from GitHub. +2. Map every issue to exactly one user-outcome group. +3. Write the grouped changelog entries. +4. Extract all `closes` references attached to #2155 entries. +5. Compare the extracted set with GitHub's closure set. +6. Update the release PR body so its entry counts and validation claims remain + truthful. +7. Run the release lint mirror, commit the correction, and push it to PR #2164. + +## Error handling + +- If the GitHub closure set changes before the correction is committed, stop + and regenerate the mapping. +- If any issue is missing or duplicated, do not push. +- If an issue title and #2155's implementation summary disagree, use the + user-observable behavior established by tests and flag the ambiguity. +- If the lint mirror fails, do not push the correction. + +## Validation + +The correction is complete when: + +- The #2155 changelog entries contain exactly 24 unique `closes` references. +- That set equals PR #2155's `closingIssuesReferences`. +- Every #2155 entry explains a concrete user-visible behavior. +- The release PR body no longer claims a stale changelog-entry count. +- The release lint mirror passes. +- No release tag is created. + +## Trade-offs + +Eleven grouped entries are longer than one summary entry, but preserve +community traceability without repeating #2155 twenty-four times. One bullet +per issue was rejected because closely related issues describe one user-visible +fix and would make the release notes harder to scan. From 0b78c08d024069837309d568ad5eaa7381ee7014 Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Sun, 12 Jul 2026 08:40:41 +0200 Subject: [PATCH 3/5] docs: plan community-fix release notes Plan the issue-set validation, grouped changelog rewrite, PR body synchronization, and release-gate checks for #2155. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 425fcf34-8659-4f23-a91e-a5a590208239 --- ...6-07-12-restore-release-community-fixes.md | 366 ++++++++++++++++++ 1 file changed, 366 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-12-restore-release-community-fixes.md diff --git a/docs/superpowers/plans/2026-07-12-restore-release-community-fixes.md b/docs/superpowers/plans/2026-07-12-restore-release-community-fixes.md new file mode 100644 index 000000000..98bb4c571 --- /dev/null +++ b/docs/superpowers/plans/2026-07-12-restore-release-community-fixes.md @@ -0,0 +1,366 @@ +# Restore Release Community Fixes Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Restore complete, issue-linked coverage for every community-visible fix shipped by PR #2155 in the v0.25.0 release notes. + +**Architecture:** Treat GitHub PR #2155 `closingIssuesReferences` as the closure source of truth. Replace the broad changelog summary with 11 user-outcome groups, validate exact set equality for all 24 issue numbers, then synchronize PR #2164's description before pushing. + +**Tech Stack:** Markdown, GitHub CLI, Python standard library, existing cut-release lint scripts + +--- + +## File structure + +- Modify: `CHANGELOG.md` - replace the broad #2155 summary with grouped, + issue-linked user outcomes. +- Modify: `/Users/danielmeppiel/.copilot/session-state/1124f104-3167-48e8-a6cb-37c77797be67/files/pr-body-v0.25.0.md` + - keep PR #2164's counts, rationale, and trade-offs truthful. +- Read: `docs/superpowers/specs/2026-07-12-release-changelog-community-fixes-design.md` + - authoritative grouping and validation contract. + +### Task 1: Restore #2155 issue coverage in the changelog + +**Files:** +- Modify: `CHANGELOG.md:20-67` +- Reference: `docs/superpowers/specs/2026-07-12-release-changelog-community-fixes-design.md` + +- [ ] **Step 1: Run the closure validator against the current changelog** + +Run: + +```bash +uv run python - <<'PY' +import json +import re +import subprocess +from pathlib import Path + +payload = json.loads( + subprocess.check_output( + ["gh", "pr", "view", "2155", "--json", "closingIssuesReferences"], + text=True, + ) +) +expected = {item["number"] for item in payload["closingIssuesReferences"]} +text = Path("CHANGELOG.md").read_text(encoding="utf-8") +release = text.split("## [0.25.0]", 1)[1].split("## [0.24.1]", 1)[0] +entries = re.findall(r"(?ms)^- .*?(?=^- |^### |^## |\Z)", release) +actual = [] +for entry in entries: + if "#2155" not in entry or "(closes " not in entry: + continue + closure_text = entry.split("(closes ", 1)[1].split(";", 1)[0] + actual.extend(int(value) for value in re.findall(r"#(\d+)", closure_text)) + +duplicates = {value for value in actual if actual.count(value) > 1} +assert len(actual) == 24, f"expected 24 closure references, found {len(actual)}" +assert not duplicates, f"duplicate closure references: {sorted(duplicates)}" +assert set(actual) == expected, ( + f"missing={sorted(expected - set(actual))}, " + f"extra={sorted(set(actual) - expected)}" +) +print("validated 24 unique #2155 issue closures") +PY +``` + +Expected: FAIL with `expected 24 closure references, found 0`. + +- [ ] **Step 2: Replace the broad #2155 summary with the approved Changed entries** + +Use these exact entries under `### Changed`: + +```markdown +- `apm compile --target`, compile help and errors, and `apm init --target` + now use one canonical target catalog, so every advertised target is accepted + consistently. (closes #2138, #2147; #2155) +- Generated hooks now use canonical upstream contracts: Claude matcher/hooks + nesting, Kiro v1 schema, Copilot's required top-level version, and provenance + outside vendor payloads. (closes #2062, #2071, #2128, #2157; #2155) +``` + +Keep the existing Homebrew entry immediately after these entries. + +- [ ] **Step 3: Add the approved Fixed entries** + +Insert these entries at the start of `### Fixed`: + +```markdown +- `apm install` now fails before commit when declared plugin components or a + requested `--skill` are missing, and total positional-URL failure exits `1`. + (closes #2103, #2116, #2126; #2155) +- Failed global Claude installs now clean up bootstrap state, corrected cyclic + dependency graphs resume without deleting `apm_modules`, and exception output + routes through the command logger. (closes #2129, #2140, #2161; #2155) +- `apm audit --ci` now detects both changed and removed MCP declarations from + local-path sub-packages. (closes #2127, #2136; #2155) +- Contracting the target set now reconciles `deployed_files`, removes + APM-managed MCP servers from dropped targets, and safely adopts exact matches + from legacy lockfiles. (closes #2139, #2149, #2158; #2155) +- Manifest and policy parsers now reject invalid identity values and unknown + policy keys. Migration: quote numeric manifest versions and use the declared + mapping/list types for policy blocks. (closes #2137; #2155) +- `apm compile --clean` now removes the stale context artifact when the final + primitive is removed. (closes #2130; #2155) +- `apm uninstall` now transfers shared deployed-file ownership to a surviving + package and persists deployment state atomically. (closes #2148, #2160; #2155) +- Semver install and update now preserve Azure DevOps bearer authentication and + retry a stale PAT `401` with the Azure CLI bearer. (closes #2150, #2156; #2155) +``` + +Keep the existing entries for #2092, #2122, #2121, #2114, and #2041 after the +#2155 entries. + +- [ ] **Step 4: Add the approved Performance entry** + +Insert this entry before the existing #2124 performance entry: + +```markdown +- Deployment-ledger reconciliation now uses indexed mutation paths, avoiding + quadratic scans as deployment history grows. (closes #2159; #2155) +``` + +- [ ] **Step 5: Re-run the closure validator** + +Run: + +```bash +uv run python - <<'PY' +import json +import re +import subprocess +from pathlib import Path + +payload = json.loads( + subprocess.check_output( + ["gh", "pr", "view", "2155", "--json", "closingIssuesReferences"], + text=True, + ) +) +expected = {item["number"] for item in payload["closingIssuesReferences"]} +text = Path("CHANGELOG.md").read_text(encoding="utf-8") +release = text.split("## [0.25.0]", 1)[1].split("## [0.24.1]", 1)[0] +entries = re.findall(r"(?ms)^- .*?(?=^- |^### |^## |\Z)", release) +actual = [] +for entry in entries: + if "#2155" not in entry or "(closes " not in entry: + continue + closure_text = entry.split("(closes ", 1)[1].split(";", 1)[0] + actual.extend(int(value) for value in re.findall(r"#(\d+)", closure_text)) + +duplicates = {value for value in actual if actual.count(value) > 1} +assert len(actual) == 24, f"expected 24 closure references, found {len(actual)}" +assert not duplicates, f"duplicate closure references: {sorted(duplicates)}" +assert set(actual) == expected, ( + f"missing={sorted(expected - set(actual))}, " + f"extra={sorted(set(actual) - expected)}" +) +print("validated 24 unique #2155 issue closures") +PY +``` + +Expected: + +```text +validated 24 unique #2155 issue closures +``` + +- [ ] **Step 6: Inspect the changelog diff** + +Run: + +```bash +git --no-pager diff -- CHANGELOG.md +``` + +Expected: 11 #2155 entries grouped under Changed, Fixed, and Performance; every +entry describes a user-visible outcome and uses `(closes ...; #2155)`. + +### Task 2: Synchronize PR #2164's description + +**Files:** +- Modify: `/Users/danielmeppiel/.copilot/session-state/1124f104-3167-48e8-a6cb-37c77797be67/files/pr-body-v0.25.0.md` + +- [ ] **Step 1: Replace the stale WHY claim** + +Replace the bullet claiming that #2155 should be one entry with: + +```markdown +- [x] PR #2155 closed 24 issues across distinct user-visible surfaces. + Compressing them into one broad entry removed the connection between + community reports and shipped outcomes, so this release preserves grouped + entries for each outcome family. +``` + +- [ ] **Step 2: Update the changelog approach and implementation rows** + +Change Approach step 3 to: + +```markdown +3. Rewrite `[Unreleased]` as `[0.25.0] - 2026-07-12`, keep concise entries + for normal PRs, and preserve 11 grouped user-outcome entries for #2155 with + all 24 closed issues linked. +``` + +Change the `CHANGELOG.md` implementation row to: + +```markdown +| `CHANGELOG.md` | Adds the dated 0.25.0 section and restores 11 grouped #2155 outcomes covering all 24 closed issues. | Keeps an empty `[Unreleased]` placeholder and credits external contributors. | +``` + +- [ ] **Step 3: Update trade-offs and benefits** + +Replace the stale sanitization trade-off with: + +```markdown +- **Grouped outcomes instead of one entry per merged PR.** Chose 11 #2155 + entries because one unusually broad PR closed 24 distinct issues; rejected + one mega-entry and 24 repetitive entries because neither is traceable and + scannable. +``` + +Replace Benefit 2 with: + +```markdown +2. Every issue closed by #2155 appears exactly once in a concrete, + user-visible changelog outcome. +``` + +- [ ] **Step 4: Validate the PR body artifact** + +Run: + +```bash +body=/Users/danielmeppiel/.copilot/session-state/1124f104-3167-48e8-a6cb-37c77797be67/files/pr-body-v0.25.0.md +lines=$(wc -l < "$body" | tr -d ' ') +printf 'lines=%s\n' "$lines" +[ "$lines" -ge 150 ] && [ "$lines" -le 220 ] +! grep -nE '|TBD|TODO' "$body" +tail -n 1 "$body" | grep -F \ + 'Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>' +``` + +Expected: line count remains between 150 and 220, no placeholder output, and +the trailer is printed. + +### Task 3: Validate, commit, push, and update the open PR + +**Files:** +- Modify: `CHANGELOG.md` +- Existing plan/spec commits: `docs/superpowers/specs/2026-07-12-release-changelog-community-fixes-design.md` + and `docs/superpowers/plans/2026-07-12-restore-release-community-fixes.md` + +- [ ] **Step 1: Confirm the closure source has not changed** + +Run: + +```bash +uv run python - <<'PY' +import json +import re +import subprocess +from pathlib import Path + +payload = json.loads( + subprocess.check_output( + ["gh", "pr", "view", "2155", "--json", "closingIssuesReferences"], + text=True, + ) +) +expected = {item["number"] for item in payload["closingIssuesReferences"]} +text = Path("CHANGELOG.md").read_text(encoding="utf-8") +release = text.split("## [0.25.0]", 1)[1].split("## [0.24.1]", 1)[0] +entries = re.findall(r"(?ms)^- .*?(?=^- |^### |^## |\Z)", release) +actual = [] +for entry in entries: + if "#2155" not in entry or "(closes " not in entry: + continue + closure_text = entry.split("(closes ", 1)[1].split(";", 1)[0] + actual.extend(int(value) for value in re.findall(r"#(\d+)", closure_text)) + +duplicates = {value for value in actual if actual.count(value) > 1} +assert len(actual) == 24, f"expected 24 closure references, found {len(actual)}" +assert not duplicates, f"duplicate closure references: {sorted(duplicates)}" +assert set(actual) == expected, ( + f"missing={sorted(expected - set(actual))}, " + f"extra={sorted(set(actual) - expected)}" +) +print("validated 24 unique #2155 issue closures") +PY +``` + +Expected: + +```text +validated 24 unique #2155 issue closures +``` + +- [ ] **Step 2: Run the release lint mirror** + +Run: + +```bash +./.agents/skills/cut-release/scripts/verify-lint-mirror.sh +``` + +Expected: four PASS rows and `[+] all lint-mirror checks PASSED`. + +- [ ] **Step 3: Commit the changelog correction** + +Run: + +```bash +git add CHANGELOG.md +git commit -m "docs: restore v0.25.0 community fixes" \ + -m "Expand PR #2155 into grouped user outcomes and link every issue it closed." \ + -m "Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>" \ + -m "Copilot-Session: 425fcf34-8659-4f23-a91e-a5a590208239" +``` + +Expected: one commit containing the changelog correction. + +- [ ] **Step 4: Push the branch** + +Run: + +```bash +git push +``` + +Expected: `danielmeppiel-chore-cut-next-release` advances without force-push. + +- [ ] **Step 5: Update PR #2164 through the REST-backed PR update tool** + +Call `update_pull_request` with: + +```text +repo_full_name: microsoft/apm +pr_number: 2164 +body: Read the exact UTF-8 contents from + /Users/danielmeppiel/.copilot/session-state/1124f104-3167-48e8-a6cb-37c77797be67/files/pr-body-v0.25.0.md +``` + +Expected: PR #2164 retains title `chore: release v0.25.0` and displays the +updated community-fix rationale. + +- [ ] **Step 6: Verify the final PR state** + +Run: + +```bash +gh pr view 2164 --json state,headRefOid,body --jq '{ + state, + headRefOid, + hasIssueCount: (.body | contains("24 issues")), + hasGroupedCount: (.body | contains("11 grouped")) +}' +git status --short +``` + +Expected: PR state is `OPEN`, both body checks are `true`, and `git status` +prints no changes. + +- [ ] **Step 7: Preserve the human release gate** + +Do not create or push `v0.25.0`. The post-merge tag remains the maintainer's +explicit release trigger. From 4aed286aa0ad8b7f70abb5c8aada73081864261c Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Sun, 12 Jul 2026 08:58:13 +0200 Subject: [PATCH 4/5] docs: restore v0.25.0 community fixes Expand PR #2155 into grouped user outcomes and link every issue it closed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 425fcf34-8659-4f23-a91e-a5a590208239 --- CHANGELOG.md | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 698ecb620..a2530ba47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,17 +20,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Core install, uninstall, audit, compile, policy, MCP, hook, lockfile, and target - lifecycle paths now fail closed and reconcile through canonical owners, - preventing partial state and drift across repeated operations. `apm compile - --target` also accepts every canonical project target and treats targets - without compile output as successful no-ops. (#2155) +- `apm compile --target`, compile help and errors, and `apm init --target` + now use one canonical target catalog, so every advertised target is accepted + consistently. (closes #2138, #2147; #2155) +- Generated hooks now use canonical upstream contracts: Claude matcher/hooks + nesting, Kiro v1 schema, Copilot's required top-level version, and provenance + outside vendor payloads. (closes #2062, #2071, #2128, #2157; #2155) - Homebrew formula updates now use the tap's daily poller instead of an obsolete PAT-backed dispatch, restoring release propagation without cross-repository credentials. (#2088) ### Fixed +- `apm install` now fails before commit when declared plugin components or a + requested `--skill` are missing, and total positional-URL failure exits `1`. + (closes #2103, #2116, #2126; #2155) +- Failed global Claude installs now clean up bootstrap state, corrected cyclic + dependency graphs resume without deleting `apm_modules`, and exception output + routes through the command logger. (closes #2129, #2140, #2161; #2155) +- `apm audit --ci` now detects both changed and removed MCP declarations from + local-path sub-packages. (closes #2127, #2136; #2155) +- Contracting the target set now reconciles `deployed_files`, removes + APM-managed MCP servers from dropped targets, and safely adopts exact matches + from legacy lockfiles. (closes #2139, #2149, #2158; #2155) +- Manifest and policy parsers now reject invalid identity values and unknown + policy keys. Migration: quote numeric manifest versions and use the declared + mapping/list types for policy blocks. (closes #2137; #2155) +- `apm compile --clean` now removes the stale context artifact when the final + primitive is removed. (closes #2130; #2155) +- `apm uninstall` now transfers shared deployed-file ownership to a surviving + package and persists deployment state atomically. (closes #2148, #2160; #2155) +- Semver install and update now preserve Azure DevOps bearer authentication and + retry a stale PAT `401` with the Azure CLI bearer. (closes #2150, #2156; #2155) - `apm prune` and `apm deps list` now treat nested `apm.yml` files as part of their installed parent package instead of exposing or deleting them as top-level orphans. (#2092) @@ -49,6 +70,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Performance +- Deployment-ledger reconciliation now uses indexed mutation paths, avoiding + quadratic scans as deployment history grows. (closes #2159; #2155) - Dependency lookup, HTTP cache enforcement, marketplace ref selection, and host classification now use indexed or cached paths, avoiding repeated linear scans at scale. (by @sergio-sisternes-epam, #2124) From a5a10996604d6656aaa7af2766efb1899f097cf7 Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Sun, 12 Jul 2026 09:09:18 +0200 Subject: [PATCH 5/5] chore: remove superpowers planning artifacts Remove the repository-local superpowers planning tree and ignore future local planning artifacts. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 425fcf34-8659-4f23-a91e-a5a590208239 --- .gitignore | 1 + .../2026-07-10-plugin-pack-source-layout.md | 729 ------------------ ...6-07-12-restore-release-community-fixes.md | 366 --------- ...-07-10-plugin-pack-source-layout-design.md | 219 ------ ...elease-changelog-community-fixes-design.md | 100 --- 5 files changed, 1 insertion(+), 1414 deletions(-) delete mode 100644 docs/superpowers/plans/2026-07-10-plugin-pack-source-layout.md delete mode 100644 docs/superpowers/plans/2026-07-12-restore-release-community-fixes.md delete mode 100644 docs/superpowers/specs/2026-07-10-plugin-pack-source-layout-design.md delete mode 100644 docs/superpowers/specs/2026-07-12-release-changelog-community-fixes-design.md diff --git a/.gitignore b/.gitignore index c55897be4..023d61486 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,7 @@ coverage.xml # Documentation docs/_build/ docs/wip/ +docs/superpowers/ # Local development *.log diff --git a/docs/superpowers/plans/2026-07-10-plugin-pack-source-layout.md b/docs/superpowers/plans/2026-07-10-plugin-pack-source-layout.md deleted file mode 100644 index ef8301900..000000000 --- a/docs/superpowers/plans/2026-07-10-plugin-pack-source-layout.md +++ /dev/null @@ -1,729 +0,0 @@ -# Plugin Pack Source Layout Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use -> superpowers:subagent-driven-development (recommended) or -> superpowers:executing-plans to implement this plan task-by-task. Steps use -> checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Revise PR #2122 so `apm pack` prevents accidental root-folder -publication after `.apm/` adoption without breaking native Claude plugins that -author in root convention directories. - -**Architecture:** Separate source-layout detection from publication consent. -The project-root `.apm/` directory selects APM-native layout; its absence -selects plugin-native root discovery. Explicit `includes` lists remain the -strongest publication boundary. Mixed layouts pack from `.apm/` and emit -actionable warnings for skipped root directories. - -**Tech Stack:** Python 3.11+, Click, pytest, subprocess-based integration tests, -Starlight Markdown. - -**Design source:** Commit `c6c3c713d`, file -`docs/superpowers/specs/2026-07-10-plugin-pack-source-layout-design.md`. - ---- - -## File Map - -- Modify `src/apm_cli/bundle/plugin_exporter.py`: select local source layout, - warn on skipped mixed-layout directories, and retain dependency discovery. -- Create `src/apm_cli/bundle/plugin_layout.py`: hold the shared native-root - source names and detect which ones are present. -- Modify `src/apm_cli/commands/_helpers.py`: explain native-plugin discovery - during `apm init` without creating `.apm/`. -- Modify `tests/unit/test_plugin_exporter.py`: pin the layout matrix and warning - contract, including root hook configuration. -- Modify `tests/unit/test_init_command.py`: pin init messaging for native - convention directories. -- Modify `tests/integration/test_pack_root_skills_e2e.py`: cover native - Claude-plugin init-to-pack behavior and `.apm/` authority. -- Modify `docs/src/content/docs/producer/pack-a-bundle.md`: document the source - switch and mixed-layout warning. -- Modify `docs/src/content/docs/producer/repo-shapes.md`: document progressive - migration. -- Modify `docs/src/content/docs/reference/cli/pack.md`: document pack behavior. -- Modify `docs/src/content/docs/reference/manifest-schema.md`: keep `includes` - consent separate from layout. -- Modify `docs/src/content/docs/getting-started/first-package.md`: preserve the - plugin-native onboarding path. -- Modify `packages/apm-guide/.apm/skills/apm-usage/package-authoring.md`: align - agent-facing authoring guidance. -- Modify `CHANGELOG.md`: replace PR #2122's current discriminator claim with the - source-layout decision. - -### Task 1: Reground PR #2122 and run the design panel - -**Files:** -- Read: `docs/superpowers/specs/2026-07-10-plugin-pack-source-layout-design.md` -- Read: `src/apm_cli/bundle/plugin_exporter.py` -- Read: `src/apm_cli/commands/_helpers.py` -- Read: `tests/integration/test_pack_root_skills_e2e.py` - -- [ ] **Step 1: Check out and rebase the PR head** - -```bash -gh pr checkout 2122 --repo microsoft/apm -git fetch origin main -git rebase origin/main -``` - -Expected: the branch is based on current `main`; resolve only faithful conflicts -within PR #2122 files. - -- [ ] **Step 2: Load the approved design** - -```bash -git show c6c3c713d:docs/superpowers/specs/2026-07-10-plugin-pack-source-layout-design.md -``` - -Expected: the decision says `.apm/` presence selects APM-native layout, -plugin-native roots remain sources when `.apm/` is absent, and mixed layouts -warn. - -- [ ] **Step 3: Run `apm-review-panel` with the design as mandatory context** - -Require the panel to answer: - -1. Does `.apm/` presence provide a deterministic authority boundary? -2. Does the proposal preserve official Claude plugin-root conventions? -3. Are explicit `includes` paths still treated as the strongest publication - boundary? -4. Are warnings actionable and ASCII-safe? - -Expected: fold every in-scope panel finding into PR #2122 before terminal -`ship_now`. - -### Task 2: Pin source-layout selection with unit tests - -**Files:** -- Modify: `tests/unit/test_plugin_exporter.py` -- Test: `tests/unit/test_plugin_exporter.py` - -- [ ] **Step 1: Replace the PR's includes-based unit test** - -Replace `test_declared_includes_excludes_root_level_plugin_dirs` with tests that -make `.apm/` presence the only implicit-layout discriminator: - -```python -def test_apm_dir_excludes_root_level_plugin_dirs(self, tmp_path): - project = _setup_plugin_project(tmp_path) - _write_apm_yml(project, extra={"includes": "auto"}) - (project / ".apm").mkdir(exist_ok=True) - root_agents = project / "agents" - root_agents.mkdir() - (root_agents / "root-bot.agent.md").write_text("root bot", encoding="utf-8") - - result = export_plugin_bundle(project, tmp_path / "build") - - assert not (result.bundle_path / "agents" / "root-bot.agent.md").exists() - - -def test_auto_includes_preserves_native_root_without_apm_dir(self, tmp_path): - project = _setup_plugin_project(tmp_path) - _write_apm_yml(project, extra={"includes": "auto"}) - root_agents = project / "agents" - root_agents.mkdir() - (root_agents / "root-bot.agent.md").write_text("root bot", encoding="utf-8") - - result = export_plugin_bundle(project, tmp_path / "build") - - assert (result.bundle_path / "agents" / "root-bot.agent.md").is_file() -``` - -- [ ] **Step 2: Add omitted-includes matrix coverage** - -```python -def test_omitted_includes_with_apm_dir_skips_root_components(self, tmp_path): - project = _setup_plugin_project(tmp_path) - (project / ".apm").mkdir(exist_ok=True) - root_skills = project / "skills" / "root-skill" - root_skills.mkdir(parents=True) - (root_skills / "SKILL.md").write_text("# Root\n", encoding="utf-8") - - result = export_plugin_bundle(project, tmp_path / "build") - - assert not (result.bundle_path / "skills" / "root-skill").exists() -``` - -- [ ] **Step 3: Add explicit-includes and root-hooks coverage** - -```python -def test_explicit_includes_does_not_restore_root_components(self, tmp_path): - project = _setup_plugin_project(tmp_path) - _write_apm_yml(project, extra={"includes": [".apm/agents/published.agent.md"]}) - agents = project / ".apm" / "agents" - agents.mkdir(parents=True) - (agents / "published.agent.md").write_text("published", encoding="utf-8") - root_agents = project / "agents" - root_agents.mkdir() - (root_agents / "draft.agent.md").write_text("draft", encoding="utf-8") - - result = export_plugin_bundle(project, tmp_path / "build") - - assert (result.bundle_path / "agents" / "published.agent.md").is_file() - assert not (result.bundle_path / "agents" / "draft.agent.md").exists() - - -def test_explicit_includes_are_exhaustive(self, tmp_path): - project = _setup_plugin_project(tmp_path) - _write_apm_yml(project, extra={"includes": [".apm/agents/published.agent.md"]}) - agents = project / ".apm" / "agents" - agents.mkdir(parents=True) - (agents / "published.agent.md").write_text("published", encoding="utf-8") - (agents / "private.agent.md").write_text("private", encoding="utf-8") - - result = export_plugin_bundle(project, tmp_path / "build") - - assert (result.bundle_path / "agents" / "published.agent.md").is_file() - assert not (result.bundle_path / "agents" / "private.agent.md").exists() - - -def test_missing_explicit_include_fails_pack(self, tmp_path): - project = _setup_plugin_project(tmp_path) - _write_apm_yml(project, extra={"includes": [".apm/agents/missing.agent.md"]}) - - with pytest.raises( - ValueError, - match=r"includes path '\.apm/agents/missing\.agent\.md' does not exist", - ): - export_plugin_bundle(project, tmp_path / "build") - - -def test_apm_dir_excludes_root_hook_config(self, tmp_path): - project = _setup_plugin_project(tmp_path) - apm_hooks = project / ".apm" / "hooks" - apm_hooks.mkdir(parents=True) - (apm_hooks / "hooks.json").write_text( - json.dumps({"preCommit": ["published"]}), - encoding="utf-8", - ) - (project / "hooks.json").write_text( - json.dumps({"postPush": ["draft"]}), - encoding="utf-8", - ) - - result = export_plugin_bundle(project, tmp_path / "build") - hooks = json.loads((result.bundle_path / "hooks.json").read_text(encoding="utf-8")) - - assert hooks == {"preCommit": ["published"]} - - -def test_apm_authority_preserves_dependency_components(self, tmp_path): - project = _setup_plugin_project(tmp_path) - (project / ".apm").mkdir(exist_ok=True) - deployed = _write_deployed_agent(project, "dep-agent.agent.md", "dependency") - dep = LockedDependency( - repo_url="acme/tools", - depth=1, - deployed_files=deployed, - ) - _write_lockfile(project, [dep]) - - result = export_plugin_bundle(project, tmp_path / "build") - - assert (result.bundle_path / "agents" / "dep-agent.agent.md").is_file() - - -def test_empty_apm_dir_warns_when_no_local_primitives_exist(self, tmp_path): - project = _setup_plugin_project(tmp_path) - (project / ".apm").mkdir(exist_ok=True) - captured = [] - - class _StubLogger: - def warning(self, message): - captured.append(message) - - export_plugin_bundle(project, tmp_path / "build", logger=_StubLogger()) - - assert captured == [ - "No local primitives found. Expected content under .apm/. " - "Check the project layout or move plugin-native content into .apm/." - ] -``` - -- [ ] **Step 4: Add mixed-layout warning coverage** - -Use the `_StubLogger` pattern already present in this module. Assert exact -semantics rather than ANSI formatting: - -```python -captured = [] - - -class _StubLogger: - def warning(self, message): - captured.append(message) - - -result = export_plugin_bundle( - project, - tmp_path / "build", - logger=_StubLogger(), -) -assert result.bundle_path.is_dir() -assert captured == [ - "Skipping root-level agents/ because .apm/ is present. " - "Move publishable files to .apm/agents/ or remove agents/ " - "to silence this warning.", - "Skipping root-level hooks.json because .apm/ is present. " - "Move publishable hook configuration to .apm/hooks/ or remove hooks.json " - "to silence this warning.", -] -``` - -- [ ] **Step 5: Run the focused unit tests and confirm RED** - -```bash -uv run --extra dev pytest \ - tests/unit/test_plugin_exporter.py::TestExportPluginBundle::test_apm_dir_excludes_root_level_plugin_dirs \ - tests/unit/test_plugin_exporter.py::TestExportPluginBundle::test_auto_includes_preserves_native_root_without_apm_dir \ - tests/unit/test_plugin_exporter.py::TestExportPluginBundle::test_omitted_includes_with_apm_dir_skips_root_components \ - tests/unit/test_plugin_exporter.py::TestExportPluginBundle::test_apm_dir_excludes_root_hook_config \ - -xvs -``` - -Expected: at least the native-root and omitted-includes cases fail against PR -#2122's `has_declared_includes` gate. - -### Task 3: Implement deterministic local source selection - -**Files:** -- Create: `src/apm_cli/bundle/plugin_layout.py` -- Modify: `src/apm_cli/bundle/plugin_exporter.py:121-130` -- Modify: `src/apm_cli/bundle/plugin_exporter.py:768-778` -- Test: `tests/unit/test_plugin_exporter.py` - -- [ ] **Step 1: Add shared source detection** - -```python -"""Plugin-native source-layout conventions.""" - -from pathlib import Path - -PLUGIN_ROOT_DIRS = ("agents", "skills", "commands", "instructions", "extensions", "hooks") - - -def find_plugin_root_sources(project_root: Path) -> list[str]: - """Return plugin-native root sources that exist.""" - sources = [name for name in PLUGIN_ROOT_DIRS if (project_root / name).is_dir()] - if (project_root / "hooks.json").is_file(): - sources.append("hooks.json") - return sources -``` - -- [ ] **Step 2: Add a warning emitter** - -Add a focused helper next to `_collect_root_plugin_components`: - -```python -def _warn_skipped_root_components( - project_root: Path, - logger=None, -) -> None: - """Explain why plugin-native root directories are not packed.""" - for source in find_plugin_root_sources(project_root): - if source == "hooks.json": - message = ( - "Skipping root-level hooks.json because .apm/ is present. " - "Move publishable hook configuration to .apm/hooks/ or remove " - "hooks.json to silence this warning." - ) - else: - message = ( - f"Skipping root-level {source}/ because .apm/ is present. " - f"Move publishable files to .apm/{source}/ or remove {source}/ " - "to silence this warning." - ) - if logger: - logger.warning(message) - else: - _rich_warning(message) - - -def _warn_no_local_primitives(logger=None) -> None: - message = ( - "No local primitives found. Expected content under .apm/. " - "Check the project layout or move plugin-native content into .apm/." - ) - if logger: - logger.warning(message) - else: - _rich_warning(message) -``` - -Keep `logger` unannotated, matching `export_plugin_bundle`; do not introduce -`Any` or a new logging abstraction in this bug fix. - -- [ ] **Step 3: Add explicit include collection** - -Add a local collector that rejects unsafe or missing paths, expands declared -directories recursively, and maps each selected file through -`_plugin_rel_for_deployed_path`: - -```python -def _collect_explicit_local_components( - project_root: Path, - includes: list[str], -) -> tuple[list[tuple[Path, str]], dict]: - components: list[tuple[Path, str]] = [] - hooks: dict = {} - for declared_path in includes: - parts = _deployed_path_parts(declared_path) - source = ensure_path_within(project_root.joinpath(*parts), project_root) - if not source.exists(): - raise ValueError(f"includes path {declared_path!r} does not exist.") - files = ( - [source] - if source.is_file() - else sorted(path for path in source.rglob("*") if path.is_file()) - ) - for file_path in files: - if file_path.is_symlink(): - raise ValueError(f"Explicit include path is a symlink: {file_path}") - file_path = ensure_path_within(file_path, project_root) - repo_relative = portable_relpath(file_path, project_root) - plugin_relative = _plugin_rel_for_deployed_path(repo_relative, None) - if plugin_relative is None: - raise ValueError( - f"Explicit include path is not a packable primitive: {repo_relative}" - ) - if plugin_relative == "hooks.json" or plugin_relative.startswith("hooks/"): - try: - hook_data = json.loads(file_path.read_text(encoding="utf-8")) - except (OSError, ValueError, RecursionError) as exc: - raise ValueError( - f"Explicit hook include is not valid JSON: {repo_relative}" - ) from exc - if not isinstance(hook_data, dict): - raise ValueError( - f"Explicit hook include must contain a JSON object: {repo_relative}" - ) - _deep_merge(hooks, hook_data, overwrite=False) - else: - components.append((file_path, plugin_relative)) - return components, hooks -``` - -Import `portable_relpath` from `apm_cli.utils.paths`. - -- [ ] **Step 4: Replace the includes gate with the authority algorithm** - -```python -own_apm_dir = project_root / ".apm" -if isinstance(package.includes, list): - own_components, root_hooks = _collect_explicit_local_components( - project_root, - package.includes, - ) -else: - own_components = _collect_apm_components(own_apm_dir) - root_hooks = _collect_hooks_from_apm(own_apm_dir) - root_components = _collect_root_plugin_components(project_root) - if own_apm_dir.is_dir(): - _warn_skipped_root_components(project_root, logger) - else: - own_components.extend(root_components) - root_hooks_top = _collect_hooks_from_root(project_root) - _deep_merge(root_hooks, root_hooks_top, overwrite=False) - -if own_apm_dir.is_dir() and not own_components and not root_hooks: - _warn_no_local_primitives(logger) -_merge_file_map(file_map, own_components, pkg_name, force, collisions) -_deep_merge(merged_hooks, root_hooks, overwrite=True) -``` - -Add `.apm/hooks/published.json` and `.apm/hooks/private.json` to the -explicit-list unit fixture and assert only the selected hook key reaches -`hooks.json`. - -Do not alter `_collect_deployed_components`: dependency packages retain their -own source layouts. - -- [ ] **Step 5: Run focused tests and confirm GREEN** - -```bash -uv run --extra dev pytest tests/unit/test_plugin_exporter.py -q -``` - -Expected: all plugin exporter tests pass. - -- [ ] **Step 6: Prove the mutation break** - -Temporarily change `if own_apm_dir.is_dir():` to `if False:` and run: - -```bash -uv run --extra dev pytest \ - tests/unit/test_plugin_exporter.py::TestExportPluginBundle::test_apm_dir_excludes_root_level_plugin_dirs \ - -q -``` - -Expected: FAIL because the root agent is packed. Restore the guard and rerun to -PASS. - -- [ ] **Step 7: Commit the source-selection slice** - -```bash -git add src/apm_cli/bundle/plugin_layout.py src/apm_cli/bundle/plugin_exporter.py tests/unit/test_plugin_exporter.py -git commit -m "fix(pack): select source layout from .apm presence" \ - -m "Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>" -``` - -### Task 4: Add native-plugin init-to-pack e2e proof - -**Files:** -- Modify: `tests/integration/test_pack_root_skills_e2e.py` -- Test: `tests/integration/test_pack_root_skills_e2e.py` - -- [ ] **Step 1: Add a subprocess helper** - -```python -def _run_apm(project: Path, *args: str) -> subprocess.CompletedProcess[str]: - apm_executable = Path(sys.executable).with_name("apm") - return subprocess.run( - [str(apm_executable), *args], - cwd=project, - capture_output=True, - text=True, - timeout=60, - check=False, - ) -``` - -- [ ] **Step 2: Add the native Claude plugin journey** - -```python -def test_init_then_pack_preserves_native_claude_skill(tmp_path: Path) -> None: - project = tmp_path / "native-plugin" - skill = project / "skills" / "published" - skill.mkdir(parents=True) - (skill / "SKILL.md").write_text("# Published\n", encoding="utf-8") - - init_result = _run_apm(project, "init", "--yes") - assert init_result.returncode == 0, init_result.stderr - assert not (project / ".apm").exists() - - pack_result = _run_apm(project, "pack") - assert pack_result.returncode == 0, pack_result.stderr - bundles = [path for path in (project / "build").iterdir() if path.is_dir()] - assert len(bundles) == 1 - assert (bundles[0] / "skills" / "published" / "SKILL.md").is_file() -``` - -- [ ] **Step 3: Extend the existing `.apm/` authority e2e test** - -Keep `test_pack_auto_includes_only_apm_authored_skills` and assert the warning -contains the skipped `skills/` directory, `.apm/` cause, and move/remove action. - -- [ ] **Step 4: Run e2e tests RED then GREEN** - -```bash -uv run --extra dev pytest tests/integration/test_pack_root_skills_e2e.py -xvs -``` - -Expected before Task 3: native-plugin journey fails because root skill is -absent. Expected after Task 3: all tests pass. - -- [ ] **Step 5: Run mutation-break on the e2e trap** - -Temporarily restore PR #2122's `if not package.has_declared_includes` gate. -Run: - -```bash -uv run --extra dev pytest \ - tests/integration/test_pack_root_skills_e2e.py::test_init_then_pack_preserves_native_claude_skill \ - -q -``` - -Expected: FAIL. Restore the `.apm/` presence guard and confirm PASS. - -- [ ] **Step 6: Commit the e2e slice** - -```bash -git add tests/integration/test_pack_root_skills_e2e.py -git commit -m "test(pack): preserve native plugin init journey" \ - -m "Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>" -``` - -### Task 5: Add `apm init` migration guidance - -**Files:** -- Modify: `src/apm_cli/bundle/plugin_layout.py` -- Modify: `src/apm_cli/commands/_helpers.py:656-702` -- Modify: `tests/unit/test_init_command.py` - -- [ ] **Step 1: Add a root convention detector** - -Import the detector created in Task 3: - -```python -from ..bundle.plugin_layout import find_plugin_root_sources -``` - -- [ ] **Step 2: Warn after writing `apm.yml`** - -Use the existing console/logger helper in the init flow: - -```python -native_dirs = find_plugin_root_sources(out_file.parent) -if native_dirs and not (out_file.parent / ".apm").is_dir(): - rendered = ", ".join(f"{name}/" for name in native_dirs) - _rich_warning( - f"Found plugin-native directories at the project root: {rendered}. " - "They remain included by apm pack. Move publishable files under .apm/ " - "when adopting the APM source layout." - ) -``` - -Do not create `.apm/` and do not move files. - -- [ ] **Step 3: Add init warning coverage** - -```python -def test_init_preserves_plugin_native_layout(self): - with tempfile.TemporaryDirectory() as tmp_dir: - os.chdir(tmp_dir) - try: - Path("skills").mkdir() - - result = self.runner.invoke(cli, ["init", "--yes"]) - - assert result.exit_code == 0 - assert "Found plugin-native directories" in result.output - assert "skills/" in result.output - assert "remain included by apm pack" in result.output - assert not Path(".apm").exists() - finally: - os.chdir(self.original_dir) -``` - -- [ ] **Step 4: Run focused init tests** - -```bash -uv run --extra dev pytest tests/unit/test_init_command.py -q -``` - -Expected: all init tests pass. - -- [ ] **Step 5: Commit the onboarding slice** - -```bash -git add src/apm_cli/bundle/plugin_layout.py src/apm_cli/commands/_helpers.py tests/unit/test_init_command.py -git commit -m "feat(init): explain native plugin source layout" \ - -m "Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>" -``` - -### Task 6: Align documentation and changelog - -**Files:** -- Modify: `docs/src/content/docs/producer/pack-a-bundle.md` -- Modify: `docs/src/content/docs/producer/repo-shapes.md` -- Modify: `docs/src/content/docs/reference/cli/pack.md` -- Modify: `docs/src/content/docs/reference/manifest-schema.md` -- Modify: `docs/src/content/docs/getting-started/first-package.md` -- Modify: `packages/apm-guide/.apm/skills/apm-usage/package-authoring.md` -- Modify: `CHANGELOG.md` - -- [ ] **Step 1: Replace includes-based layout claims** - -Use this canonical sentence on each relevant documentation surface: - -```text -When `.apm/` exists, local primitive content is sourced from `.apm/`. -Without `.apm/`, supported plugin-native root directories remain pack -sources. An explicit `includes` list remains exhaustive. -``` - -- [ ] **Step 2: Document mixed-layout warnings** - -Explain that `.apm/` wins, root directories are skipped, pack succeeds, and the -warning tells the author to move or remove the directory. - -- [ ] **Step 3: Preserve first-package native onboarding** - -Keep the existing statement that a plugin can begin with no `.apm/` and -plugin-native convention directories. Add that `apm init` does not invalidate -that layout. - -- [ ] **Step 4: Correct the changelog** - -The entry must describe the user outcome: - -```text -- Fixed plugin packing so `.apm/` becomes authoritative when present while - native plugin convention directories remain packable before `.apm/` - adoption. -``` - -- [ ] **Step 5: Commit documentation** - -```bash -git add CHANGELOG.md docs/src/content/docs packages/apm-guide/.apm/skills/apm-usage/package-authoring.md -git commit -m "docs(pack): explain source layout authority" \ - -m "Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>" -``` - -### Task 7: Full validation and shepherd convergence - -**Files:** -- Verify all PR #2122 files. - -- [ ] **Step 1: Run focused suites** - -```bash -uv run --extra dev pytest tests/unit/test_plugin_exporter.py tests/unit/test_init_command.py -q -uv run --extra dev pytest tests/integration/test_pack_root_skills_e2e.py -q -``` - -Expected: all pass. - -- [ ] **Step 2: Run every integration test** - -```bash -bash scripts/test-integration.sh -``` - -Expected: exit 0 with no failures. - -- [ ] **Step 3: Run the canonical lint contract** - -```bash -uv run --extra dev ruff check src/ tests/ \ - && uv run --extra dev ruff format --check src/ tests/ \ - && uv run --extra dev python -m pylint --disable=all --enable=R0801 \ - --min-similarity-lines=10 --fail-on=R0801 src/apm_cli/ \ - && bash scripts/lint-auth-signals.sh -``` - -Expected: exit 0. - -- [ ] **Step 4: Re-run `apm-review-panel`** - -Fold all in-scope findings. Repeat implementation, focused tests, full -integration, lint, push, and CI recovery until the final posted panel verdict -is `ship_now`. - -- [ ] **Step 5: Push safely** - -```bash -git push --force-with-lease origin HEAD:fix/2054-pack-auto-includes -``` - -Expected: PR #2122 updates without overwriting unexpected remote work. - -- [ ] **Step 6: Watch CI** - -```bash -gh pr checks 2122 --repo microsoft/apm --watch -``` - -Expected: all required checks green on the final head SHA. - -- [ ] **Step 7: Verify mergeability and posted verdict** - -```bash -gh pr view 2122 --repo microsoft/apm \ - --json headRefOid,mergeable,mergeStateStatus,statusCheckRollup,comments -``` - -Expected: `mergeable=MERGEABLE`, CI green, and the final panel comment contains -`ship_now`. diff --git a/docs/superpowers/plans/2026-07-12-restore-release-community-fixes.md b/docs/superpowers/plans/2026-07-12-restore-release-community-fixes.md deleted file mode 100644 index 98bb4c571..000000000 --- a/docs/superpowers/plans/2026-07-12-restore-release-community-fixes.md +++ /dev/null @@ -1,366 +0,0 @@ -# Restore Release Community Fixes Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Restore complete, issue-linked coverage for every community-visible fix shipped by PR #2155 in the v0.25.0 release notes. - -**Architecture:** Treat GitHub PR #2155 `closingIssuesReferences` as the closure source of truth. Replace the broad changelog summary with 11 user-outcome groups, validate exact set equality for all 24 issue numbers, then synchronize PR #2164's description before pushing. - -**Tech Stack:** Markdown, GitHub CLI, Python standard library, existing cut-release lint scripts - ---- - -## File structure - -- Modify: `CHANGELOG.md` - replace the broad #2155 summary with grouped, - issue-linked user outcomes. -- Modify: `/Users/danielmeppiel/.copilot/session-state/1124f104-3167-48e8-a6cb-37c77797be67/files/pr-body-v0.25.0.md` - - keep PR #2164's counts, rationale, and trade-offs truthful. -- Read: `docs/superpowers/specs/2026-07-12-release-changelog-community-fixes-design.md` - - authoritative grouping and validation contract. - -### Task 1: Restore #2155 issue coverage in the changelog - -**Files:** -- Modify: `CHANGELOG.md:20-67` -- Reference: `docs/superpowers/specs/2026-07-12-release-changelog-community-fixes-design.md` - -- [ ] **Step 1: Run the closure validator against the current changelog** - -Run: - -```bash -uv run python - <<'PY' -import json -import re -import subprocess -from pathlib import Path - -payload = json.loads( - subprocess.check_output( - ["gh", "pr", "view", "2155", "--json", "closingIssuesReferences"], - text=True, - ) -) -expected = {item["number"] for item in payload["closingIssuesReferences"]} -text = Path("CHANGELOG.md").read_text(encoding="utf-8") -release = text.split("## [0.25.0]", 1)[1].split("## [0.24.1]", 1)[0] -entries = re.findall(r"(?ms)^- .*?(?=^- |^### |^## |\Z)", release) -actual = [] -for entry in entries: - if "#2155" not in entry or "(closes " not in entry: - continue - closure_text = entry.split("(closes ", 1)[1].split(";", 1)[0] - actual.extend(int(value) for value in re.findall(r"#(\d+)", closure_text)) - -duplicates = {value for value in actual if actual.count(value) > 1} -assert len(actual) == 24, f"expected 24 closure references, found {len(actual)}" -assert not duplicates, f"duplicate closure references: {sorted(duplicates)}" -assert set(actual) == expected, ( - f"missing={sorted(expected - set(actual))}, " - f"extra={sorted(set(actual) - expected)}" -) -print("validated 24 unique #2155 issue closures") -PY -``` - -Expected: FAIL with `expected 24 closure references, found 0`. - -- [ ] **Step 2: Replace the broad #2155 summary with the approved Changed entries** - -Use these exact entries under `### Changed`: - -```markdown -- `apm compile --target`, compile help and errors, and `apm init --target` - now use one canonical target catalog, so every advertised target is accepted - consistently. (closes #2138, #2147; #2155) -- Generated hooks now use canonical upstream contracts: Claude matcher/hooks - nesting, Kiro v1 schema, Copilot's required top-level version, and provenance - outside vendor payloads. (closes #2062, #2071, #2128, #2157; #2155) -``` - -Keep the existing Homebrew entry immediately after these entries. - -- [ ] **Step 3: Add the approved Fixed entries** - -Insert these entries at the start of `### Fixed`: - -```markdown -- `apm install` now fails before commit when declared plugin components or a - requested `--skill` are missing, and total positional-URL failure exits `1`. - (closes #2103, #2116, #2126; #2155) -- Failed global Claude installs now clean up bootstrap state, corrected cyclic - dependency graphs resume without deleting `apm_modules`, and exception output - routes through the command logger. (closes #2129, #2140, #2161; #2155) -- `apm audit --ci` now detects both changed and removed MCP declarations from - local-path sub-packages. (closes #2127, #2136; #2155) -- Contracting the target set now reconciles `deployed_files`, removes - APM-managed MCP servers from dropped targets, and safely adopts exact matches - from legacy lockfiles. (closes #2139, #2149, #2158; #2155) -- Manifest and policy parsers now reject invalid identity values and unknown - policy keys. Migration: quote numeric manifest versions and use the declared - mapping/list types for policy blocks. (closes #2137; #2155) -- `apm compile --clean` now removes the stale context artifact when the final - primitive is removed. (closes #2130; #2155) -- `apm uninstall` now transfers shared deployed-file ownership to a surviving - package and persists deployment state atomically. (closes #2148, #2160; #2155) -- Semver install and update now preserve Azure DevOps bearer authentication and - retry a stale PAT `401` with the Azure CLI bearer. (closes #2150, #2156; #2155) -``` - -Keep the existing entries for #2092, #2122, #2121, #2114, and #2041 after the -#2155 entries. - -- [ ] **Step 4: Add the approved Performance entry** - -Insert this entry before the existing #2124 performance entry: - -```markdown -- Deployment-ledger reconciliation now uses indexed mutation paths, avoiding - quadratic scans as deployment history grows. (closes #2159; #2155) -``` - -- [ ] **Step 5: Re-run the closure validator** - -Run: - -```bash -uv run python - <<'PY' -import json -import re -import subprocess -from pathlib import Path - -payload = json.loads( - subprocess.check_output( - ["gh", "pr", "view", "2155", "--json", "closingIssuesReferences"], - text=True, - ) -) -expected = {item["number"] for item in payload["closingIssuesReferences"]} -text = Path("CHANGELOG.md").read_text(encoding="utf-8") -release = text.split("## [0.25.0]", 1)[1].split("## [0.24.1]", 1)[0] -entries = re.findall(r"(?ms)^- .*?(?=^- |^### |^## |\Z)", release) -actual = [] -for entry in entries: - if "#2155" not in entry or "(closes " not in entry: - continue - closure_text = entry.split("(closes ", 1)[1].split(";", 1)[0] - actual.extend(int(value) for value in re.findall(r"#(\d+)", closure_text)) - -duplicates = {value for value in actual if actual.count(value) > 1} -assert len(actual) == 24, f"expected 24 closure references, found {len(actual)}" -assert not duplicates, f"duplicate closure references: {sorted(duplicates)}" -assert set(actual) == expected, ( - f"missing={sorted(expected - set(actual))}, " - f"extra={sorted(set(actual) - expected)}" -) -print("validated 24 unique #2155 issue closures") -PY -``` - -Expected: - -```text -validated 24 unique #2155 issue closures -``` - -- [ ] **Step 6: Inspect the changelog diff** - -Run: - -```bash -git --no-pager diff -- CHANGELOG.md -``` - -Expected: 11 #2155 entries grouped under Changed, Fixed, and Performance; every -entry describes a user-visible outcome and uses `(closes ...; #2155)`. - -### Task 2: Synchronize PR #2164's description - -**Files:** -- Modify: `/Users/danielmeppiel/.copilot/session-state/1124f104-3167-48e8-a6cb-37c77797be67/files/pr-body-v0.25.0.md` - -- [ ] **Step 1: Replace the stale WHY claim** - -Replace the bullet claiming that #2155 should be one entry with: - -```markdown -- [x] PR #2155 closed 24 issues across distinct user-visible surfaces. - Compressing them into one broad entry removed the connection between - community reports and shipped outcomes, so this release preserves grouped - entries for each outcome family. -``` - -- [ ] **Step 2: Update the changelog approach and implementation rows** - -Change Approach step 3 to: - -```markdown -3. Rewrite `[Unreleased]` as `[0.25.0] - 2026-07-12`, keep concise entries - for normal PRs, and preserve 11 grouped user-outcome entries for #2155 with - all 24 closed issues linked. -``` - -Change the `CHANGELOG.md` implementation row to: - -```markdown -| `CHANGELOG.md` | Adds the dated 0.25.0 section and restores 11 grouped #2155 outcomes covering all 24 closed issues. | Keeps an empty `[Unreleased]` placeholder and credits external contributors. | -``` - -- [ ] **Step 3: Update trade-offs and benefits** - -Replace the stale sanitization trade-off with: - -```markdown -- **Grouped outcomes instead of one entry per merged PR.** Chose 11 #2155 - entries because one unusually broad PR closed 24 distinct issues; rejected - one mega-entry and 24 repetitive entries because neither is traceable and - scannable. -``` - -Replace Benefit 2 with: - -```markdown -2. Every issue closed by #2155 appears exactly once in a concrete, - user-visible changelog outcome. -``` - -- [ ] **Step 4: Validate the PR body artifact** - -Run: - -```bash -body=/Users/danielmeppiel/.copilot/session-state/1124f104-3167-48e8-a6cb-37c77797be67/files/pr-body-v0.25.0.md -lines=$(wc -l < "$body" | tr -d ' ') -printf 'lines=%s\n' "$lines" -[ "$lines" -ge 150 ] && [ "$lines" -le 220 ] -! grep -nE '|TBD|TODO' "$body" -tail -n 1 "$body" | grep -F \ - 'Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>' -``` - -Expected: line count remains between 150 and 220, no placeholder output, and -the trailer is printed. - -### Task 3: Validate, commit, push, and update the open PR - -**Files:** -- Modify: `CHANGELOG.md` -- Existing plan/spec commits: `docs/superpowers/specs/2026-07-12-release-changelog-community-fixes-design.md` - and `docs/superpowers/plans/2026-07-12-restore-release-community-fixes.md` - -- [ ] **Step 1: Confirm the closure source has not changed** - -Run: - -```bash -uv run python - <<'PY' -import json -import re -import subprocess -from pathlib import Path - -payload = json.loads( - subprocess.check_output( - ["gh", "pr", "view", "2155", "--json", "closingIssuesReferences"], - text=True, - ) -) -expected = {item["number"] for item in payload["closingIssuesReferences"]} -text = Path("CHANGELOG.md").read_text(encoding="utf-8") -release = text.split("## [0.25.0]", 1)[1].split("## [0.24.1]", 1)[0] -entries = re.findall(r"(?ms)^- .*?(?=^- |^### |^## |\Z)", release) -actual = [] -for entry in entries: - if "#2155" not in entry or "(closes " not in entry: - continue - closure_text = entry.split("(closes ", 1)[1].split(";", 1)[0] - actual.extend(int(value) for value in re.findall(r"#(\d+)", closure_text)) - -duplicates = {value for value in actual if actual.count(value) > 1} -assert len(actual) == 24, f"expected 24 closure references, found {len(actual)}" -assert not duplicates, f"duplicate closure references: {sorted(duplicates)}" -assert set(actual) == expected, ( - f"missing={sorted(expected - set(actual))}, " - f"extra={sorted(set(actual) - expected)}" -) -print("validated 24 unique #2155 issue closures") -PY -``` - -Expected: - -```text -validated 24 unique #2155 issue closures -``` - -- [ ] **Step 2: Run the release lint mirror** - -Run: - -```bash -./.agents/skills/cut-release/scripts/verify-lint-mirror.sh -``` - -Expected: four PASS rows and `[+] all lint-mirror checks PASSED`. - -- [ ] **Step 3: Commit the changelog correction** - -Run: - -```bash -git add CHANGELOG.md -git commit -m "docs: restore v0.25.0 community fixes" \ - -m "Expand PR #2155 into grouped user outcomes and link every issue it closed." \ - -m "Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>" \ - -m "Copilot-Session: 425fcf34-8659-4f23-a91e-a5a590208239" -``` - -Expected: one commit containing the changelog correction. - -- [ ] **Step 4: Push the branch** - -Run: - -```bash -git push -``` - -Expected: `danielmeppiel-chore-cut-next-release` advances without force-push. - -- [ ] **Step 5: Update PR #2164 through the REST-backed PR update tool** - -Call `update_pull_request` with: - -```text -repo_full_name: microsoft/apm -pr_number: 2164 -body: Read the exact UTF-8 contents from - /Users/danielmeppiel/.copilot/session-state/1124f104-3167-48e8-a6cb-37c77797be67/files/pr-body-v0.25.0.md -``` - -Expected: PR #2164 retains title `chore: release v0.25.0` and displays the -updated community-fix rationale. - -- [ ] **Step 6: Verify the final PR state** - -Run: - -```bash -gh pr view 2164 --json state,headRefOid,body --jq '{ - state, - headRefOid, - hasIssueCount: (.body | contains("24 issues")), - hasGroupedCount: (.body | contains("11 grouped")) -}' -git status --short -``` - -Expected: PR state is `OPEN`, both body checks are `true`, and `git status` -prints no changes. - -- [ ] **Step 7: Preserve the human release gate** - -Do not create or push `v0.25.0`. The post-merge tag remains the maintainer's -explicit release trigger. diff --git a/docs/superpowers/specs/2026-07-10-plugin-pack-source-layout-design.md b/docs/superpowers/specs/2026-07-10-plugin-pack-source-layout-design.md deleted file mode 100644 index a92064917..000000000 --- a/docs/superpowers/specs/2026-07-10-plugin-pack-source-layout-design.md +++ /dev/null @@ -1,219 +0,0 @@ -# Plugin Pack Source Layout Design - -Date: 2026-07-10 -Status: Approved -Related: issue #2054, PR #2122 - -## Decision - -`apm pack` uses the presence of the project-root `.apm/` directory as -the source-layout switch: - -- When `.apm/` exists, `.apm/` is the authoritative local primitive - source. Root convention directories are not packed implicitly. -- When `.apm/` does not exist, supported root convention directories - remain authoritative and are packed implicitly. -- An explicit `includes` list is exhaustive regardless of layout. -- `includes: auto` grants publication consent but does not select the - source layout. - -When both `.apm/` and supported root convention directories exist, -`.apm/` wins. Packing succeeds and emits an actionable warning for -each skipped root directory. - -## Problem - -Issue #2054 demonstrates a publication-boundary bug: an APM-authored -repository can contain an unrelated root `skills/` working directory, -and `apm pack` currently includes it in a plugin bundle. - -PR #2122 fixes that symptom by skipping root convention directories -whenever `includes` is declared. That discriminator is incorrect -because `includes` represents publication consent, not source layout. - -Official Claude plugin documentation defines `skills/`, `agents/`, -`commands/`, and `hooks/` at the plugin root as native authoring -locations: - -https://code.claude.com/docs/en/plugins - -A Claude plugin author can therefore have valid publishable root -content, run `apm init`, receive `includes: auto`, and then silently -lose that content from `apm pack`. APM must support progressive -adoption without making vendor-native content disappear. - -## Goals - -1. Prevent unrelated root convention directories from leaking into - bundles after a project adopts `.apm/`. -2. Preserve native Claude plugin behavior before `.apm/` adoption. -3. Keep `includes` semantics independent from source-layout detection. -4. Make mixed-layout behavior deterministic and explainable. -5. Support gradual migration from plugin-native layout to `.apm/`. - -## Non-goals - -- Merge `.apm/` and root convention trees implicitly. -- Add a `source_layout` field to `apm.yml`. -- Change dependency package discovery. -- Move files automatically during `apm init` or `apm pack`. -- Change README claims without explicit maintainer approval. - -## Behavioral Contract - -| Includes | `.apm/` | Root convention dirs | Implicit root packing | -|----------|---------|----------------------|-----------------------| -| omitted | absent | present | yes | -| `auto` | absent | present | yes | -| omitted | present | absent | no | -| `auto` | present | absent | no | -| omitted | present | present | no, with warning | -| `auto` | present | present | no, with warning | -| explicit list | either | either | no; only listed paths | - -Supported root convention directories remain the set already recognized -by plugin packing. This decision changes when that existing discovery -logic runs; it does not expand the recognized set. - -## Precedence - -Packing resolves local content in this order: - -1. If `includes` is an explicit path list, pack only those paths. -2. Otherwise, detect whether the project-root `.apm/` directory exists. -3. If `.apm/` exists, collect local primitives from `.apm/` and skip - implicit root convention discovery. -4. If `.apm/` does not exist, collect local primitives from supported - root convention directories. -5. Continue using existing dependency component discovery independently - of the root project's source-layout decision. - -Directory presence is intentional. An empty `.apm/` directory indicates -that the author has switched to APM-native layout. This prevents -half-migrated root content from being merged silently. - -## Mixed-layout UX - -When `.apm/` and one or more supported root convention directories both -exist, packing succeeds and writes one warning per skipped directory to -stderr: - -```text -[!] Skipping root-level skills/ because .apm/ is present. - Move publishable files to .apm/skills/ or remove skills/ to silence - this warning. -``` - -The warning names: - -1. the skipped directory, -2. the cause, -3. the next action. - -A mixed layout is not an error because migration can occur incrementally. -Implicitly merging both trees is rejected because collision provenance -would become difficult to predict and debug. - -If packing finds no local primitives after applying the source-layout -rule, it emits: - -```text -[!] No local primitives found. Expected content under .apm/. - Check the project layout or move plugin-native content into .apm/. -``` - -## `apm init` Onboarding - -`apm init` must not make an existing native Claude plugin stop packing. - -When root convention directories exist and `.apm/` does not: - -1. `apm init` may continue writing `includes: auto`. -2. It does not create `.apm/` implicitly. -3. It reports that native root directories remain pack sources. -4. It explains how to opt into `.apm/` by moving content. - -Suggested output: - -```text -[!] Found plugin-native directories at the project root: skills/. - They remain included by apm pack. Move them to .apm/skills/ when - adopting the APM source layout. -``` - -## Error Handling - -An explicit `includes` path that does not exist is a configuration -error, not a layout warning: - -```text -[x] includes path '.apm/skills/example' does not exist. - Fix the path in apm.yml or create it. -``` - -The error names the failure, cause, and next action. No implicit fallback -to root convention discovery occurs for an invalid explicit list. - -## Compatibility - -This design preserves: - -- plugin-native repositories with omitted `includes`, -- plugin-native repositories after `apm init` adds `includes: auto`, -- APM-native repositories that author under `.apm/`, -- explicit allow-list behavior, -- dependency package convention discovery. - -The intentional behavior change is for mixed repositories: once `.apm/` -exists, root convention directories stop being implicit pack sources and -produce warnings. Authors can still include a root path through an -explicit list when that is a deliberate publication decision. - -## Acceptance Tests - -1. `includes: auto`, `.apm/skills/published/`, and root `skills/wip/` - packs only the `.apm/` skill. -2. `includes: auto`, no `.apm/`, and root `skills/published/` packs the - root skill. -3. Omitted `includes`, `.apm/skills/published/`, and root `skills/wip/` - packs only the `.apm/` skill. -4. Omitted `includes`, no `.apm/`, and root `skills/published/` packs - the root skill. -5. An explicit list packs only listed paths with or without `.apm/`. -6. `.apm/` plus root `skills/` emits a warning naming `skills/`, the - `.apm/` cause, and a move-or-remove action. -7. `apm init` in a Claude-native plugin leaves root skills packable. -8. Creating `.apm/` after initialization switches authority and causes - root skills to be skipped with a warning. -9. An invalid explicit path fails with the path and a corrective action. -10. Dependency package root conventions continue to be discovered. - -The e2e regression trap must execute the user-visible `apm init` and -`apm pack` flow for a native Claude plugin. Mutation-break proof removes -the `.apm/` presence guard and confirms that the test fails. - -## Documentation Impact - -PR #2122 documentation must describe the conditional rule: - -> When `.apm/` exists, local primitive content is sourced from `.apm/`. -> Without `.apm/`, supported plugin-native root directories remain pack -> sources. - -The affected Starlight pages and APM usage guidance include: - -- producer pack guidance, -- repository shape guidance, -- pack CLI reference, -- manifest schema reference, -- first-package onboarding, -- package-authoring agent guidance. - -README drift, if any, requires separate explicit maintainer approval. - -## PR #2122 Disposition - -PR #2122 should be revised before merge. Its publication-safety goal is -correct, but `has_declared_includes` must not select the source layout. -The revised change should use `.apm/` presence, add mixed-layout -warnings, and cover the native-plugin-after-`apm init` journey. diff --git a/docs/superpowers/specs/2026-07-12-release-changelog-community-fixes-design.md b/docs/superpowers/specs/2026-07-12-release-changelog-community-fixes-design.md deleted file mode 100644 index 62c93efc6..000000000 --- a/docs/superpowers/specs/2026-07-12-release-changelog-community-fixes-design.md +++ /dev/null @@ -1,100 +0,0 @@ -# Release changelog community-fix coverage design - -## Problem - -The v0.25.0 release draft compressed PR #2155 into one broad changelog entry. -That removed the direct connection between the merged work and the 24 issues -that #2155 closed. It also hid distinct user-visible fixes behind architecture -language. - -The release notes must let a community reporter answer two questions without -reading the pull request: - -1. Was my issue fixed in this release? -2. What changed in the product? - -## Scope - -This correction changes the v0.25.0 section in `CHANGELOG.md` and synchronizes -the release PR description. It does not change runtime code, version numbers, -release workflows, or the post-merge tagging gate. - -## Source of truth - -GitHub PR #2155 `closingIssuesReferences` is the authoritative closure set: - -`#2062`, `#2071`, `#2103`, `#2116`, `#2126`, `#2127`, `#2128`, `#2129`, -`#2130`, `#2136`, `#2137`, `#2138`, `#2139`, `#2140`, `#2147`, `#2148`, -`#2149`, `#2150`, `#2156`, `#2157`, `#2158`, `#2159`, `#2160`, and `#2161`. - -Issue titles and PR #2155's consolidated-fix sections provide the wording -evidence. Closed, unmerged point PRs are supporting history, not changelog -authorities. - -## Changelog structure - -Replace the broad #2155 summary with 11 user-outcome entries. Every entry names -the affected command or contract, explains the observable result, and ends with -`(closes #, ...; #2155)`. This keeps the merged PR distinct from the -issues it resolved. - -### Changed - -1. Canonical target acceptance and help: `#2138`, `#2147`. -2. Claude, Kiro, and Copilot hook contracts and provenance: `#2062`, `#2071`, - `#2128`, `#2157`. - -### Fixed - -3. Missing plugin or skill selection and total install failure semantics: - `#2103`, `#2116`, `#2126`. -4. Install rollback, resumability, and owned diagnostics: `#2129`, `#2140`, - `#2161`. -5. Local-path MCP source and declaration drift detection: `#2127`, `#2136`. -6. Target contraction and legacy lockfile adoption: `#2139`, `#2149`, `#2158`. -7. Manifest and policy schema rejection: `#2137`. -8. Final compile orphan cleanup: `#2130`. -9. Shared-file ownership and atomic uninstall persistence: `#2148`, `#2160`. -10. Azure DevOps bearer preservation and stale-PAT fallback: `#2150`, `#2156`. - -### Performance - -11. Indexed deployment-ledger mutation: `#2159`. - -## Data flow - -1. Read #2155's closure set from GitHub. -2. Map every issue to exactly one user-outcome group. -3. Write the grouped changelog entries. -4. Extract all `closes` references attached to #2155 entries. -5. Compare the extracted set with GitHub's closure set. -6. Update the release PR body so its entry counts and validation claims remain - truthful. -7. Run the release lint mirror, commit the correction, and push it to PR #2164. - -## Error handling - -- If the GitHub closure set changes before the correction is committed, stop - and regenerate the mapping. -- If any issue is missing or duplicated, do not push. -- If an issue title and #2155's implementation summary disagree, use the - user-observable behavior established by tests and flag the ambiguity. -- If the lint mirror fails, do not push the correction. - -## Validation - -The correction is complete when: - -- The #2155 changelog entries contain exactly 24 unique `closes` references. -- That set equals PR #2155's `closingIssuesReferences`. -- Every #2155 entry explains a concrete user-visible behavior. -- The release PR body no longer claims a stale changelog-entry count. -- The release lint mirror passes. -- No release tag is created. - -## Trade-offs - -Eleven grouped entries are longer than one summary entry, but preserve -community traceability without repeating #2155 twenty-four times. One bullet -per issue was rejected because closely related issues describe one user-visible -fix and would make the release notes harder to scan.