From 3d0eb93028d85a89ee2191b1acb410809989a43d Mon Sep 17 00:00:00 2001 From: Vardhman Gupta Date: Tue, 29 Sep 2026 10:47:08 +0530 Subject: [PATCH 1/2] feat(tools/forgejo): add Forgejo/Gitea adapter bridge (part of #310) --- .github/labeler.yml | 3 + docs/adapters/registry.md | 2 +- docs/labels-and-capabilities.md | 1 + docs/vendor-neutrality.md | 6 +- tools/forgejo/README.md | 37 +++++ tools/forgejo/issue-template.md | 116 ++++++++++++++++ tools/forgejo/labels.md | 75 ++++++++++ tools/forgejo/operations.md | 235 ++++++++++++++++++++++++++++++++ tools/forgejo/project-board.md | 30 ++++ tools/forgejo/source-control.md | 101 ++++++++++++++ tools/forgejo/status-rollup.md | 107 +++++++++++++++ tools/forgejo/tool.md | 79 +++++++++++ tools/github/source-control.md | 2 +- 13 files changed, 789 insertions(+), 5 deletions(-) create mode 100644 tools/forgejo/README.md create mode 100644 tools/forgejo/issue-template.md create mode 100644 tools/forgejo/labels.md create mode 100644 tools/forgejo/operations.md create mode 100644 tools/forgejo/project-board.md create mode 100644 tools/forgejo/source-control.md create mode 100644 tools/forgejo/status-rollup.md create mode 100644 tools/forgejo/tool.md diff --git a/.github/labeler.yml b/.github/labeler.yml index a4745cc6a..1f26e76f9 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -27,6 +27,7 @@ contract:change-request: - any-glob-to-any-file: - 'tools/bitbucket/**' - 'tools/change-request/**' + - 'tools/forgejo/**' - 'tools/github/**' - 'tools/gitlab/**' - 'tools/jira-patch/**' @@ -101,6 +102,7 @@ contract:source-control: - changed-files: - any-glob-to-any-file: - 'tools/asf-svn/**' + - 'tools/forgejo/**' - 'tools/fossil/**' - 'tools/github/**' - 'tools/gitlab/**' @@ -112,6 +114,7 @@ contract:tracker: - changed-files: - any-glob-to-any-file: - 'tools/bitbucket/**' + - 'tools/forgejo/**' - 'tools/fossil/**' - 'tools/github/**' - 'tools/github-body-field/**' diff --git a/docs/adapters/registry.md b/docs/adapters/registry.md index b7db70c77..54338f973 100644 --- a/docs/adapters/registry.md +++ b/docs/adapters/registry.md @@ -54,7 +54,7 @@ extension point = a documented, labelled slot with a tracking issue. | [`tools/forwarder-relay`](../../tools/forwarder-relay/) | ASF-security ([`tools/gmail/asf-relay.md`](../../tools/gmail/asf-relay.md)) | huntr.com, HackerOne, GHSA relay | | [`tools/scan-format`](../../tools/scan-format/) | ASVS | other scanner formats | | [`tools/vcs`](../../tools/vcs/) | Git, Mercurial, Fossil | Subversion [\#602](https://github.com/apache/magpie/issues/602), Jujutsu [\#603](https://github.com/apache/magpie/issues/603), Perforce [\#605](https://github.com/apache/magpie/issues/605) | -| Forge / tracker | [`github`](../../tools/github/), [`jira`](../../tools/jira/), [`bitbucket`](../../tools/bitbucket/) `partial-read-only` foundation, [`sourcehut`](../../tools/sourcehut/), [`fossil`](../../tools/fossil/), [`gitlab`](../../tools/gitlab/) `partial-read-only` foundation | Forgejo/Gitea [\#310](https://github.com/apache/magpie/issues/310), Pagure [\#312](https://github.com/apache/magpie/issues/312), deeper Bitbucket/Jira coverage [\#606](https://github.com/apache/magpie/issues/606), GitLab [\#305](https://github.com/apache/magpie/issues/305), Bugzilla [\#302](https://github.com/apache/magpie/issues/302) | +| Forge / tracker | [`github`](../../tools/github/), [`forgejo`](../../tools/forgejo/) `partial` foundation, [`jira`](../../tools/jira/), [`bitbucket`](../../tools/bitbucket/) `partial-read-only` foundation, [`sourcehut`](../../tools/sourcehut/), [`fossil`](../../tools/fossil/), [`gitlab`](../../tools/gitlab/) `partial-read-only` foundation | Forgejo/Gitea [\#310](https://github.com/apache/magpie/issues/310), Pagure [\#312](https://github.com/apache/magpie/issues/312), deeper Bitbucket/Jira coverage [\#606](https://github.com/apache/magpie/issues/606), GitLab [\#305](https://github.com/apache/magpie/issues/305), Bugzilla [\#302](https://github.com/apache/magpie/issues/302) | | [`tools/chat`](../../tools/chat/) | [`chat-slack`](../../tools/chat-slack/) | Discord [#1421](https://github.com/apache/magpie/issues/1421) | | Agent harness | Claude Code, [Codex](codex.md) `experimental` ([#313](https://github.com/apache/magpie/issues/313)), [Gemini CLI](gemini.md) `experimental` ([#314](https://github.com/apache/magpie/issues/314)), [Local LLM (Ollama / llama.cpp / vLLM)](local-llm.md) ([#315](https://github.com/apache/magpie/issues/315)), [Cursor](cursor.md) ([#316](https://github.com/apache/magpie/issues/316)), [Goose](goose.md) `guide only` ([#319](https://github.com/apache/magpie/issues/319)), [Aider](aider.md) `guide only` ([#317](https://github.com/apache/magpie/issues/317)), [GitHub Copilot](copilot.md) `guide only` ([#318](https://github.com/apache/magpie/issues/318)), Grok `reviewer backend only` ([#1416](https://github.com/apache/magpie/issues/1416)) | Amazon Q [#320](https://github.com/apache/magpie/issues/320)–OpenHands [#322](https://github.com/apache/magpie/issues/322) | | Security cross-ref | [`tools/osv`](../../tools/osv/) | — | diff --git a/docs/labels-and-capabilities.md b/docs/labels-and-capabilities.md index 7abfbda56..9470fc567 100644 --- a/docs/labels-and-capabilities.md +++ b/docs/labels-and-capabilities.md @@ -326,6 +326,7 @@ or a contract-free mix of substrates (e.g. `tools/spec-inventory` is | [`tools/container-gateway`](../tools/container-gateway/) | `substrate:sandbox` | Per-project policy proxy for the podman / docker API; label-scoped, mount- and privilege-checked container access from inside the sandbox | | [`tools/forwarder-relay`](../tools/forwarder-relay/) | `contract:report-relay` | Adapter contract for inbound-relay backends (ASF Security relay, huntr.com, HackerOne triagers). Pure interface spec; adapters declare detection + credit-extraction + reporter-addressing rules. | | [`tools/bitbucket`](../tools/bitbucket/) | `contract:change-request` + `contract:tracker` | Coverage: `partial`. Bitbucket Cloud and Bitbucket Data Center bridge foundation for repository metadata context, branch restriction context for PR-management decisions, pull-request discovery/fetching, read-only commit fetching, read-only diff fetching, comments-only discussion fetching, read-only review-state fetching, Cloud-only pull-request task listing/fetching, read-only merge-check context fetching, and read-only status fetching, plus narrowly scoped Cloud pull-request comment creation and approve/unapprove actions. Tracker coverage includes Cloud-only issue listing/fetching, issue comment fetching, issue attachment metadata fetching, and confirmed issue-comment creation. The `partial` qualifier means this tool implements named contract operations but does not satisfy the complete contract and must not be counted as a complete/selectable backend. Broader pull-request review/mutation, broader issue writes, and linked Jira handoff coverage remain incomplete. | +| [`tools/forgejo`](../tools/forgejo/) | `contract:tracker` + `contract:source-control` + `contract:change-request` | Coverage: `partial`. Forgejo / Gitea REST API and `tea` CLI forge bridge foundation for issue listing/fetching, confirmed issue creation, body edits, comments, labels, and milestones under `contract:tracker`, git-backed branch/commit/push operations under `contract:source-control`, and pull-request creation via REST API / compare URL and label edits under `contract:change-request`. Project boards are unsupported (`no-op`) due to absence of REST card/column endpoints. The `partial` qualifier means this tool implements named contract operations but does not satisfy the complete contract and must not be counted as a complete/selectable backend. | | [`tools/fossil`](../tools/fossil/) | `contract:tracker` + `contract:source-control` | Fossil SCM forge bridge: integrates local SQLite-backed ticket tracking, wiki, and forum reads with the version-control shim | | [`tools/github`](../tools/github/) | `contract:tracker` + `contract:source-control` + `contract:change-request` | GitHub REST / GraphQL tracker substrate (called by every lifecycle phase) plus the Git source-control binding documented in [`source-control.md`](../tools/github/source-control.md) (runnable backend in [`tools/vcs`](../tools/vcs/)) and the pull-request review/merge gate (`change-request`; the ASF default backend, alongside `tools/jira-patch/` and `tools/mail-patch/` for SVN-first projects) | | [`tools/gitlab`](../tools/gitlab/) | `contract:tracker` + `contract:source-control` + `contract:change-request` | Coverage: `partial`. GitLab REST API v4 forge bridge foundation for repository metadata context under `contract:source-control`, issue listing/fetching under `contract:tracker`, and merge request discovery, diffs, commits, and CI pipeline status under `contract:change-request`. The `partial` qualifier means this tool implements named contract operations but does not satisfy the complete contract and must not be counted as a complete/selectable backend. Write operations, issue mutation, and merge request mutations remain out of scope for this foundation. | diff --git a/docs/vendor-neutrality.md b/docs/vendor-neutrality.md index 192c4a60f..8bd2567b3 100644 --- a/docs/vendor-neutrality.md +++ b/docs/vendor-neutrality.md @@ -576,9 +576,9 @@ generated block below. | Capability contract | Neutral? | Class | Backends today | Basis | |---|---|---|---|---| -| `contract:tracker` | ✅ | vendor-backed | Atlassian, Fossil, GitHub, SourceHut | 4 backend vendors: Atlassian, Fossil, GitHub, SourceHut; partial foundation, not counted: bitbucket, gitlab | -| `contract:source-control` | ✅ | vendor-backed | Fossil, Git, GitHub, SourceHut, Subversion | 5 backend vendors: Fossil, Git, GitHub, SourceHut, Subversion; partial foundation, not counted: gitlab | -| `contract:change-request` | ✅ | vendor-backed | Atlassian, GitHub, email | 3 backend vendors: Atlassian, GitHub, email; partial foundation, not counted: bitbucket, gitlab | +| `contract:tracker` | ✅ | vendor-backed | Atlassian, Fossil, GitHub, SourceHut | 4 backend vendors: Atlassian, Fossil, GitHub, SourceHut; partial foundation, not counted: bitbucket, forgejo, gitlab | +| `contract:source-control` | ✅ | vendor-backed | Fossil, Git, GitHub, SourceHut, Subversion | 5 backend vendors: Fossil, Git, GitHub, SourceHut, Subversion; partial foundation, not counted: forgejo, gitlab | +| `contract:change-request` | ✅ | vendor-backed | Atlassian, GitHub, email | 3 backend vendors: Atlassian, GitHub, email; partial foundation, not counted: bitbucket, forgejo, gitlab | | `contract:mail-archive` | ✅ | vendor-backed | ASF, Google, SourceHut | 3 backend vendors: ASF, Google, SourceHut | | `contract:chat` | ❌ | vendor-backed | Slack | only 1 backend vendor (Slack); needs 1 more | | `contract:mail-source` | ✅ | vendor-backed | ASF, Google, Maildir | 3 backend vendors: ASF, Google, Maildir | diff --git a/tools/forgejo/README.md b/tools/forgejo/README.md new file mode 100644 index 000000000..e8d7032ac --- /dev/null +++ b/tools/forgejo/README.md @@ -0,0 +1,37 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [`tools/forgejo/`](#toolsforgejo) + - [Prerequisites](#prerequisites) + - [Configuration](#configuration) + + + + + +# `tools/forgejo/` + +**Capability:** contract:tracker + contract:source-control + contract:change-request + +**Coverage:** partial + +**Kind:** implementation + +**Vendor:** Forgejo / Gitea + +Forgejo / Gitea REST substrate. Pure read/write wrapper used by every lifecycle phase (triage / intake / fix / resolve / stats). See [`tool.md`](tool.md) for the operation catalogue and the per-area files ([`issue-template.md`](issue-template.md), [`labels.md`](labels.md), [`operations.md`](operations.md), [`project-board.md`](project-board.md), [`status-rollup.md`](status-rollup.md)) for specifics. + +This tool implements three capability contracts: `contract:tracker` (issues / labels), `contract:source-control` (Git branch / commit / diff / push, documented in [`source-control.md`](source-control.md)), and `contract:change-request` — partial pull-request recipes driven by `tea pr` and REST endpoints. On Forgejo/Gitea the `change-request` `land` verb resolves to `tea pr merge` (the forge lands and closes atomically). + +## Prerequisites + +- **Runtime:** Bash — this is a doc-only adapter; skills invoke the `tea` CLI (`tea`) and `git`, no local package. +- **CLIs:** `tea` (authenticated), `git` (source-control capability), `curl`, `jq`. +- **Credentials / auth:** Every REST recipe in [`operations.md`](operations.md) (collaborator lookup, issue create, issue body edit, comments, PR create) sends `Authorization: token $TEA_TOKEN` to `$FORGEJO_HOST`, so both variables are required whenever those recipes run. The token value is stored in a private configuration file in the user's home directory (e.g. `~/.config/tea/token` or `~/.config/apache-magpie/user.md` per [`AGENTS.md` § Local setup](../../AGENTS.md#local-setup)) and exported to the agent's environment. `$FORGEJO_HOST` is the base URL including the scheme (`https://forge.example.org`), since the recipes build `$FORGEJO_HOST/api/v1/...` from it. For `tea` CLI operations, `tea login list` must show an authenticated login. +- **Network:** The Forgejo/Gitea instance host (`$FORGEJO_HOST`) must be added to the sandbox network allowlist, permitting access to the instance API (`/api/v1/`) and Git remote; source-control recipes are offline except explicit `fetch` / `push`. + +## Configuration + +Adopters select Forgejo-backed tracker, source-control, and change-request behavior through `/project.md` repository keys such as `tracker_repo`, `upstream_repo`, and the source-control / change-request entries in the *Tools enabled* table. Forgejo issue body fields, labels, and PR-management knobs live in the matching `/*-config.md` files documented from `projects/_template/README.md`. diff --git a/tools/forgejo/issue-template.md b/tools/forgejo/issue-template.md new file mode 100644 index 000000000..2268ac72e --- /dev/null +++ b/tools/forgejo/issue-template.md @@ -0,0 +1,116 @@ + + + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Forgejo / Gitea — issue-body field schema](#forgejo--gitea--issue-body-field-schema) + - [Where the schema is authoritative](#where-the-schema-is-authoritative) + - [Field roles the skills use](#field-roles-the-skills-use) + - [Body-field surgery](#body-field-surgery) + - [Empty-field convention](#empty-field-convention) + - [Issue-template to CVE 5.x mapping](#issue-template-to-cve-5x-mapping) + + + +# Forgejo / Gitea — issue-body field schema + +The skills treat every tracker's **issue body** as a structured +document with named fields, rather than free-form prose. Each field is +a markdown `### ` heading followed by a value block; the +set of field names is the schema the skills read from and write to. + +The field names themselves are project-specific (they come from the +project's Forgejo issue-template configuration). Generic skill logic refers to +them by **role** — *"the CVE-tool-link field"*, *"the public-advisory +URL field"* — and the role → concrete-name mapping is declared in the +project manifest. For an adopting project, see +[`../..//project.md`](../..//project.md#issue-template-fields). + +## Where the schema is authoritative + +Three surfaces need to stay in lock-step whenever a field is added, +renamed, or removed: + +1. **The Forgejo Template** — `.gitea/ISSUE_TEMPLATE/issue_report.yaml` (or equivalent `.forgejo/`) + in the tracker repo. This is what Forgejo renders as the "New + issue" form and is the machine-readable schema. +2. **The project manifest** — `/project.md` declares + the concrete field name each skill role maps to. Renaming the + field in the YAML requires updating this mapping. +3. **The skills that write fresh issue bodies** — `security-issue-import` + emits a heredoc body with the full field set; when the schema + changes, the heredoc must change in lock-step. + +No skill parses the YAML at runtime. The field list is hand- +maintained in the project manifest and in the `security-issue-import` +heredoc, and the three surfaces are kept aligned by convention. + +## Field roles the skills use + +The generic lifecycle refers to fields by these roles: + +| Role | Read by | Written by | Purpose | +|---|---|---|---| +| `issue-description` | dedupe | import | The verbatim inbound report; private to the security team. | +| `public-summary` | CVE JSON generator | release manager (Step 13) | Sanitised one-paragraph public summary for the advisory. | +| `affected-versions` | CVE JSON generator, sync | sync proposes, user confirms | The `>= X, < Y` range that populates CVE 5.x `affected[]`. | +| `security-thread` | dedupe, sync (reporter-notification lookup) | import | Private pointer to the inbound mail thread — the Gmail `threadId`, any PonyMail archive URL, and the root `Message-ID` (archive-independent message handle; backtick-wrapped). **Never** exported to the public CVE record. | +| `public-advisory-url` | CVE JSON generator, sync (gates close) | sync (Step 14) | Public archive URL; tagged `vendor-advisory` in `references[]`. | +| `reporter-credit` | CVE JSON generator | import (placeholder), sync (after reporter confirms) | Credit line as the reporter wants to appear in the public advisory. | +| `pr-with-fix` | sync, CVE JSON generator | fix, sync | URL of the merged `` PR. | +| `remediation-developer` | CVE JSON generator | sync (auto-populated from `pr-with-fix` author when set; manual edits preserved) | Person(s) who authored the fix; one credit per line. | +| `cwe` | CVE JSON generator | sync proposes, user confirms | CWE number for the CVE 5.x `problemTypes[]`. | +| `severity` | CVE JSON generator | sync proposes, user confirms | CVSS severity; never copy the reporter's self-assigned value. | +| `cve-tool-link` | sync, security-cve-allocate (blocker check) | security-cve-allocate | Canonical link to the CVE record in the project's CVE tool. | + +The concrete field names each role maps to for the adopting project +live in the project manifest. + +## Body-field surgery + +Skills that update one field without touching the rest use the +following pattern: + +1. **Read** the full body (`curl -fsS -H "Authorization: token $TEA_TOKEN" "$FORGEJO_HOST/api/v1/repos//issues/" | jq -r .body` or `tea issues --repo --output json | jq -r .body`). +2. **Split** on `\n### ` to get per-field sections. +3. **Replace** the target section's value (between its `### \n\n` + header and the next `### ` or end-of-body). +4. **Join** the sections back together. +5. **Write** the new body via the REST API PATCH endpoint (`curl -fsS -X PATCH -H "Authorization: token $TEA_TOKEN" -H "Content-Type: application/json" --data-binary @/issue-body.json "$FORGEJO_HOST/api/v1/repos//issues/"` with the payload formatted via the Write tool per [`operations.md`](operations.md#edit--body)). + +Never construct the body by string concatenation in a shell command — +literal backticks, `$(…)`, and newlines in the body are silently +corrupted by shell quoting. Always materialise the edited body to a +temp file first. + +## Empty-field convention + +Forgejo's issue-form renderer behaves similarly to GitHub's. The skills honour this convention: + +- On **read**, treat `_No response_` as "field unset". +- On **write**, preserve `_No response_` in fields the triager has + not yet filled (do not collapse them to empty strings or delete + the heading). +- The CVE JSON generator treats `_No response_` as absence and simply + omits the corresponding CVE-record element. + +## Issue-template to CVE 5.x mapping + +The `generate-cve-json` tool maps body-field roles to CVE 5.x record +elements as follows (generic — applies to any project using this +schema): + +| Body field role | CVE 5.x element | +|---|---| +| `public-summary` | `descriptions[].value` | +| `affected-versions` | `affected[].versions[].version` / `.lessThan` | +| `cwe` | `problemTypes[].descriptions[].cweId` | +| `severity` | `metrics[].other.content` (textual severity) | +| `public-advisory-url` | `references[]` with `tags: ["vendor-advisory"]` | +| `pr-with-fix` | `references[]` with `tags: ["patch"]` | +| `reporter-credit` | `credits[]` with `type: "finder"` | +| `remediation-developer` | `credits[]` with `type: "remediation developer"` | +| `security-thread` | **not exported** — private-only | +| `cve-tool-link` | **not exported** — points at the tool itself, not at a public URL | diff --git a/tools/forgejo/labels.md b/tools/forgejo/labels.md new file mode 100644 index 000000000..2629fc190 --- /dev/null +++ b/tools/forgejo/labels.md @@ -0,0 +1,75 @@ + + + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Forgejo / Gitea — lifecycle label taxonomy](#forgejo--gitea--lifecycle-label-taxonomy) + - [Lifecycle labels](#lifecycle-labels) + - [Closing-disposition labels](#closing-disposition-labels) + - [Secondary labels](#secondary-labels) + - [Maintenance](#maintenance) + + + +# Forgejo / Gitea — lifecycle label taxonomy + +The **generic** label taxonomy every Forgejo/Gitea-backed tracker shares. +These labels drive the state machine the skills reconcile; their +spellings and meanings are project-agnostic and stable across projects +that reuse this framework. + +Project-specific labels — in particular the **scope labels** that pin +a tracker to a product family — live in the adopting project's +directory. See +[`../..//scope-labels.md`](../..//scope-labels.md). + +The end-to-end state diagram that combines these labels into a +lifecycle lives in [`../../README.md`](../../README.md). + +## Lifecycle labels + +| Label | Meaning | Added at process step | Removed at process step | +|---|---|---|---| +| `needs triage` | Freshly filed; assessment not yet started. | 1 (set automatically by the issue template) | 5 | +| *scope label* | Project-specific scope pin (e.g. `` / ``). Exactly one is set after triage. Per-project definitions live in the project directory. | 5 | never (sticks for the lifetime of the issue) | +| `cve allocated` | A CVE has been reserved for the issue. Allocation is gated by the project's CVE-tool policy (see the project manifest's `cve_allocation_gated_by` value). | 6 | never | +| `pr created` | A public fix PR has been opened on the upstream repository but has not yet merged. | 10 | 11 (replaced by `pr merged`) | +| `pr merged` | The fix PR has merged upstream; no release carrying the fix has shipped yet. | 11 | 12 (replaced by `fix released` when the release ships) | +| `fix released` | A release carrying the fix has shipped to users; advisory has not been sent yet. | 12 | 13 (replaced by `announced - emails sent`) | +| `announced - emails sent` | The public advisory has been sent to the project's `announce` / `users` mailing lists. The issue **stays open** after this label is applied; closing is gated on the RM completing Step 15. | 13 | never (stays on the issue after closing for audit history) | +| `announced` | The public advisory URL has been captured in the tracking issue's *Public advisory URL* body field and the attached CVE JSON has been regenerated so its `references[]` now carries the `vendor-advisory` URL. | 14 | never (stays on the issue after closing) | + +## Closing-disposition labels + +Applied when a tracker leaves the lifecycle without producing a CVE. +These are mutually exclusive — a tracker closes with exactly one of: + +| Label | Meaning | +|---|---| +| `invalid` | Report is not a vulnerability per the project's Security Model. | +| `not CVE worthy` | Reproducible but not severe / scoped enough to warrant a CVE (e.g. self-XSS, DoS by authenticated admin). | +| `duplicate` | Root-cause-equivalent to another tracker; kept tracker carries the CVE. See the `security-issue-deduplicate` skill. | +| `wontfix` | Will not be fixed (e.g. feature-not-bug, deprecated surface being removed in the next release). | + +## Secondary labels + +These do not gate state transitions but carry coordination signals. + +| Label | Meaning | Scope | +|---|---|---| +| `security issue` | Applied by the issue template. Flags the issue as security-related for the UI and any org-level filters. | Generic — applied by the issue template. | +| Backport labels (e.g. `backport-to-v3-2-test`) | Project-specific — applied on the **public upstream PR**, not on the private tracker. Trigger the project's backport automation. | Project-specific; see the per-project fix-workflow file ([`../..//fix-workflow.md#backport-labels`](../..//fix-workflow.md#backport-labels)). | + +## Maintenance + +The `security-issue-sync` skill is the authority on label transitions +— on every run it detects the current state (labels + body fields + +fix-PR state + release state) and proposes the label transitions the +process requires. + +Adding a new generic lifecycle label is a **process change** that +should be proposed, reviewed, and merged in the same PR that adds the +label to `` via `tea labels create` (see +[`operations.md`](operations.md#create-2)). diff --git a/tools/forgejo/operations.md b/tools/forgejo/operations.md new file mode 100644 index 000000000..8e5b28e97 --- /dev/null +++ b/tools/forgejo/operations.md @@ -0,0 +1,235 @@ + + + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Forgejo / Gitea — CLI and API operation catalogue](#forgejo--gitea--cli-and-api-operation-catalogue) + - [Authentication](#authentication) + - [Collaborator lookup (security-team roster)](#collaborator-lookup-security-team-roster) + - [Issues](#issues) + - [Read](#read) + - [Create](#create) + - [Edit — labels](#edit--labels) + - [Edit — assignees](#edit--assignees) + - [Edit — body](#edit--body) + - [Comment](#comment) + - [Close / reopen](#close--reopen) + - [Milestones](#milestones) + - [List](#list) + - [Create](#create-1) + - [Assign to an issue](#assign-to-an-issue) + - [Labels](#labels) + - [List](#list-1) + - [Create](#create-2) + - [Pull requests](#pull-requests) + - [Create (public PR on the upstream repo)](#create-public-pr-on-the-upstream-repo) + - [Edit — backport / other labels](#edit--backport--other-labels) + - [Cross-link from the public PR back to the private tracker](#cross-link-from-the-public-pr-back-to-the-private-tracker) + - [Projects](#projects) + - [Error handling](#error-handling) + + + +# Forgejo / Gitea — CLI and API operation catalogue + +Shared reference for the `tea` CLI (official Gitea/Forgejo CLI) and REST API invocations the skills use against the project's tracker repository. The skills reference this file for the recipe shape; each inline command in a skill already substitutes the tracker repo slug from the adopting project's manifest (see [`../..//project.md`](../..//project.md#repositories)). + +Placeholder convention used below: + +- `` — the tracker repository slug from `.tracker_repo`. +- `` — the upstream codebase slug from `.upstream_repo`. +- `` — issue or PR number. +- `$FORGEJO_HOST` / `$TEA_TOKEN` — environment variables for REST API fallbacks. + +## Authentication + +Every skill's Step 0 pre-flight must verify that `tea` is authenticated: + +```bash +tea --version # must show installed version +tea login list # must show logged-in user / server +``` + +A non-zero exit on either command is a hard stop — the skill reports the failure and asks the user to `tea login add` rather than retrying. +Subshell fetching like `$(gh auth token)` is avoided to comply with environment restrictions. +Note that `tea` CLI credentials come from `tea login add` (or `GITEA_SERVER_URL` / `GITEA_SERVER_TOKEN`); `$TEA_TOKEN` and `$FORGEJO_HOST` are environment variables required specifically for the REST API fallback recipes below. + +## Collaborator lookup (security-team roster) + +Using the REST API fallback (since `tea` lacks a direct collaborators listing command), iterating pages until empty to ensure full roster retrieval without truncation: + +```bash +page=1 +while :; do + curl -fsS -H "Authorization: token $TEA_TOKEN" \ + "$FORGEJO_HOST/api/v1/repos//collaborators?limit=50&page=$page" > /collabs.json || exit 1 + jq -e 'type == "array"' /collabs.json >/dev/null || exit 1 + if jq -e '. == []' /collabs.json >/dev/null; then + break + fi + jq -r '.[].login' /collabs.json + page=$((page + 1)) +done +``` + +The authoritative "who is on the security team" list. Every collaborator counts regardless of permission level. Roster snapshots maintained in the project manifest files are caches of this command's output and can drift between changes. + +## Issues + +### Read + +```bash +tea issues --repo --output json +``` + +When reading issue comments, you can use the REST API: +```bash +curl -fsS -H "Authorization: token $TEA_TOKEN" \ + "$FORGEJO_HOST/api/v1/repos//issues//comments" | jq . +``` + +### Create + +A tracker title almost always derives from attacker-controlled text, so it **must not** be inlined into a shell argument or spliced with subshell commands. Use the Write tool (not Bash) to construct a JSON payload containing the title, body, and labels, then submit via the REST API: + +*Write tool call:* `file_path: /issue-payload.json`, `content: {"title": "", "body": "<body>", "labels": [<label-ids>]}` + +Title and body derive from reporter text: format `<scratch>/issue-payload.json` using the Write tool ensuring properly escaped JSON for any quotes, backslashes, or newlines. The `labels` array takes integer label IDs (retrieved from `tea labels ls --output json` or `GET /api/v1/repos/<tracker>/labels`), not string label names. + +```bash +curl -fsS -X POST -H "Authorization: token $TEA_TOKEN" \ + -H "Content-Type: application/json" \ + --data-binary @<scratch>/issue-payload.json \ + "$FORGEJO_HOST/api/v1/repos/<tracker>/issues" | jq . +``` + +### Edit — labels + +```bash +tea issues edit <N> --repo <tracker> \ + --add-labels '<label-a>,<label-b>' \ + --remove-labels '<label-c>' +``` + +Apply every add + remove in **one** call so the change lands as a single audit-trail entry. + +### Edit — assignees + +```bash +tea issues edit <N> --repo <tracker> --add-assignees @me +tea issues edit <N> --repo <tracker> --add-assignees <handle> +``` + +### Edit — body + +Because `tea issues edit` does not support a `--description-file` flag (only inline `-d, --description string`, which violates the rule against passing bodies as quoted arguments), issue body edits use the REST API PATCH endpoint. +Write the edited body to `<scratch>/issue-body.json` as a JSON payload using the Write tool: + +*Write tool call:* `file_path: <scratch>/issue-body.json`, `content: {"body": "<edited body>"}` + +Then apply the update: + +```bash +curl -fsS -X PATCH -H "Authorization: token $TEA_TOKEN" \ + -H "Content-Type: application/json" \ + --data-binary @<scratch>/issue-body.json \ + "$FORGEJO_HOST/api/v1/repos/<tracker>/issues/<N>" | jq . +``` + +### Comment + +```bash +curl -fsS -X POST -H "Authorization: token $TEA_TOKEN" \ + -H "Content-Type: application/json" \ + --data-binary @<body-json-file> \ + "$FORGEJO_HOST/api/v1/repos/<tracker>/issues/<N>/comments" +``` +(Format the `<body-json-file>` using Write tool to ensure properly escaped JSON `{"body": "..."}`) + +Before posting, **scrub the comment body for bare-name mentions** of project maintainers and replace with `@`-handles. + +### Close / reopen + +```bash +tea issues close <N> --repo <tracker> +tea issues reopen <N> --repo <tracker> +``` + +## Milestones + +### List + +```bash +tea milestones ls --repo <tracker> --output json +``` + +### Create + +```bash +tea milestones create --repo <tracker> \ + --title '<target>' \ + --description '<optional one-line description>' +``` + +### Assign to an issue + +```bash +tea issues edit <N> --repo <tracker> --milestone '<title>' +``` + +## Labels + +### List + +```bash +tea labels ls --repo <tracker> --output json +``` + +### Create + +```bash +tea labels create --repo <tracker> \ + --name '<name>' \ + --description '<short description>' \ + --color '<hex>' +``` + +Do **not** silently create labels without asking the user. + +## Pull requests + +### Create (public PR on the upstream repo) + +Opening a public PR is irreversible. In GitHub workflows, `--web` is load-bearing so a human reviews scrubbed titles and Gen-AI disclosures in-browser before publishing. Since `tea pulls create` publishes immediately without an interactive browser check and lacks a `--description-file` flag (accepting only inline `-d, --description string`), the calling skill must: +1. Emit the interactive browser compare URL for human creation: + `$FORGEJO_HOST/<upstream>/compare/<base-branch>...<user>:<branch>` +2. Or, if creating via API, display the scrubbed title, body, and Gen-AI disclosure to the user and obtain explicit interactive confirmation before executing: + +*Write tool call:* `file_path: <scratch>/pr-payload.json`, `content: {"title": "<scrubbed title>", "body": "<body>", "head": "<user>:<branch>", "base": "<base-branch>"}` + +```bash +curl -fsS -X POST -H "Authorization: token $TEA_TOKEN" \ + -H "Content-Type: application/json" \ + --data-binary @<scratch>/pr-payload.json \ + "$FORGEJO_HOST/api/v1/repos/<upstream>/pulls" | jq . +``` + +### Edit — backport / other labels + +```bash +tea pr edit <N> --repo <upstream> --add-labels '<backport-label>' +``` + +### Cross-link from the public PR back to the private tracker + +**Forbidden.** The public PR body and any follow-up public comment must not reveal the CVE, the security nature, or the private tracker URL. Enforce via the scrub step before writing the PR body. + +## Projects + +See [`project-board.md`](project-board.md) — Forgejo/Gitea project boards lack REST API card/column endpoints, so board reconciliation is a no-op across skills. + +## Error handling + +If any state-changing command fails, **stop the apply loop**, report the failure verbatim, and ask the user how to proceed — do not guess. diff --git a/tools/forgejo/project-board.md b/tools/forgejo/project-board.md new file mode 100644 index 000000000..b6e5fd978 --- /dev/null +++ b/tools/forgejo/project-board.md @@ -0,0 +1,30 @@ +<!-- SPDX-License-Identifier: Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0 --> + +<!-- START doctoc generated TOC please keep comment here to allow auto update --> +<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Forgejo / Gitea — Project boards](#forgejo--gitea--project-boards) + - [No GraphQL or board REST API support](#no-graphql-or-board-rest-api-support) + - [When the board is a no-op](#when-the-board-is-a-no-op) + +<!-- END doctoc generated TOC please keep comment here to allow auto update --> + +# Forgejo / Gitea — Project boards + +This file documents the project board integration for Forgejo/Gitea. + +> [!NOTE] +> **Board reconciliation unsupported (No-Op):** Neither Forgejo nor Gitea exposes a stable, released REST or GraphQL API for programmatically managing project boards, columns, or cards (boards are UI-only). Board reconciliation for Forgejo/Gitea trackers is therefore a **no-op**. + +## No GraphQL or board REST API support + +Unlike GitHub (which supports GraphQL Projects V2), Forgejo and Gitea do not provide API endpoints for moving cards across board columns. Skills interacting with a Forgejo tracker must treat board operations as unsupported and skip board-column reconciliation. + +## When the board is a no-op + +Not every project runs a project board. For Forgejo-backed adopters, project-board reconciliation is a no-op: +1. Skills skip board status updates and column movements. +2. Tracker state transitions are tracked exclusively through issue labels (`needs triage`, `cve allocated`, `pr created`, `pr merged`, `fix released`, `announced`), milestones, and issue body fields. +3. Sync skills skip board column verification without failing the sync. diff --git a/tools/forgejo/source-control.md b/tools/forgejo/source-control.md new file mode 100644 index 000000000..213de4997 --- /dev/null +++ b/tools/forgejo/source-control.md @@ -0,0 +1,101 @@ +<!-- SPDX-License-Identifier: Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0 --> + +<!-- START doctoc generated TOC please keep comment here to allow auto update --> +<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Forgejo / Gitea — source-control (VCS) capability](#forgejo--gitea--source-control-vcs-capability) + - [What the skills require](#what-the-skills-require) + - [Distributed-VCS assumptions](#distributed-vcs-assumptions) + - [When to replace this capability](#when-to-replace-this-capability) + +<!-- END doctoc generated TOC please keep comment here to allow auto update --> + +# Forgejo / Gitea — source-control (VCS) capability + +Shared reference for the **version-control operations** the skills run +against a local working copy of the project's source. On the Forgejo +tool this capability is backed by **Git** (`git` + `git worktree`), +which is Forgejo/Gitea's native VCS. + +This is a *distinct* capability from the tracker / project-board +surface documented in [`operations.md`](operations.md): those recipes +talk to the Forgejo API over `tea` / `curl`; the recipes here operate on a local +checkout with the `git` binary and never touch the network except for +explicit `fetch` / `push`. A project can in principle pair Forgejo's +tracker with a different VCS, or a different forge with Git — see +[*When to replace this capability*](#when-to-replace-this-capability). + +This contract has a runnable implementation in +[`tools/vcs/`](../vcs/README.md) (`magpie-vcs`): one abstract +`VCSBackend` interface, a complete Git backend, and detected extension +points for the non-Git bridges. A skill can call the abstract operation +(`magpie-vcs diff`, `magpie-vcs log`) instead of a raw `git` command and +let the tool dispatch to whichever backend governs the working copy. The +Git-binding tables below are the contract that tool implements. + +## What the skills require + +The dev-loop skills (`issue-fix-workflow`, `pr-management-code-review`, +`issue-reproducer`, `issue-reassess`) and the `magpie-setup` worktree +machinery rely on the following abstract operations. The Git binding is +shown alongside each; a sibling VCS tool provides its own binding for +the same abstract operation. + +| Abstract operation | Git binding | Used by | +|---|---|---| +| Locate the repo root / common dir | `git rev-parse --show-toplevel` / `--git-common-dir` / `--git-dir` | worktree setup, all dev-loop skills | +| Inspect working-copy state | `git status` (`-s` / `--short`) | fix-workflow, code-review pre-flight | +| Create / switch a line of work | `git checkout` / `git switch`, `git branch` | fix-workflow (branch per fix) | +| Stage + record a change | `git add`, `git commit -m` | fix-workflow | +| Show changes | `git diff` (`--cached`, `<base>`) | code-review, fix-workflow | +| History read | `git log` (`--oneline` / `--grep` / `--author` / `--since`), `git show` | reproducer, reassess, nomination | +| Determine divergence base | `git merge-base`, `git rev-parse` | code-review (diff against base) | +| Sync with the forge | `git fetch [origin]`, `git push [-u]` | fix-workflow hand-off | +| Re-apply onto an updated base | `git rebase` | triage rebase action | +| Isolated checkouts | `git worktree add` / `list --porcelain` | `magpie-setup` worktree flow | +| Park uncommitted work | `git stash --include-untracked` | fix-workflow safety | + +Write-path operations (`commit`, `push`, `rebase`) stay gated on +explicit user confirmation in the calling skill, exactly as the +tracker write paths are. + +## Distributed-VCS assumptions + +The Git binding assumes a **distributed** model: local commits, +cheap branches, a `worktree` primitive, and a `fetch`/`push` split +from the forge. Skills written against this capability should treat +those as the *abstract* contract, not as guaranteed primitives — a +centralized backend (e.g. Subversion, Perforce) maps "branch + local +commit + push" onto a different shape (changelists, server-side +branches) and its tool doc must spell out the divergence. + +## When to replace this capability + +Source control is a separable capability: any VCS that can provide the +abstract operations above can be plugged in by creating a sibling +`tools/<vcs>/` directory with its own `source-control.md` binding and +listing it in the project manifest under *Tools enabled* (the +*Source control* row). The generic skill logic — *"branch off the +default branch, commit the fix, push for review"* — does not change +when the VCS changes; only the bindings in the tool doc do. + +Tracked VCS bridges that implement this capability against a non-Git +backend: + +- Mercurial (Hg) — apache/magpie#601 (generic VCS binding; + [`tools/vcs/`](../vcs/)) +- Apache Subversion (SVN) — apache/magpie#602 (generic VCS binding); + [`tools/asf-svn/`](../asf-svn/) packages the full ASF SVN surface + (source control + `dist.apache.org` release distribution + + authorization) for ASF projects +- Jujutsu (jj) — apache/magpie#603 +- Fossil — [`tools/fossil/`](../fossil/) +- Perforce / Helix Core — apache/magpie#605 + +Forge bridges that pair a non-GitHub forge with this capability live +alongside the tracker bridges ([`tools/gitlab/`](../gitlab/), +[`tools/bitbucket/`](../bitbucket/), [`tools/sourcehut/`](../sourcehut/) — +the last also exercising the Hg binding), and the Git reference in +[`tools/github/source-control.md`](../github/source-control.md). diff --git a/tools/forgejo/status-rollup.md b/tools/forgejo/status-rollup.md new file mode 100644 index 000000000..494d821cd --- /dev/null +++ b/tools/forgejo/status-rollup.md @@ -0,0 +1,107 @@ +<!-- SPDX-License-Identifier: Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0 --> + +<!-- START doctoc generated TOC please keep comment here to allow auto update --> +<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Forgejo / Gitea — Status-rollup comment](#forgejo--gitea--status-rollup-comment) + - [The rollup comment shape](#the-rollup-comment-shape) + - [Summary — action labels](#summary--action-labels) + - [The entry body](#the-entry-body) + - [Upsert recipe — append to an existing rollup, or create one](#upsert-recipe--append-to-an-existing-rollup-or-create-one) + +<!-- END doctoc generated TOC please keep comment here to allow auto update --> + +# Forgejo / Gitea — Status-rollup comment + +Every agent-authored status update on a `<tracker>` issue (the import +receipt, each sync pass, CVE allocation, dedupe merges, fix-PR +announcements, etc.) lands in **one single rollup comment** per +tracker. Each pass appends a new *entry* to that comment instead of +posting a fresh one. The result: scrolling a tracker's timeline shows +one rollup comment plus the human discussion, not twenty bot comments +drowning out the actual conversation. + +This file is the canonical shape + upsert recipe for Forgejo/Gitea. + +## The rollup comment shape + +One comment per tracker, identified by an opening HTML marker on the +first line: + +```markdown +<!-- <tracker-name> status rollup v1 — all bot-authored status updates fold into this single comment. --> +<details><summary>YYYY-MM-DD · @user · <Action></summary> + +<entry body> + +</details> + +--- + +<details><summary>YYYY-MM-DD · @user · <Action></summary> + +<entry body> + +</details> +``` + +Rules (all load-bearing — breaking any of them breaks Markdown rendering): + +- **First line is the marker.** `<!-- <tracker-name> status rollup v1 — … -->` + identifies the comment as the rollup. +- **Every entry is its own `<details>` block.** Including the very + first one (the import receipt). +- **Open tag is one line.** Write `<details><summary>…</summary>` on + a single line. +- **Summary contains three fields, `·`-separated**, in this order: + `YYYY-MM-DD · @handle · <Action>`. Optional fourth field in parentheses is allowed only for disambiguation. +- **Exactly one blank line after `<summary>…</summary>`.** +- **Exactly one blank line before `</details>`.** +- **No leading whitespace on any line inside the entry.** +- **Entries are separated by a bare `---` on its own line**, with one + blank line on each side. +- **Chronological order — newest at the bottom.** + +## Summary — action labels + +Each skill emits one of the following `<Action>` strings so the summary +line tells the reader at a glance *what* the entry represents: + +| Emitting skill | `<Action>` value | Optional parenthetical | +|---|---|---| +| `security-issue-import` | `Import` | class + reporter, e.g. `Import (Report, Jane Doe)` | +| `security-issue-sync` | `Sync` | one-phrase headline | +| `security-cve-allocate` | `CVE allocated` | the allocated ID | +| `security-issue-deduplicate` | `Merge (kept)` / `Merge (dropped)` | counterpart number | +| `security-issue-fix` | `Fix PR` | upstream PR number | + +## The entry body + +Inside the `<details>` block, write what the skill used to write in +its pre-collapse body — the bold headline, the `**Next:**` line, the +reporter-notification line, the full rationale. + +Required elements inside every entry body: + +- **Bold headline** as the first line (e.g. `**Sync 2026-04-21 — pr merged → fix released.**`). +- **`**Next:**` line** — one sentence on what comes next. +- **Reporter-notification line** when applicable. + +## Upsert recipe — append to an existing rollup, or create one + +To append an entry to an existing rollup comment on Forgejo/Gitea, the agent must: + +1. **Find the existing rollup comment:** + Fetch the issue comments and find the one starting with `<!-- <tracker-name> status rollup v1`: + ```bash + curl -fsS -H "Authorization: token $TEA_TOKEN" \ + "$FORGEJO_HOST/api/v1/repos/<tracker>/issues/<N>/comments" > <scratch>/comments.json || exit 1 + # Use jq to find the comment ID and body + ``` +2. **Append or Create:** + - If found, append the new `<details>...` block to the existing body and update the comment using `PATCH /api/v1/repos/<tracker>/issues/comments/<comment-id>`. + - If not found, create a new comment with the marker and the first `<details>...` block using `POST /api/v1/repos/<tracker>/issues/<N>/comments`. + +Always use a temporary file to construct the JSON payload properly before sending via `curl`. diff --git a/tools/forgejo/tool.md b/tools/forgejo/tool.md new file mode 100644 index 000000000..75793f3d4 --- /dev/null +++ b/tools/forgejo/tool.md @@ -0,0 +1,79 @@ +<!-- START doctoc generated TOC please keep comment here to allow auto update --> +<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Tool: Forgejo / Gitea](#tool-forgejo--gitea) + - [What this tool provides](#what-this-tool-provides) + - [When to replace this tool with another](#when-to-replace-this-tool-with-another) + - [Confidentiality note](#confidentiality-note) + +<!-- END doctoc generated TOC please keep comment here to allow auto update --> + +<!-- SPDX-License-Identifier: Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0 --> + +# Tool: Forgejo / Gitea + +This directory documents the **Forgejo** (and Gitea) tool adapter — the set of +capabilities the skills use when the adopting project declares Forgejo/Gitea +as its issue-tracking / source-control / change-request backend. + +A project opts into this tool by naming it in its manifest under +*Tools enabled*. For the adopting project see +[`../../<project-config>/project.md`](../../<project-config>/project.md#tools-enabled). + +## What this tool provides + +The skills use Forgejo for distinct capabilities. Each has its own +reference file in this directory: + +| Capability | File | What it covers | +|---|---|---| +| CLI / API operations | [`operations.md`](operations.md) | `tea` CLI + REST API recipes the skills invoke (issue edit, milestone create, label edit, comment post, PR create, collaborator lookup, auth sanity check) | +| Source control (VCS) | [`source-control.md`](source-control.md) | The version-control operations the dev-loop skills run on a local checkout — branch/commit/diff/log/fetch/push/worktree — backed by Git on the Forgejo tool; the separable capability the non-Git VCS bridges plug into | +| Issue-body schema | [`issue-template.md`](issue-template.md) | The body-field schema pattern: skills read named `### <field>` sections from the issue body; the per-project field names are declared in the project manifest | +| Lifecycle labels | [`labels.md`](labels.md) | Generic lifecycle-label taxonomy (`needs triage`, `cve allocated`, `pr created`, `pr merged`, `fix released`, `announced - emails sent`, `announced`, closing dispositions) | +| Project board | [`project-board.md`](project-board.md) | Documented as unsupported / no-op across skills (Forgejo/Gitea boards lack REST card/column management APIs) | +| Credentials | [`operations.md#authentication`](operations.md#authentication) | `tea login list` pre-flight that every skill's Step 0 runs | + +## When to replace this tool with another + +The generic skills are written around an abstract *"issue tracker with +body fields, labels, milestones, comments, and a CLI"*, plus a +*"source-control working copy with branches, commits, diffs, history, +and a fetch/push split"*. Any backend that provides those primitives +can be plugged in by: + +1. Creating a sibling `tools/<name>/` directory with the same files + (`tool.md`, `operations.md`, `source-control.md`, + `issue-template.md`, `labels.md`, `project-board.md` — the last + only if the backend has a board-equivalent, and `source-control.md` + only for the VCS capability). +2. Listing that tool in the project's manifest under *Tools enabled*. +3. Declaring the backend-specific values (repo slug / project key / URL + templates / field names / board IDs) in the project manifest. + +The two capabilities are **separable**: the source-control capability +([`source-control.md`](source-control.md)) can be backed by a +different VCS than the tracker (e.g. Forgejo issues over a Mercurial or +Subversion working copy), so a +sibling tool may implement only the VCS binding and leave the rest to +the Forgejo tool — or vice versa. + +An alternate forge or tracker adapter would replace: + +- `tea issues` / Forgejo REST calls with its own API / CLI tooling; +- body-field `### <name>` sections with the target system's custom fields or issue description schema; +- Forgejo labels with the target forge's label taxonomy. + +The generic skill logic — *"when CVE is allocated, transition the tracker state to `CVE allocated`"* — does not change when the tool changes. + +## Confidentiality note + +Some of the recipes in the sibling files ([`operations.md`](operations.md), [`source-control.md`](source-control.md)) operate on the **private** tracker repo +(e.g. `<tracker>` for the adopting project) and others on a +public repo (e.g. `<upstream>`). The confidentiality rules in +[`../../AGENTS.md`](../../AGENTS.md) still bind regardless of which +tool is in use: anything that lands on a public surface must be +scrubbed for the project's private-tracker URLs, CVE IDs, and +security-nature signals. diff --git a/tools/github/source-control.md b/tools/github/source-control.md index ae2dd0355..638c84f71 100644 --- a/tools/github/source-control.md +++ b/tools/github/source-control.md @@ -93,6 +93,6 @@ backend: - Perforce / Helix Core — apache/magpie#605 Forge bridges that pair a non-GitHub forge with this capability live -alongside the tracker bridges (GitLab #305, Forgejo/Gitea #310, +alongside the tracker bridges ([`tools/forgejo/`](../forgejo/), GitLab #305, Bitbucket #606, SourceHut #607 — the last also exercising the Hg binding). From c86e8706efb63e822a5e20b4370369b1832e3c07 Mon Sep 17 00:00:00 2001 From: Jarek Potiuk <potiuk@apache.org> Date: Mon, 5 Oct 2026 15:48:49 +0200 Subject: [PATCH 2/2] docs(tools/forgejo): drop the @me assignee form and note JSON escaping `tea issues edit --add-assignees` (0.15.1) takes a comma-separated list of usernames and does not resolve `@me`, so the recipe would assign a literal "@me". Keep only the `<handle>` form. The body-edit and PR-create Write-tool payloads carry multi-line text, so say they must be properly escaped JSON, as the issue-create and comment recipes already do. Generated-by: Claude Opus 5 --- tools/forgejo/operations.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tools/forgejo/operations.md b/tools/forgejo/operations.md index 8e5b28e97..63da935a4 100644 --- a/tools/forgejo/operations.md +++ b/tools/forgejo/operations.md @@ -118,7 +118,6 @@ Apply every add + remove in **one** call so the change lands as a single audit-t ### Edit — assignees ```bash -tea issues edit <N> --repo <tracker> --add-assignees @me tea issues edit <N> --repo <tracker> --add-assignees <handle> ``` @@ -127,7 +126,7 @@ tea issues edit <N> --repo <tracker> --add-assignees <handle> Because `tea issues edit` does not support a `--description-file` flag (only inline `-d, --description string`, which violates the rule against passing bodies as quoted arguments), issue body edits use the REST API PATCH endpoint. Write the edited body to `<scratch>/issue-body.json` as a JSON payload using the Write tool: -*Write tool call:* `file_path: <scratch>/issue-body.json`, `content: {"body": "<edited body>"}` +*Write tool call:* `file_path: <scratch>/issue-body.json`, `content: {"body": "<edited body>"}` — the body is multi-line text, so write properly escaped JSON (quotes, backslashes and newlines escaped). Then apply the update: @@ -207,7 +206,7 @@ Opening a public PR is irreversible. In GitHub workflows, `--web` is load-bearin `$FORGEJO_HOST/<upstream>/compare/<base-branch>...<user>:<branch>` 2. Or, if creating via API, display the scrubbed title, body, and Gen-AI disclosure to the user and obtain explicit interactive confirmation before executing: -*Write tool call:* `file_path: <scratch>/pr-payload.json`, `content: {"title": "<scrubbed title>", "body": "<body>", "head": "<user>:<branch>", "base": "<base-branch>"}` +*Write tool call:* `file_path: <scratch>/pr-payload.json`, `content: {"title": "<scrubbed title>", "body": "<body>", "head": "<user>:<branch>", "base": "<base-branch>"}` — write properly escaped JSON (quotes, backslashes and newlines escaped). ```bash curl -fsS -X POST -H "Authorization: token $TEA_TOKEN" \