Table of Contents generated with DocToc
The operations every security-* skill performs on <tracker>, stated once, independent of where the tracker lives. A backend is selected per project by tracker.platform in <project-config>/project.md, mirrored as [tracker] platform in the vetted-ops policy. Skills never call a backend directly: every tracker command a skill writes as gh is a vetted operation, and vetted-ops runs it against whichever backend the policy names. Operations on <upstream> (public PRs, code search) are not part of this contract — they stay on the project's forge.
tracker.platform | Backend | What a vetted operation runs |
|---|---|---|
github (default) | Private GitHub repository | Its gh builder, per tools/github/operations.md. Nothing changes for an existing adopter. |
local-files | Markdown files in a folder of a private Git or SVN working copy | magpie-local-tracker — tools/local-tracker/ |
jira | A Jira project whose issues carry an issue-security level | magpie-jira-tracker — tools/jira/ |
The two non-GitHub backends expose the same subcommand surface and the same JSON output, defined below. The vetted-ops policy pins the target (the tracker folder, or the Jira URL, project and security level); the backend's code runs from the tool installed beside vetted-ops, never from a path a policy names. A skill can name neither.
The mapping that makes the skills backend-neutral, keyed by vetted operation. The first column is the gh shape the security skills are written in; the skill runs the vetted operation in the second column instead (vetted-op-read for reads, vetted-op-tracker for the procedures and tracker-publish, vetted-op for the other writes — the entry points keep their meaning on every backend). The last two columns are what vetted-ops runs on a non-GitHub tracker.
Skill command (gh) | Vetted operation | local-files / jira subcommand |
|---|---|---|
gh issue view <N> --repo <tracker> --json … | issue-view N | view N |
gh issue view <N> --json comments, gh api …/issues/<N>/comments | issue-comments N | view N --comments |
gh issue list --repo <tracker> --state S | issue-list S | list --state S |
gh issue list --repo <tracker> --state S --label L | issue-list-label S L | list --state S --label L |
gh search issues "<words>" --repo <tracker>, gh issue list --search … | issue-search "<words>" (no : qualifiers) | list --state all --search "<words>" |
gh label list --repo <tracker> | label-list | labels |
gh api repos/<tracker>/issues -F title=@… -F body=@… | issue-create "<title>" <body> | create --title "<title>" --body-file - |
gh issue edit <N> --add-label L | issue-add-label N L | edit N --add-label L |
gh issue edit <N> --remove-label L | issue-remove-label N L | edit N --remove-label L |
gh issue edit <N> --milestone M | issue-set-milestone N M | edit N --milestone M |
gh issue edit <N> --add-assignee U | issue-add-assignee N U | edit N --add-assignee U |
gh issue edit <N> --remove-assignee U | issue-remove-assignee N U | edit N --remove-assignee U |
gh issue edit <N> --body-file F | issue-edit-body N F | edit N --body-file - |
gh issue edit <N> --title T | issue-edit-title N "<title>" | edit N --title "<title>" |
gh issue comment <N> --body-file F | issue-comment N F | comment N --body-file - |
gh issue close <N> --reason R | issue-close N R | close N --reason completed|not-planned |
gh issue reopen <N> | issue-reopen N | reopen N |
vetted-op-tracker rollup-append | rollup-append N A F | rollup-append N --action A --entry-body-file - |
vetted-op-tracker rollup-amend-latest | rollup-amend-latest N A F | rollup-amend-latest N --action A --entry-body-file - |
vetted-op-read body-field-get | body-field-get N FIELD | field-get N FIELD |
vetted-op-tracker body-field-set | body-field-set N FIELD F | field-set N FIELD --value-file - |
gh api repos/<tracker> --jq .name (Step 0 access check) | tracker-doctor | doctor |
generate-cve-json <N> --attach (reads the issue) | issue-body N (saved), then generate-cve-json --stdin | body N (markdown ### sections; Jira h3. converted) |
generate-cve-json <N> --attach (attaches the JSON) | issue-attach N FILE NAME | attach N - --name=NAME |
| — (end of a session) | tracker-pending | pending (local-files only) |
| — (end of a session) | tracker-publish F | publish --message-file - --push (local-files only) |
Bodies always travel on stdin, from a file inside the vetted-ops workspace; labels, milestones, assignees and close reasons must be values the policy declares. These tracker operations have no counterpart and are refused by name on local-files and jira, never approximated: milestone-list, milestone-create, milestone-close (milestones are free values, listed in the policy), collaborators (the roster comes from release-trains.md), comment-update and rollup-fold (the rollup is kept by rollup-append; there are no legacy bot comments), and the board-* operations (no project board). gh label create needs no counterpart: labels are free strings on both backends.
A tracker is addressed by an ID string: the issue number on GitHub (123), the issue key on Jira (FOOSEC-12), the file or folder name in a local tracker (SEC-0012). Every backend also accepts the bare number and expands it with its own prefix. Skills render an ID as a link with the url the backend returns; a local tracker without tracker.local.url_template returns a repository-relative path, which is a link inside the tracker and the bare ID everywhere else.
Every output is one JSON document on stdout. Errors go to stderr with a non-zero exit code.
These are the backend CLIs' subcommands — what the vetted operations above run.
| Operation | Arguments | Output | Reached from |
|---|---|---|---|
view <id> | --comments | {id, title, state, labels, assignees, milestone, author, created, updated, body, url, comments?}; with --comments the status rollup is the first comment (kind: "rollup"), as on GitHub | issue-view, issue-comments |
list | --state open|closed|all (default open), --label L (repeatable, AND), --search TEXT, --limit N | [{id, title, state, labels, updated, url}] | issue-list, issue-list-label, issue-search |
labels | — | ["<label>", …] (labels in use) | label-list |
field-get <id> <heading> | — | {id, heading, value} (value is null when the section is missing) | body-field-get |
create | --title T or --title-file F, --body-file F, --label L (repeatable) | {id, url} | issue-create |
edit <id> | --add-label, --remove-label, --add-assignee, --remove-assignee (all repeatable), --milestone M, --clear-milestone, --title T or --title-file F, --body-file F | {id, url} | issue-add-label … issue-edit-title |
field-set <id> <heading> | --value-file F | {id, heading, created: bool} | body-field-set |
comment <id> | --body-file F, --author A | {id, comment_id, url} | issue-comment |
close <id> | --reason completed|not-planned | {id, state} | issue-close |
reopen <id> | — | {id, state} | issue-reopen |
rollup-append <id> | --action A, --entry-body-file F, --user U, --now ISO8601 | {id, url} | rollup-append |
rollup-amend-latest <id> | --action A, --entry-body-file F | {id, url}; exit 4 when the latest entry's action is not A | rollup-amend-latest |
attach <id> <file> | --name N (required when <file> is -, stdin) | {id, name, url} | issue-attach, e.g. the patch of security-issue-fix's Subversion flow |
body <id> | — | the body as raw markdown with ### field sections (not JSON), for generate-cve-json --stdin | issue-body |
pending | — | {backend, changes: [...], unpushed_commits, needs_publish: bool} | tracker-pending |
publish | --message-file F, --trailer T (repeatable), --push | {committed, pushed, revision} | tracker-publish |
doctor | — | {backend, ok, checks: [{name, ok, detail}]} | direct, from /magpie-setup config |
Rules that hold for every backend:
--*-file arguments (- for stdin), never as shell arguments — the same rule tools/github/operations.md sets for gh, for the same reason. --title exists for vetted-ops, which passes the validated one-line title as a single argv element with no shell; a human or skill typing a command uses --title-file.rollup-amend-latest.tracker.labels in project.md; a backend never invents a label.### sections by default. field-get / field-set address the section whose heading matches <heading> case-insensitively; field-set creates the section at the end of the body when it is missing. A backend that stores fields differently (Jira wiki markup, a detected local format) maps the heading itself.tools/github/status-rollup.md — summary line YYYY-MM-DD · @user · <Action>, newest at the bottom — wherever the backend stores them.0 success, 2 usage error, 3 tracker or section not found, 4 refused (a guard below, or an amend-latest mismatch), 5 backend error (I/O, HTTP, VCS).The tracker holds embargoed vulnerability details. A backend must refuse to write to a tracker it cannot show is access-restricted, and say why, with exit 4:
jira — every write checks that the issue carries exactly the configured issue-security level (a scheme can define levels open to every logged-in user, so “any level” is not enough), and create always sets tracker.jira.security_level. An issue without one is refused, never “fixed” by the backend.local-files — the backend refuses writes until the tracker‘s committed format descriptor records access_restricted: true, which only detect --write --access-restricted / init --access-restricted set. /magpie-setup config passes that flag only after the user confirms who can read the repository; it never decides that on its own. doctor reports the working copy’s remotes and, for a GitHub remote, fails when the repository is public.github — unchanged: the tracker repository is private, per AGENTS.md.github and jira write to a server; each operation is visible to the team when it returns. local-files writes to a working copy, and nothing is shared until it is committed and pushed. So a skill that wrote through a local-files backend ends its run with a publish proposal:
vetted-op-read --caller <skill> tracker-pending and show the changed files.security: import SEC-0012, SEC-0013), with the project's commit attribution resolved per docs/setup/commit-attribution.md.vetted-op-tracker --caller <skill> tracker-publish <file> (the backend's publish --message-file - --push). On Git that is git add + git commit of the tracker root only, then git push; on SVN it is svn add / svn rm for new and missing files and svn commit, which already publishes.vetted-op-tracker runs outside the sandbox for exactly this reason; if it is not wired that way, say so and point at docs/setup/secure-agent-setup.md; never retry silently.Never publish without the user's confirmation, and never publish files outside the tracker root.
local-files and jira have no Projects V2 board; skills treat them as tracker.project_board_enabled: false.<upstream> may carry the tracker ID; whether it carries a URL depends on the backend having one.