Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/labels-and-capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ Tools under [`tools/`](../tools/). Tools with two values (separated by
| [`tools/forwarder-relay`](../tools/forwarder-relay/) | `capability:setup` | 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/github`](../tools/github/) | `capability:setup` | GitHub REST / GraphQL substrate (called by every lifecycle phase — pure substrate, no single phase) |
| [`tools/github-body-field`](../tools/github-body-field/) | `capability:setup` | Read or rewrite one `### Field` section of a GitHub issue body without bringing the body into agent context — substrate helper for the security-sync skills |
| [`tools/github-rollup`](../tools/github-rollup/) | `capability:setup` | Append to (or create) the status-rollup comment on a GitHub issue without bringing the rollup body into agent context — substrate helper for every status-update-emitting skill |
| [`tools/gmail`](../tools/gmail/) | `capability:setup` | Gmail API substrate |
| [`tools/jira`](../tools/jira/) | `capability:setup` | JIRA REST substrate (read-only today; write subcommands tracked in [#301](https://github.com/apache/airflow-steward/issues/301)) |
| [`tools/mail-archive`](../tools/mail-archive/) | `capability:setup` | Adapter contract for public mail-archive backends (PonyMail, Hyperkitty, Discourse, Google Groups, GitHub Discussions). Pure interface spec. |
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ members = [
"tools/cve-tool-vulnogram/generate-cve-json",
"tools/cve-tool-vulnogram/oauth-api",
"tools/github-body-field",
"tools/github-rollup",
"tools/gmail/oauth-draft",
"tools/jira",
"tools/permission-audit",
Expand Down
84 changes: 84 additions & 0 deletions tools/github-rollup/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<!-- 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)*

- [github-rollup](#github-rollup)
- [Why](#why)
- [Invocation](#invocation)
- [`append <issue> --action "<label>" ...`](#append-issue---action-label-)
- [`list <issue>`](#list-issue)
- [`latest <issue>`](#latest-issue)
- [Failure modes](#failure-modes)

<!-- 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 -->

# github-rollup

**Capability:** capability:setup

Append to (or create) the status-rollup comment on a GitHub
issue **without bringing the rollup body into agent context**.

## Why

Every skill that updates a `<tracker>` issue (import receipt,
sync passes, CVE allocation, dedupe, fix-PR announcements) folds
its status update into one rollup comment per tracker. The
existing recipe in
[`tools/github/status-rollup.md`](../github/status-rollup.md)
walks the agent through: fetch the comment, concatenate
`<old body>` + ruler + new entry, PATCH the comment. That loops
the full rollup body — which grows monotonically and is the
single largest comment on a long-running tracker — through agent
context every sync pass.

This tool does the read / append / PATCH in a subprocess. Only a
one-line confirmation lands on the agent's stderr. The body never
crosses the boundary.

## Invocation

```bash
uv run --directory tools/github-rollup github-rollup <subcommand> ...
```

### `append <issue> --action "<label>" ...`

Append a new entry to the rollup comment on `<issue>`. Creates
the rollup if none exists. Required: `--action <label>` (the
right-hand field of the entry's summary line). Body comes from
either `--entry-body "<text>"` or `--entry-body-file <path>`
(`-` reads stdin).

Optional flags:

- `--user @handle` — override the summary's `@user` field
(default: the authenticated `gh` user from `gh api user`).
- `--now <ISO8601>` — override the date used in the summary
(default: real now). Useful for deterministic replay tests.
- `--dry-run` — print the decision (create vs append) without
writing.

### `list <issue>`

Print every entry's summary line in order (or `--json` for a
machine-readable array of `{date, user, action}`). Exit 3 if the
issue has no rollup yet.

### `latest <issue>`

Print just the body of the most recent entry. Useful for
*"what did the last sync do?"* in a follow-up script. Exit 3
if the rollup or entries are missing.

## Failure modes

| Exit | Meaning |
|---|---|
| 0 | Success (or `--dry-run` planned). |
| 2 | CLI argument error (mutually-exclusive flags, missing required). |
| 3 | Issue has no rollup yet, or rollup has no entries (for `latest`). |
| other | `gh` returned non-zero; the underlying stderr is forwarded. |
67 changes: 67 additions & 0 deletions tools/github-rollup/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "github-rollup"
version = "0.1.0"
description = "Append to (or create) the status-rollup comment on a GitHub issue without bringing the rollup body into agent context."
readme = "README.md"
requires-python = ">=3.11"
license = { text = "Apache-2.0" }
dependencies = []

[project.scripts]
github-rollup = "github_rollup:main"

[tool.hatch.build.targets.wheel]
packages = ["src/github_rollup"]

[tool.ruff]
line-length = 110
target-version = "py311"
src = ["src", "tests"]

[tool.ruff.lint]
select = ["E", "W", "F", "I", "B", "UP", "SIM", "C4", "RUF"]
ignore = ["E501"]

[tool.ruff.lint.per-file-ignores]
"tests/**" = ["B", "SIM"]

[tool.mypy]
python_version = "3.11"
files = ["src", "tests"]
warn_unused_ignores = true
warn_redundant_casts = true
warn_unreachable = true
check_untyped_defs = true
no_implicit_optional = true
disallow_untyped_defs = true
disallow_incomplete_defs = true

[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false
disallow_incomplete_defs = false

[tool.pytest.ini_options]
minversion = "8.0"
addopts = "-ra -q"
testpaths = ["tests"]
37 changes: 37 additions & 0 deletions tools/github-rollup/src/github_rollup/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from github_rollup.cli import main
from github_rollup.rollup import (
ROLLUP_MARKER_PREFIX,
RollupEntry,
build_entry,
build_new_rollup_body,
iter_entries,
parse_summary_line,
rebuild_with_appended_entry,
)

__all__ = [
"ROLLUP_MARKER_PREFIX",
"RollupEntry",
"build_entry",
"build_new_rollup_body",
"iter_entries",
"main",
"parse_summary_line",
"rebuild_with_appended_entry",
]
Loading
Loading