Claude Code's (and Copilot's) native plugin installer has zero awareness of .apm/ nesting -- it convention-scans only flat skills/, agents/, commands/, hooks.json at each plugin's root. Confirmed via strings on the installed claude binary and live installs of git@holocron/gitea@holocron/kyberforge@ holocron, all reporting Skills(0) Agents(0) Hooks(0) post ADR-0015's apm conversion. Root cause (apm_cli/core/plugin_manifest.py): apm's plugin.json compiler deliberately strips skills/agents/commands keys, assuming the host already auto-discovers those convention directories -- it has no model of .apm/ being host-visible at all. Separately, apm's own bundle exporter (apm_cli/bundle/plugin_exporter.py, behind `apm pack --format plugin`) implements the correct .apm/ -> flat mapping, but only ever targeted build/<name>-<version>/, a path nothing in marketplace.json's source: points at. scripts/sync-plugin-content.sh wraps that bundle exporter and copies its agents/, skills/, commands/, instructions/, extensions/, and merged hooks.json back into each plugin's own root as a second tracked compiled-output category -- same governance status as .claude-plugin/plugin.json: generated from .apm/, never hand-edited. tests/ subdirectories are excluded from the mirror (dev fixtures, not host-visible runtime content; several hardcode a relative repo-root walk-up sized for the .apm/-nested depth, which breaks when duplicated one level shallower). Applied for real across all 6 plugins and verified two ways: `claude plugin validate --strict` passes on every real plugin directory, and a live `claude --plugin-dir <path> -p "list skills/agents"` behavioral test confirms content is now actually discovered. Also, from the same issue #90 review round: - scripts/check-manifests.sh pointed at each plugin's root-level plugin.json (checking skills/hooks/mcpServers/agents pointer fields) -- that file was a stale near-duplicate of .claude-plugin/plugin.json nothing else read or wrote, now deleted across all 6 plugins. check-manifests.sh is rewritten to validate .claude-plugin/plugin.json instead, and drops the pointer-field checks entirely (nothing to check -- those fields are correctly absent by design). Content-presence drift is now check-plugin-content-sync's job, a new pre-push hook wired in .pre-commit-config.yaml. docs/adr/0017 records the root cause and decision in full, including two rejected alternatives (patching plugin.json's path fields directly -- apm's compiler strips them on every run; pointing marketplace.json at apm pack's build/ output -- a version-suffixed non-source directory nothing can install from without an extra build step). ADR-0015 and CONTEXT.md are updated to point at it. Refs: #90
6.0 KiB
topic, source_keys
| topic | source_keys | ||
|---|---|---|---|
| pull-requests |
|
Pull request read/write execution detail
Parameter signatures below are cross-checked live against the deployed gitea-mcp server tool schemas at authoring time — not copied verbatim from the plugin's research doc for this domain, which has a known history of drifting from the deployed server (e.g. a prior type parameter that no longer exists on list_issues, and the review_scomments typo covered in references/reviews.md). Re-verify via ToolSearch before trusting this file if the gitea-mcp version changes.
list_pull_requests
Parameters:
owner(string, required)repo(string, required)state(string, optional, default"all") —"open"|"closed"|"all"sort(string, optional, default"recentupdate") —"oldest"|"recentupdate"|"leastupdate"|"mostcomment"|"leastcomment"|"priority"milestone(number, optional) — milestone ID filter, not a titlepage(number, optional, default 1)per_page(number, optional, default 30)
List responses trim PRs down to summary fields — head/base are bare ref strings and milestone does not appear as an object. Don't rely on this call to resolve a milestone's ID; see the Gotcha below.
pull_request_read
Parameters:
method(string, required) —"get"|"get_diff"|"get_files"|"get_status"|"get_reviews"|"get_review"|"get_review_comments"owner(string, required)repo(string, required)pull_number(number, required)review_id(number, optional) — required for"get_review"and"get_review_comments"; seereferences/reviews.mdbinary(boolean, optional) — include binary diff content for"get_diff"page(number, optional, default 1)per_page(number, optional, default 30)
"get", "get_diff", "get_files", and "get_status" are covered here. "get_reviews", "get_review", and "get_review_comments" are covered in references/reviews.md.
"get"returns the full PR object: state, draft, merged, mergeable flags;head/baseas full objects ({ref, sha, repo?});milestoneas a bare title string (not{id, title});review_scomments(typo, seereferences/reviews.md)."get_diff"returns raw diff text."get_files"returns the list of changed file objects."get_status"returns the combined commit status for the PR's head commit — CI result only, not review/approval state (seereferences/merging.md).
Milestone gotcha: because pull_request_read only ever returns a milestone title, never an ID, resolving "which milestone ID does this PR belong to" requires calling into gitea-labels-milestones' milestone_read method: "list" and matching by title. Do not attempt to infer or guess the ID.
pull_request_write
Parameters:
method(string, required) —"create"|"update"|"close"|"reopen"|"merge"|"update_branch"|"add_reviewers"|"remove_reviewers"owner(string, required)repo(string, required)pull_number(number, required for every method except"create"— this is enforced by convention, not by the tool's schema, so passing it incorrectly produces a runtime error rather than a client-side validation error)title(string, required for"create"; optional for"update")body(string, required for"create"; optional for"update")head(string, required for"create") — source branch; same-repo PRs use a bare branch name, cross-repo fork PRs use"fork-owner:branch-name"(see Gotcha)base(string, required for"create") — target branchassignee(string, optional) — single loginassignees(array of strings, optional) — login namesmilestone(number, optional) — milestone ID, never a title; settable on both"create"and"update"state(string, optional, for"update") —"open"|"closed"(no"all"— unlike issue state filters)allow_maintainer_edit(boolean, optional, for"update")labels(array of numbers, optional) — label IDs, never names — resolve viagitea-labels-milestonesfirstdeadline(string, optional) — ISO 8601remove_deadline(boolean, optional)reviewers(array of strings, optional) — login names; settable directly on"create", or use"add_reviewers"/"remove_reviewers"to adjust reviewers on an already-open PRteam_reviewers(array of strings, optional) — same asreviewers: settable on"create", or via"add_reviewers"/"remove_reviewers"post-creationdraft(boolean, optional, for"create") — prepends"WIP:"to the title (see Gotcha)
Merge-specific parameters (merge_style, delete_branch, force_merge, merge_when_checks_succeed, head_commit_id, message as merge commit message) are covered in references/merging.md.
Cross-repo head format: head must be "fork-owner:branch-name" for a PR originating from a fork of the base repo. Passing a bare branch name causes Gitea to search for that branch in the base repo instead, and returns 422 when it isn't found there.
Draft/WIP behavior: draft: true on "create" is implemented by prepending "WIP:" to the title — there is no first-class draft boolean stored separately from the title. The title returned by subsequent reads will include the prefix. To un-draft, call "update" with title set to the same text minus the "WIP:" prefix; there is no dedicated undraft method.
update_branch: takes only owner, repo, pull_number — no other parameters. It merges the current base branch into the PR's head branch server-side, resolving a PR that Gitea reports as behind its base (analogous to GitHub's "Update branch" button). Use it when a PR shows mergeable: false or an out-of-date status due to base-branch drift rather than an actual conflict; if the head and base have truly diverged with conflicting changes, this call fails and the conflict must be resolved by pushing a merge/rebase to the head branch directly, outside this skill's scope.