)]}'
{
  "log": [
    {
      "commit": "15be87f9fda3668acad98165614ac5df0cb585b7",
      "tree": "ba8429dcb6410335e6c032c8c518980cbb74ede9",
      "parents": [
        "675702395749ef3ba52fb5c0b897726b27adbe83"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Thu Apr 30 11:57:47 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Apr 30 11:57:47 2026 +0200"
      },
      "message": "docs(secure-agent-setup): sandbox-bypass warning hook + private-repo sync pattern (#17)\n\n* docs(secure-agent-setup): sandbox-bypass warning hook + private-repo sync pattern\n\nTwo new sections in `secure-agent-setup.md`:\n\n- \"Sandbox-bypass visibility hook\" — install + wiring for a\n  PreToolUse hook that prints a bold-red banner whenever the\n  model invokes Bash with `dangerouslyDisableSandbox: true`.\n  Recommended user-scope (`~/.claude/settings.json`) so it fires\n  in every session on the host, not just tracker sessions.\n  Includes a Verify snippet and Trade-offs notes (visibility-not-\n  block; grep-based JSON match for schema robustness).\n\n- \"Syncing user-scope config across machines\" — the dotfile-style\n  pattern for keeping `CLAUDE.md`, hook scripts, and an optional\n  global `claude-iso.sh` in lockstep across hosts via a private\n  git repo. What-to-track table, repo layout, fresh-host setup,\n  a minimal \\`sync.sh\\`, and a \"Why a private repo\" rationale.\n\nAlso adds two recommended/optional steps to \"Adopter setup\"\nlinking to the new sections.\n\nShips the hook script as \\`tools/agent-isolation/sandbox-bypass-warn.sh\\`\nwith the same ASF license header as the sibling \\`claude-iso.sh\\`,\nand adds it to the directory\u0027s README files table.\n\nThe doctoc-managed TOC at the top of \\`secure-agent-setup.md\\` is\nNOT updated by this commit — re-run doctoc before merging.\n\nGenerated-by: Claude Code (Claude Opus 4.7)\n\n* docs(secure-agent-setup): regenerate doctoc TOC for new sections\n\nThe previous commit (35bf73d) flagged that the doctoc-managed TOC\nat the top of `secure-agent-setup.md` was not updated when the two\nnew sections — \"Sandbox-bypass visibility hook\" and \"Syncing\nuser-scope config across machines\" — were added. This commit\nregenerates the TOC to include those sections plus their H3\nsubsections (maxlevel\u003d3, matching `.pre-commit-config.yaml`).\n\ndoctoc itself was not run to produce this — it is not installed in\nthe local environment and the sandbox blocks the npm/uv network\naccess needed to fetch it. The anchors were instead derived by\nhand following the same encoding rules already visible in the\nexisting TOC entries in this file:\n\n- spaces → `-`\n- in-word hyphens preserved (e.g. `user-scope`)\n- parentheses, asterisks, periods, backticks dropped without a separator\n- em-dash / slash with surrounding spaces → `--`\n\nA future doctoc run (e.g. via the pre-commit hook in CI) should\nproduce a no-op diff against this TOC.\n\nGenerated-by: Claude Code (Claude Opus 4.7)\n\n* docs(secure-agent-setup): sandbox-state status line for the terminal footer\n\nAdds a `statusLine` helper that makes the active sandbox state of\nthe current Claude Code session visible at all times — green\n`[sandbox]` when `sandbox.enabled: true` is set in the resolved\n`settings.json`, bold-red `[NO SANDBOX]` otherwise. The indicator\nsits in the terminal footer that Claude Code already renders, so\nno extra UI surface is added; it just makes the existing footer\ninformative about the *one* configuration property the rest of\nthis document is about.\n\nWhy it is needed even with the existing sandbox-bypass-warn hook:\nthe `PreToolUse` hook fires per-call when the model requests a\nbypass, but a session whose `sandbox.enabled` was simply never set\n(e.g. an unrelated project\u0027s `.claude/settings.json` is missing,\nthe user is in `~`, settings drift after a Claude Code upgrade)\nemits no signal until something goes wrong. The status line covers\nthat gap by surfacing settings-level state continuously.\n\nThree changes:\n\n- `tools/agent-isolation/sandbox-status-line.sh` — the helper.\n  Reads the statusLine JSON on stdin, resolves\n  `.sandbox.enabled` from project- then user-scope `settings.json`\n  via `jq`, prints `\u003cmodel\u003e [sandbox]` (green) or\n  `\u003cmodel\u003e [NO SANDBOX]` (bold red). ASF license header matching\n  the sibling scripts; executable bit set.\n- `secure-agent-setup.md` — new H2 \"Sandbox-state status line\"\n  between the bypass-warn hook section and the sync-config section\n  (sibling user-scope observability features). Adopter step 5 now\n  recommends both, the sync-config layout, \"what to track\" table\n  and fresh-host setup snippet pick up the new script. TOC entry\n  added by hand following the same encoding rule as the previous\n  TOC regen — anchor `#sandbox-state-status-line`.\n- `tools/agent-isolation/README.md` — Files table picks up the\n  new script.\n\nTrade-offs are documented in-line: the indicator is settings-level\ntruth, not session-level truth (CLI flags like\n`--bypass-permissions` and runtime mode changes that override the\nfile are not visible), and pairing with the per-call bypass-warn\nhook is the recommended belt-and-braces posture. A future Claude\nCode statusLine schema that exposes sandbox state directly would\nlet the helper drop the file-read.\n\nGenerated-by: Claude Code (Claude Opus 4.7)"
    },
    {
      "commit": "675702395749ef3ba52fb5c0b897726b27adbe83",
      "tree": "cc2bd816a4a00d9244ece5ed3e07c163444aa636",
      "parents": [
        "74d2830d4210e436b77af6eb168d7dbf20db367b"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Thu Apr 30 10:16:50 2026 +0200"
      },
      "committer": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Thu Apr 30 10:16:50 2026 +0200"
      },
      "message": "chore: gitignore .claude/worktrees/\n\nWorktree-isolated agent runs land under .claude/worktrees/; keep them\nout of git status.\n\nGenerated-by: Claude Code (Claude Opus 4.7)\n"
    },
    {
      "commit": "74d2830d4210e436b77af6eb168d7dbf20db367b",
      "tree": "a770fd6aa4784d1a737d4f2760bc4b1909fc5157",
      "parents": [
        "97dca75c7a1ddf3c87338f34e31256b91b0cac02"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Wed Apr 29 21:59:36 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Apr 29 21:59:36 2026 +0200"
      },
      "message": "docs(agent-isolation): mention claude\u003dclaude-iso alias in README (#16)\n\nAdd the `alias claude\u003d\u0027claude-iso\u0027` line to the \"Usage at a glance\"\nblock, with a one-line cross-reference to the trade-off discussion\nin `secure-agent-setup.md`.\n\nCo-authored-by: Claude Opus 4.7 (1M context) \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "97dca75c7a1ddf3c87338f34e31256b91b0cac02",
      "tree": "bb4d760e6ec0ef0f9aa4fe66ee9a671f113b812f",
      "parents": [
        "6db60d30242861a9193194b161c3d142b38f05fc"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Wed Apr 29 21:57:36 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Apr 29 21:57:36 2026 +0200"
      },
      "message": "fix(agent-isolation): zsh portability + opt-in default-claude alias (#15)\n\n* fix(agent-isolation): make claude-iso.sh portable to zsh\n\nThe wrapper used three bashisms that broke under zsh: ${!var}\nindirect expansion (\"bad substitution\" in zsh), unquoted parameter\nexpansion for word-splitting $CLAUDE_ISO_ALLOW (zsh needs ${\u003dvar}\nor an array), and `command -v claude` for binary lookup (which would\nrecurse through an `alias claude\u003dclaude-iso` if the user adopts the\nwrapper as their default). Replace each with a shell-aware form, and\nprint a one-line stderr banner on each launch so the isolated mode\nis visually distinguishable from a plain `claude` session.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) \u003cnoreply@anthropic.com\u003e\n\n* docs(secure-agent-setup): show how to alias claude → claude-iso\n\nAdd a short \"Optional — make the isolated wrapper your default\nclaude\" subsection under \"The clean-env wrapper\", documenting the\n`alias claude\u003d\u0027claude-iso\u0027` pattern, the bypass escape hatches\n(`command claude`, `\\claude`), and the trade-off (sessions outside\na tracker checkout also run with a stripped env). The script\u0027s\nshell-aware path lookup landed in the previous commit so the alias\nno longer recurses into itself.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) \u003cnoreply@anthropic.com\u003e\n\n---------\n\nCo-authored-by: Claude Opus 4.7 (1M context) \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "6db60d30242861a9193194b161c3d142b38f05fc",
      "tree": "39f343054fa1736dea62d555efe6ba840868e842",
      "parents": [
        "7a1941122581224bdb310bae822a241d239833e7"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Wed Apr 29 20:46:39 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Apr 29 20:46:39 2026 +0200"
      },
      "message": "docs(secure-agent-setup): document global ~/.claude/ install option for claude-iso (#14)\n\nAdopters can now choose between sourcing tools/agent-isolation/claude-iso.sh\ndirectly from the framework checkout (per-repo) or copying it into\n~/.claude/agent-isolation/ and sourcing from there (global). The global\noption survives branch/worktree changes and travels with the rest of\n~/.claude/ when dotfiles sync across machines, at the cost of a manual\nre-copy when the upstream wrapper changes.\n\nGenerated-by: Claude Code (Claude Opus 4.7)"
    },
    {
      "commit": "7a1941122581224bdb310bae822a241d239833e7",
      "tree": "3292cdd531cd3b4bbc60a162782b619d71ca05e3",
      "parents": [
        "6ab96620d8017b578b70db4f3f53adeb04eea9f8"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Wed Apr 29 15:52:20 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Apr 29 15:52:20 2026 +0200"
      },
      "message": "docs(secure-agent-setup): add Linux Mint 22 / Ubuntu Noble shortcut (#12)\n\nThe pinned bubblewrap (0.11.1) and socat (1.8.1.1) versions in\n`tools/agent-isolation/pinned-versions.toml` are the upstream\nreleases that have aged past the framework\u0027s 7-day cooldown — they\nare NOT in Ubuntu Noble\u0027s main repos. Noble ships:\n\n  bubblewrap 0.9.0  (0.9.0-1ubuntu0.1)\n  socat      1.8.0.0 (1.8.0.0-4build3)\n\nBoth pre-date the framework\u0027s pins by months and are well past the\ncooldown, so they\u0027re a legitimate adopter choice on Mint 22.\nUbuntu 24.04 — but the framework\u0027s main install path documents the\nupstream pins, which leaves Mint/Noble adopters without a cl\nstory.\n\nThis commit adds a *Distro-specific shortcut* section under\n\\`Install commands\\` that:\n\n- Documents the apt-shipped versions and their \\`apt_pin\\` s\n- Calls out the trade-off explicitly (older feature set, but apt-\n  managed security backports, no source build).\n- Notes that the framework\u0027s \\`.claude/settings.json\\` works\n  unchanged — the sandbox API has been stable since bubblewr\n  0.6.x.\n- Tells the user how to silence the drift the check script w\n  report against the upstream pins (a \\`pinned-versions.local.toml\\`,\n  matching Claude Code\u0027s own \\`settings.local.json\\` convent\n- Closes with the rationale for keeping this as a \"shortcut\" rather\n  than the canonical path.\n\nNo change to \\`pinned-versions.toml\\` itself — the framework\ndefault pin still tracks the upstream release stream, which is the\nright thing to track for the weekly check-tool-updates routi\n\nGenerated-by: Claude Code (Claude Opus 4.7)"
    },
    {
      "commit": "6ab96620d8017b578b70db4f3f53adeb04eea9f8",
      "tree": "b151ed19dd56c8be58ac934cb0b88df5dba589b5",
      "parents": [
        "1b569a837831da11870aa77475aa94238a85a88a"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Wed Apr 29 15:50:52 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Apr 29 15:50:52 2026 +0200"
      },
      "message": "fix(secure-agent-setup): allow gpg-agent socket + key paths in sandbox (#13)\n\nThe .claude/settings.json this PR ships denies all of ~/ from the\nBash sandbox, with only a tiny allowRead allowlist. That set\ncovers git/gh/uv but not commit signing — git users on Linux who\nsign commits via gpg-agent (either gpg-format or ssh-format with\nSSH_AUTH_SOCK pointing at gpg-agent\u0027s ssh-compat socket) hit:\n\n    error: Couldn\u0027t get agent socket?\n    fatal: failed to write commit object\n\non the first commit attempt inside the sandboxed session, because\n~/.gnupg/ and /run/user/\u003cUID\u003e/gnupg/ are both behind the denyRead.\n\nAdds two entries to sandbox.filesystem.allowRead:\n\n  ~/.gnupg/             # gpg keyring + trustdb\n  /run/user/*/gnupg/    # gpg-agent socket dir (the * is the UID)\n\nBoth are needed: the keyring so gpg can read the signing key\nmaterial, the runtime socket so gpg-agent / its ssh-compat s\ncan be reached. The * glob in the runtime path lets the same\nallowRead work for any UID — Claude Code\u0027s sandbox allowRead\nsupports glob wildcards.\n\nThe matching documentation block in secure-agent-setup.md is\nupdated to mirror the new entries with inline comments.\n\nGenerated-by: Claude Code (Claude Opus 4.7)"
    },
    {
      "commit": "1b569a837831da11870aa77475aa94238a85a88a",
      "tree": "d94dc0c345d4dbcc844dbf40c2f4affddfb5c715",
      "parents": [
        "2efe27f4b3ddb1b2a6cf75cadd39b1e2f078bdf8"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Wed Apr 29 15:19:25 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Apr 29 15:19:25 2026 +0200"
      },
      "message": "ci(secure-agent): add credential-isolation setup + pinned-tool manifest (#11)\n\nAdds the framework\u0027s recommended secure-agent setup — a layered\ndefence for running Claude Code (or any other SKILL.md-aware\nagent) against pre-disclosure CVE content without unfettered\naccess to host credentials. The framework dogfoods the\nconfiguration via `.claude/settings.json`; adopters scaffold their\nown copy from the example block in the new doc.\n\n## Layered defence\n\nThe new setup is four layers, each implemented in this commit:\n\n  Layer 0 — clean env       claude-iso.sh wrapper\n  Layer 1 — fs sandbox       .claude/settings.json sandbox.*\n  Layer 2 — tool perms       .claude/settings.json permissions.deny\n  Layer 3 — confirm-on-write .claude/settings.json permissions.ask\n\nLayers 1-3 share `.claude/settings.json`. Layer 0 is a separate\nshell wrapper that strips credential-shaped env vars from the\nparent shell before exec-ing claude.\n\n## 7-day cooldown for system-tool versions\n\nMirrors the framework\u0027s existing 7-day cooldown convention\n(`[tool.uv] exclude-newer \u003d \"7 days\"` in pyproject.toml; weekly\nDependabot updates with 7-day cooldown in dependabot.yml). Every\nhost-system tool the secure setup depends on (bubblewrap, socat,\nclaude-code) is pinned in `tools/agent-isolation/pinned-versions.toml`\nto a version that was released at least 7 days before the manifest\nwas last touched (`pinned_at`).\n\nA side-effect-free check script\n(`tools/agent-isolation/check-tool-updates.sh`) compares the pinned\nversions to upstream and prints upgrade candidates that have\nthemselves aged past the 7-day cutoff. The script never installs,\nnever edits the manifest, never opens a PR — bumps require explicit\nmaintainer review per the procedure documented in\n`secure-agent-setup.md`.\n\nThe doc also walks through wiring the script into a weekly\n`/schedule` routine so upgrade candidates surface automatically\nwithout manual prompting; the scheduled agent has no special\npermission to install — the surfaced candidate is a proposal, not\nan action.\n\n## Files\n\n| File | Purpose |\n|---|---|\n| `secure-agent-setup.md`                       | User-facing doc. Threat model, layered defence walkthrough, install commands per distro, bump procedure, adopter scaffold, verification commands, residual risks. |\n| `.claude/settings.json`                       | The framework\u0027s own dogfooded secure config — sandbox + permissions for cwd-rooted Claude Code sessions in this repo. |\n| `tools/agent-isolation/pinned-versions.toml`  | Machine-readable manifest of pinned upstream versions for `bubblewrap`, `socat`, `claude-code`. Each entry carries a `released` date that satisfies the 7-day cooldown. |\n| `tools/agent-isolation/check-tool-updates.sh` | Reads the manifest and reports upstream releases newer than the pin AND aged past 7 days. Side-effect-free. |\n| `tools/agent-isolation/claude-iso.sh`         | Layer-0 shell wrapper — `env -i` + tiny passthrough list. |\n| `tools/agent-isolation/README.md`             | Files-and-usage overview for the new directory. |\n\n`README.md` and `AGENTS.md` updates: link to the new doc from the\n\"agent prerequisites\" section and from \"Local setup\".\n\n## Pinned versions (at time of writing, all aged past 7 days)\n\n  bubblewrap   0.11.1   released 2026-03-21\n  socat        1.8.1.1  released 2026-03-13\n  claude-code  2.1.117  released 2026-04-22\n\nThe check script verified these by querying upstream:\n\n  $ bash tools/agent-isolation/check-tool-updates.sh\n  TOOL           PINNED     PINNED@      UPSTREAM   UPSTREAM@    STATUS\n  bubblewrap     0.11.1     2026-03-21   0.11.1     2026-03-21   ✓ up to date\n  socat          1.8.1.1    2026-03-13   1.8.1.1    2026-02-12   ✓ up to date\n  claude-code    2.1.117    2026-04-22   2.1.117    2026-04-22   ✓ up to date\n\nLatest claude-code (v2.1.123, released 2026-04-29) is intentionally\nNOT picked up — within the 7-day cooldown.\n\n## Test plan\n\n- ✅ `prek run --all-files` passes (doctoc generated TOCs for the\n  two new .md files; all other hooks clean).\n- ✅ The check script runs end-to-end, queries upstream, prints the\n  expected status table.\n- ✅ The example `.claude/settings.json` parses as valid JSON\n  (`python3 -c \u0027import json; json.load(open(\".claude/settings.json\"))\u0027`).\n- The actual filesystem-sandbox enforcement requires bubblewrap on\n  the host — the framework maintainer\u0027s first invocation of\n  Claude Code with this settings.json after merge will exercise it\n  end-to-end.\n\nGenerated-by: Claude Code (Claude Opus 4.7)"
    },
    {
      "commit": "2efe27f4b3ddb1b2a6cf75cadd39b1e2f078bdf8",
      "tree": "a0483583084630780bb23cf103eaafc3d355c73b",
      "parents": [
        "2ee606836eb8d33a387ceb42e2bee0ca49fe2bf9"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Wed Apr 29 13:21:03 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Apr 29 13:21:03 2026 +0200"
      },
      "message": "feat(release-manager-handoff): explicit hand-off + publication-ready comments (#10)\n\nAdds two new first-class comments the sync-security-issue skill posts\non the tracker as the issue moves through the release-manager half of\nthe lifecycle (Steps 12-15), instead of folding the call-to-action into\nthe catch-all status rollup. The rollup is the security team\u0027s audit\ntrail and accumulates many small entries; the RM-facing comments are\none-shot orientation surfaces with their own dated context and working\n@-mention notifications.\n\nComment 1 — Release-manager hand-off (Step 12 trigger). Fires exactly\nonce at the `pr merged` -\u003e `fix released` swap, walks the RM through\nSteps 13-15 end-to-end without forcing them to consult external docs.\nLives in tools/\u003ccve-tool\u003e/release-manager-handoff-comment.md as a\nparameterised template; sync substitutes CVE_ID, RM_HANDLE, the\nVulnogram #source / #email tab URLs, the embedded-JSON anchor, the\npublic archive scan URL, and absolute github.com URLs into the\nframework. Idempotent via an HTML marker on line 1.\n\nComment 2 — Publication-ready notification (Step 14 trigger). Fires\nexactly once when the *Public advisory URL* body field is populated and\nthe CVE JSON has been regenerated to include the archive URL. Tells the\nRM the final paste + READY -\u003e PUBLIC + close is now unblocked.\n\nTool-specific Vulnogram details (record state machine, paste flow,\nemail-preview tab) live under tools/vulnogram/; generic workflow\n(when each comment fires, idempotency, apply mechanic) lives in the\nsync skill spec and the framework README.\n\nOther lifecycle clarifications:\n\n- READY state added to tools/vulnogram/record.md (between REVIEW and\n  PUBLIC); the README always referenced it but the tool doc didn\u0027t.\n- Vulnogram #email tab documented as a load-bearing email-preview\n  checkpoint before the advisory-send step.\n- Step 4 close recipe updated to follow `gh issue close` with an\n  `archiveProjectV2Item` mutation on every close (terminal or\n  non-terminal); the recipe lives in tools/github/project-board.md\n  (\"Archive a board item — terminal-state cleanup\"), idempotent\n  on already-archived items.\n\nNo changes to the existing skill behaviour at `cve allocated` —\nthe hand-off comment deliberately fires only after release ships\n(the user-facing flow before that is handled by existing rollup\nstatus entries).\n\nGenerated-by: Claude Opus 4.7 (1M context) \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "2ee606836eb8d33a387ceb42e2bee0ca49fe2bf9",
      "tree": "994e40ad6c69cc3b9550c9673e8f9552f5ecbae8",
      "parents": [
        "11674a9e47bfaee36e474ef5a651172731e97bff"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Wed Apr 29 11:49:29 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Apr 29 11:49:29 2026 +0200"
      },
      "message": ".asf.yaml: route all notification schemes (suppress dev@ default) (#9)\n\nWithout this change, ASF Infra was silently routing four GitHub\nevent streams to `dev@airflow.apache.org` because the schemes were\nnot explicitly populated in our `notifications:` block.\n\nThe ASF asf.yaml validator at\nhttps://github.com/apache/infrastructure-asfyaml/blob/main/asfyaml/feature/notifications.py\ndefines `VALID_NOTIFICATION_SCHEMES` with eleven schemes that apply\nto a public GitHub repo. Any unset scheme falls back to\n`dev@\u003cproject\u003e.apache.org`. We were setting only five\n(`commits`, `issues`, `pullrequests`, `jobs`, `discussions`),\nso the four \"bot event\" streams were leaking to dev@:\n\n- `issues_status` — issue open/close/label-change events.\n- `issues_comment` — comments on issues.\n- `pullrequests_status` — PR state changes + CI status-check\n  failures (the noisiest one).\n- `pullrequests_comment` — comments on PRs.\n\n(`commits_by_path` is an optional path-specific override, not a\ndefault-target field, so it stays unset.)\n\nThis commit:\n\n- Adds explicit routes for all four leaking schemes — same target\n  as the rest (`commits@airflow.apache.org`), per the existing\n  Airflow PMC umbrella.\n- Reformats the field list with column alignment so the missing-\n  scheme regression is hard to reintroduce silently.\n- Expands the comment to spell out the dev@ default behaviour, the\n  schema link, and the rationale for the `commits@airflow.apache.org`\n  routing (it\u0027s the standard bot-event mirror list, public-by-\n  design, already moderated for bot-only traffic).\n\nNo effect on the five schemes that were already routed correctly;\nthe change is purely additive.\n\nGenerated-by: Claude Code (Claude Opus 4.7)"
    },
    {
      "commit": "11674a9e47bfaee36e474ef5a651172731e97bff",
      "tree": "a65ec62825da2ca6b27f4357c0cbd8750e363091",
      "parents": [
        "b73e24078beab2b632ff44fab2298bd1db02f180"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Wed Apr 29 04:15:09 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Apr 29 04:15:09 2026 +0200"
      },
      "message": "skill(import-security-issue-from-md): batch on-ramp from markdown findings (#8)\n\nAdds a third on-ramp variant to the security-issue handling process,\nalongside `import-security-issue` (Gmail-driven) and\n`import-security-issue-from-pr` (public-PR-driven).\n\nThe new skill takes a markdown file containing one or more pre-\nformatted findings — typically the output of an AI security review,\na `/security-review` pass over an upstream branch, or a third-party\nSAST report exported as markdown — and creates one `\u003ctracker\u003e`\ntracking issue per finding.\n\n## Expected input shape\n\nPer finding, separated from the next finding by `---` on its own\nline:\n\n  # \u003cTitle\u003e\n  ## Details\n  ## Location\n  ## Impact\n  ## Reproduction steps\n  ## Recommended fix\n  ---\n  **Severity:** HIGH|MEDIUM|LOW|UNKNOWN\n  **Status:** Open\n  **Category:** \u003cfree-text\u003e\n  **Repository:** \u003cowner\u003e/\u003crepo\u003e\n  **Branch:** \u003cref\u003e\n  **Date created:** YYYY-MM-DD\n\n## Behaviour\n\n- Parses every finding in the file, validates the per-section\n  payloads, and surfaces a single proposal table covering the\n  whole batch (severity, category, title, possible-duplicate flag).\n- Default disposition mirrors `import-security-issue`: import all\n  unless `skip \u003cN\u003e` upfront. A bare `go` / `proceed` / `yes, all`\n  imports every non-rejected finding.\n- Per kept finding: creates a tracker via `gh api repos/.../issues`\n  bypassing the form (so the required `Security mailing list\n  thread` field doesn\u0027t fire), applies labels (`needs triage`,\n  `security issue`), pins to the `Needs triage` board column via\n  the orphan-issue path in `tools/github/project-board.md`, and\n  posts a status-rollup comment marking the source markdown file +\n  finding index.\n- All `**Severity:** HIGH` findings still land as `Needs triage`;\n  the source\u0027s tags are recorded as informational, not adopted.\n  CVSS scoring and CWE assignment happen at allocation time, not\n  at import.\n- No reporter-reply step (the file is the report, not an inbound\n  thread).\n\n## Field mapping\n\nThe standard 11-field issue body template is populated from the\nmarkdown sections:\n\n- `## Details` + `## Impact` + `## Reproduction steps` →\n  `The issue description` (verbatim, with sub-headings).\n- `## Recommended fix` → collapsible `\u003cdetails\u003e` block at the\n  end of the body.\n- `**Repository:**` + `**Branch:**` → `Affected versions`\n  (literal text — release-train mapping happens at allocation).\n- `**Severity:**` → `Severity` field.\n- `**Category:**` → `CWE` field (free-text; actual CWE assigned\n  during triage).\n- `## Location` URL → `PR with the fix` when it points at a\n  `\u003cupstream\u003e` PR; otherwise `_No response_`.\n\n## Out of scope (potential follow-ups)\n\n- A Python parser tool that takes the file and emits structured\n  JSON. Skill-only is enough for the current shape; promote to a\n  Python tool if other input shapes start to need parsing.\n- Auto-detection that the `## Location` URL is a `\u003cupstream\u003e` PR\n  vs. a vulnerable source file (currently the skill leans on\n  pattern-matching the URL).\n- Re-run / resume on the same file after a partial-batch failure\n  beyond the duplicate-guard at Step 2 catching already-imported\n  findings.\n\nGenerated-by: Claude Code (Claude Opus 4.7)"
    },
    {
      "commit": "b73e24078beab2b632ff44fab2298bd1db02f180",
      "tree": "5c4a3424e3e186bb25aeb2d726d0835245b8ddc3",
      "parents": [
        "53b6d541be5af524eb855301b63aae8604d4a42b"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Wed Apr 29 04:04:48 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Apr 29 04:04:48 2026 +0200"
      },
      "message": "sanitize: drop Airflow remnants; synthesize fictional adopter for tests (#7)\n\n* docs+code: sanitize Airflow remnants; synthesize fictional adopter for tests\n\nComprehensive sanitization pass across the framework\u0027s documentation\nand the generate-cve-json Python package, plus a config-driven\nrefactor of three previously-hardcoded Airflow values in cve_json.py.\n\n## Documentation (Tier 1 + Tier 2)\n\nTop-level docs:\n\n- `README.md`: drop the broken `Current projects` table (no\n  per-project subdirs in this repo); reframe the legacy\n  `apache/airflow-steward` repo identity; replace hardcoded\n  `airflow / providers / chart` scope-label list with a pointer to\n  `\u003cproject-config\u003e/scope-labels.md`; replace the `airflow-s` grep\n  blocklist token with `\u003ctracker\u003e`; replace `airflow-s#XYZ` with\n  `\u003ctracker\u003e#XYZ`; rewrite `Adopting the framework` section.\n- `CONTRIBUTING.md`: drop the per-project tree section that\n  documented a no-longer-existing `projects/airflow/` subtree; rewrite\n  the file index for the framework-only layout; retarget the base\n  branch from `airflow-s` to `main`; replace per-project guidance\n  with adopter-side guidance.\n- `new-members-onboarding.md`: replace 4 hardcoded\n  `projects/airflow/` and `airflow-s/projects/2` URLs with\n  `\u003cproject-config\u003e` pointers and generic phrasing.\n- `projects/_template/{README,canned-responses,release-trains,\n  title-normalization}.md`: drop cross-references to a\n  no-longer-existing `../airflow/` template peer; inline the\n  shapes/example wording previously hosted there.\n\nTool docs:\n\n- `tools/gmail/asf-relay.md`, `tools/ponymail/tool.md`: replace\n  Airflow-specific PMC LDAP / mailing-list values with\n  `\u003cproject-config\u003e` pointers and generic phrasing.\n- `tools/gmail/{operations,search-queries,draft-backends}.md`,\n  `tools/github/{project-board,tool}.md`: replace hardcoded\n  `airflow-s` / `airflow-s/projects/2` / `airflow-s@noreply.github.com`\n  references with `\u003ctracker\u003e` placeholders / project-config\n  pointers / a new `\u003ctracker-noreply\u003e` placeholder.\n\nSkill files (`.claude/skills/*/SKILL.md`):\n\n- Replace `airflow-s` standalone references with `\u003ctracker\u003e` /\n  `\u003ctracker\u003e issue` / `\u003ctracker\u003e tracking issue` /\n  `/blob/\u003ctracker-default-branch\u003e/` everywhere it isn\u0027t an\n  `(example: airflow-s/airflow-s …)` framing line. Total: 7 skill\n  files touched, ~30 substitutions.\n- Reframe Airflow-as-the-assumed-adopter language: \"Apache Airflow\n  PMC\" → \"the project\u0027s PMC\", \"the Airflow security team\" → \"the\n  project security team\", \"the Airflow Security Model\" → \"the\n  project\u0027s security model\", \"the Airflow Release Plan wiki\" → \"the\n  project\u0027s release plan\", etc.\n\nSkill files (the deeper Airflow-specific procedural content —\nrelease-train naming, scope-label sets, milestone formats, the\ntitle-normalization regex example — is intentionally left for a\nfollow-up that moves it into per-project config under\n`\u003cproject-config\u003e/`. Refactoring it changes skill behaviour and\nneeds its own PR.)\n\n`AGENTS.md`: 3 prose tweaks to drop \"Airflow\u0027s\", \"for all Airflow\nCVEs\", \"the Airflow process\", \"the Airflow release train\" in\ncontexts that treated Airflow as the assumed project.\n\n`tools/vulnogram/generate-cve-json/SKILL.md`: rewrite the preamble\nto flag concrete `apache-foo-providers-*` strings as illustrative\nexamples rather than Airflow defaults; rewrite multi-product /\nprovider-display-map / `\u003c NEXT VERSION` sections to refer to\nadopter-supplied config rather than a built-in `AIRFLOW_PROVIDER_\nDISPLAY_MAP` (which the code no longer has — was stale doc).\n\n## Code (Tier 3 — config-driven refactor)\n\n`tools/vulnogram/generate-cve-json/src/generate_cve_json/cve_json.py`\nhad three hardcoded Airflow values that bypassed the otherwise-\nconfig-driven design:\n\n- `wrap_cve_record` hardcoded `CNA_private.projecturl \u003d\n  \"https://airflow.apache.org/\"`, `owner \u003d \"airflow\"`,\n  `userslist \u003d \"users@airflow.apache.org\"` into every emitted CVE\n  record.\n- `build_references` hardcoded the `\"airflow-s\" not in url` filter,\n  so adopters\u0027 tracker URLs would leak into published CVE records\n  while Airflow\u0027s wouldn\u0027t.\n- `resolve_title` hardcoded the `r\"^\\s*apache\\s+airflow\\s*[:\\-...]?\n  \\s*\"` strip regex, so the title-prefix scrub only worked for\n  Airflow-titled issues.\n\nAll three are now config-driven:\n\n- New `[cna_private]` config section with `project_url`, `owner`,\n  `users_list` fields. Loaded into `CNA_PRIVATE_PROJECT_URL`,\n  `CNA_PRIVATE_OWNER`, `CNA_PRIVATE_USERS_LIST` constants.\n- New `TRACKER_FILTER_TOKEN` constant derived from `meta.tracker_repo`\n  (uses the org segment) — `build_references` filters with that.\n- New `TITLE_STRIP_RE` constant compiled at config-load time from the\n  configured `top_level_product` — `resolve_title` uses it.\n\nAlso removed an orphaned `SKILL_SOURCE_URL \u003d \"https://github.com/\nairflow-s/airflow-s/...\"` assignment at module level that was\noverriding the config-loaded value (bug fix).\n\n## Test fixture (Q1b — fictional adopter)\n\n`tests/fixtures/cve-json-config.toml` replaced its Airflow-shaped\nvalues with a fictional \"Apache Example\" project: `apache-example`\ntop-level package, `apache-example-providers-\u003cname\u003e` provider\nlayout, `apache-example-s/apache-example-s` tracker repo,\n`example.apache.org` project URL, etc. The provider display map is\ntrimmed to the providers the tests actually exercise (5 entries:\ncncf-kubernetes, elasticsearch, opensearch, smtp, snowflake);\nunknown providers fall back to the title-cased dash-split path.\n\n`tests/test_generate_cve_json.py` (135 assertions touched via sed):\nall `Apache Airflow` → `Apache Example`, `apache-airflow` →\n`apache-example`, `apache-airflow-providers-*` →\n`apache-example-providers-*`, `airflow-s/airflow-s` →\n`apache-example-s/apache-example-s`, plus 1 manual update for the\nCNA_private envelope assertion (now `example` / `users@example.\napache.org`).\n\n## Other\n\n- `pyproject.toml`: rename root package `apache-airflow-steward` →\n  `apache-steward` (the future canonical name; `name` field is\n  internal-only, no PyPI publication). Rationale captured in a\n  comment.\n- `uv.lock` regenerated.\n\n## Test plan\n\n- 139 tests pass across both Python projects (134 generate-cve-json,\n  58 oauth-draft).\n- `prek run --all-files` passes (all 16 hooks).\n- `zizmor` clean.\n\nGenerated-by: Claude Code (Claude Opus 4.7)\n\n* sanitize: rename `provider` → `project`; switch test data to fictional names\n\nFollow-up to the previous sanitization commit. The earlier pass had\nleft two adopter-shape leaks in the generate-cve-json package:\n\n1. The codebase used **\"provider\"** throughout (PROVIDER_DISPLAY_MAP,\n   provider_product_template, provider_dir, etc.) — a term that comes\n   straight from Apache Airflow\u0027s `apache-airflow-providers-*`\n   sub-package layout. Renamed to **\"project\"** so the framework\n   doesn\u0027t bake in any one adopter\u0027s terminology.\n2. The test fixture\u0027s `project_display_map` (was: provider map)\n   contained the names of real ASF providers / external tools\n   (`elasticsearch`, `snowflake`, `cncf-kubernetes`, `opensearch`,\n   `smtp`). Replaced with clearly-fictional placeholders that\n   exercise the same code paths.\n\n## Renames in cve_json.py\n\n- Constants: `PROVIDER_DISPLAY_MAP` → `PROJECT_DISPLAY_MAP`,\n  `PROVIDER_PRODUCT_TEMPLATE` → `PROJECT_PRODUCT_TEMPLATE`,\n  `PROVIDER_PREFIX` → `PROJECT_PREFIX`.\n- Local vars: `provider_dir` → `project_dir`, etc.\n- Package-name infix: `-providers-` → `-project-` (so the regex now\n  matches `apache-example-project-foo` instead of\n  `apache-example-providers-foo`).\n- Comments / docstrings: every \"provider\" / \"Provider\" / \"providers\"\n  → \"project\" / \"Project\" / \"projects\".\n- Re-export from `__init__.py` updated to match.\n- **Untouched:** the CVE 5.x schema field name `providerMetadata`\n  (line 909, line 53 docstring). That\u0027s the upstream schema, not\n  our terminology.\n\n## Renames in the test fixture\n\n- TOML key: `[packages.provider_display_map]` → `[packages.project_display_map]`.\n- TOML key: `provider_product_template` → `project_product_template`.\n- Template value: `\"Apache Example Providers {display}\"` →\n  `\"Apache Example Project {display}\"`.\n- Regex named group: `(?P\u003cprovider\u003e...)` → `(?P\u003cproject\u003e...)`.\n- Display map entries — replaced 5 real-software names with\n  fictional placeholders that cover the same test cases:\n\n  | Old (real software) | New (fictional) | Test purpose |\n  |---|---|---|\n  | `cncf-kubernetes` → `CNCF Kubernetes` | `acme-xyz` → `Acme XYZ` | multi-word + acronym |\n  | `elasticsearch` → `Elasticsearch` | `foo` → `Foo` | single-word identity |\n  | `opensearch` → `OpenSearch` | `kerfluffle` → `Kerfluffle` | single-word mixed-case |\n  | `smtp` → `SMTP` | `xyz` → `XYZ` | single-word acronym |\n  | `snowflake` → `Snowflake` | `bar` → `Bar` | single-word identity |\n  |   (none) | `pop-corn` → `Pop Corn` | multi-word title-case |\n\n  `brand-new` and `madeup-widget` (used by tests for the unmapped-\n  fallback path) were already fictional and stay as-is.\n\n## Test updates\n\n`test_generate_cve_json.py`: 60+ substitutions to follow the\nrenames. All 139 tests pass against the new fixture.\n\n## SKILL.md\n\nSame terminology pass: \"provider directory\" → \"project directory\",\n\"providers trackers\" → \"project trackers\", etc. The illustrative\npackage examples (`apache-foo-providers-elasticsearch`) are now\n`apache-foo-project-alpha` / `apache-foo-project-beta` — fictional\nsub-project names instead of real software references.\n\n## Test plan\n\n- ✅ 139 tests pass (134 generate-cve-json, 58 oauth-draft).\n  No grepping for \"provider\" picks up anything except the upstream\n  `providerMetadata` CVE schema field.\n- ✅ `prek run --all-files` passes (16 hooks).\n- ✅ `zizmor` clean.\n\nGenerated-by: Claude Code (Claude Opus 4.7)"
    },
    {
      "commit": "53b6d541be5af524eb855301b63aae8604d4a42b",
      "tree": "2b78a95ed4fc4a37c562c4ad0253aa4409b21f71",
      "parents": [
        "9093baac32d23add3feb48e789db8d8a9966d4b3"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Wed Apr 29 03:13:21 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Apr 29 03:13:21 2026 +0200"
      },
      "message": "tests(oauth-draft): expand coverage 62%→99%; add CI tests workflow (#6)\n\n* tests(oauth-draft): expand coverage 62%→99%; add CI tests workflow\n\nAdds 36 new unit tests for the oauth-draft Python project (22 → 58\ntotal) and a dedicated `.github/workflows/tests.yml` CI workflow\nthat runs the per-tool pytest suites as a parallel matrix job.\n\noauth-draft test additions:\n\n- `tests/test_credentials.py` — 3 tests for `refresh_access_token`:\n  successful POST + access-token return, HTTP error path, payload\n  with no `access_token` field. Mocks `urllib.request.urlopen`\n  with a small `_FakeResponse` context-manager stand-in (used in\n  every network-mocked test below as well).\n- `tests/test_create_draft.py` — 13 tests covering:\n  - `read_body` (file path, stdin via \"-\", stdin via None);\n  - `api_get` / `api_post` (request shape + response parsing);\n  - `api_post` HTTP error path;\n  - `latest_reply_headers` (delegates to `api_get` then to\n    `headers_from_thread`);\n  - `create_draft` payload shape with and without `threadId`;\n  - `main` end-to-end with mocked `refresh_access_token`,\n    `latest_reply_headers`, and `api_post` — verifies the MIME\n    body posted to `/drafts` carries the right From/To/Subject/\n    In-Reply-To headers and the body content;\n  - `main --no-reply-headers` skips the thread-headers lookup.\n- `tests/test_mark_threads_read.py` — 9 tests covering:\n  - `list_thread_ids` pagination (multi-page response stitched\n    correctly; second-page request includes `pageToken`);\n  - `list_thread_ids` HTTP error path;\n  - `modify_thread` payload shape (with both label lists, with\n    only one label list);\n  - `modify_thread` HTTP error path;\n  - `main` dry-run (lists IDs, never calls modify);\n  - `main --execute` calls `modify_thread` per thread with the\n    default `removeLabelIds\u003d[UNREAD]`;\n  - `main --max` truncates the modify count;\n  - `main` returns 1 when any modify call fails.\n- `tests/test_setup_creds.py` — new file, 11 tests covering the\n  whole `setup_creds` module (was 22% covered → ~89%):\n  - `detect_from_address` (env var, git-config fallback, missing\n    git, git error, empty git output);\n  - `parse_args` (defaults + overrides);\n  - `main` error paths (no from-address, missing client_secrets,\n    flow returns no refresh_token);\n  - `main` success path: writes credentials JSON atomically with\n    mode 600 in a 700 parent dir;\n  - `main --rm-client-secrets` deletes the input file;\n  - `main` handles both `installed` and `web` shapes of the\n    Google client_secrets.json.\n\nCoverage on `src/oauth_draft/` after the additions:\n\n```\nsrc/oauth_draft/__init__.py             100%\nsrc/oauth_draft/create_draft.py          99%\nsrc/oauth_draft/credentials.py          100%\nsrc/oauth_draft/mark_threads_read.py     99%\nsrc/oauth_draft/setup_creds.py           89%\nTOTAL                                    99%\n```\n\nThe remaining ~1% is the `if __name__ \u003d\u003d \"__main__\":` shim in each\nmodule and a handful of chmod-error warning paths in `setup_creds`\nthat are awkward to provoke deterministically.\n\nCI workflow (`.github/workflows/tests.yml`):\n\n- Runs on every PR and push to main.\n- Per-project pytest matrix (`fail-fast: false`) so the two\n  projects\u0027 results are visible as separate CI checks rather than\n  buried in the bundled `pytest` lines of the `prek` workflow.\n- Uses `uv run --directory \u003cproject\u003e` (not `--project`) to move\n  cwd into the project root — pytest needs it because each\n  project\u0027s `[tool.pytest.ini_options] testpaths \u003d [\"tests\"]`\n  resolves relative to cwd.\n- The `prek` workflow\u0027s pytest hooks still run (defense in\n  depth); this workflow is the visible signal, not the gate.\n\nOther:\n\n- `.gitignore` adds `.coverage` to both Python projects so locally-\n  run `coverage` doesn\u0027t dirty the working tree.\n\nGenerated-by: Claude Code (Claude Opus 4.7)\n\n* ci(tests): force colour output in pytest matrix\n\nGitHub Actions does not allocate a TTY for workflow steps, so by\ndefault `pytest`, `ruff`, `mypy`, and uv all fall back to monochrome\noutput — making test failures harder to scan in the CI log viewer\n(which itself does render ANSI escapes).\n\nTwo fixes, belt-and-braces:\n\n- Set `FORCE_COLOR\u003d1` and `PY_COLORS\u003d1` at the job level. These are\n  the de-facto signals that uv, ruff, mypy, pytest, and most other\n  Python tooling honour to opt back into colour without a TTY.\n- Pass `--color\u003dyes` explicitly to the pytest invocation, for tools\n  that read the CLI flag rather than the env var.\n\nGenerated-by: Claude Code (Claude Opus 4.7)\n\n* tests(generate-cve-json): expand coverage 65%→97% (CLI, gh wrappers, attach)\n\nAdds 35 unit tests in a new `tests/test_cli.py` covering the parts\nof `generate_cve_json.cve_json` that the existing 100-test suite\ndidn\u0027t exercise: the CLI surface (`parse_args`, `main`), the `gh`\nsubprocess wrappers (`fetch_issue`, `_gh_api_json`, `_fetch_issue`),\nand the issue-body attachment helpers (`_splice_attachment_into_body`,\n`attach_to_issue`).\n\nCoverage on `src/generate_cve_json/cve_json.py`: 65% → 97%\n(stmts 590, miss 18). The remaining ~3% is a handful of defensive\nbranches that are awkward to provoke deterministically. Test count:\n100 → 135.\n\nTest groupings (each a TestX class for grep-ability):\n\n- TestParseArgs (4) — minimal positional, --stdin, repeatable\n  flags accumulate, full override matrix.\n- TestSpliceAttachment (4) — replace existing block, legacy\n  single-marker fallback, append after `### CVE tool link` when\n  no existing attachment, append at end when no CVE-tool-link\n  field.\n- TestFetchIssue (3) — happy path returning (title, body), gh\n  missing → RuntimeError, gh non-zero → RuntimeError.\n- TestGhApiJson (5) — parsed-JSON return, empty-stdout returns\n  `{}`, body_payload writes a temp JSON file passed via `--input`,\n  gh missing, gh non-zero.\n- TestFetchIssueRest (2) — happy path, defensive path when\n  `_gh_api_json` returns a non-dict.\n- TestAttachToIssue (3) — appends when no existing marker, replaces\n  when existing marker present, skips PATCH when the spliced body is\n  byte-identical to the existing body (idempotent).\n- TestMainErrors (7) — every `return 2` path: --attach with --stdin,\n  --attach without issue, missing issue without --stdin, gh failure\n  surfaces as `return 1`, --product-for malformed (no `\u003d`),\n  --product-for with empty value, --config not found.\n- TestMainHappyPath (7) — --stdin emits full envelope, --no-envelope\n  emits CNA only, --output writes a file, fetch path uses gh,\n  --attach embeds (was_update\u003dFalse), --attach replaces\n  (was_update\u003dTrue), attach failure surfaces as `return 1`.\n\nAll network-touching code paths use `unittest.mock.patch` against\n`subprocess.run` or the framework\u0027s own `_gh_api_json` /\n`fetch_issue` / `attach_to_issue`. No test hits a real `gh` CLI or\nthe GitHub API.\n\nA `_issue_body()` helper fixture builds a minimal tracker issue body\nwith the standard heading-delimited fields the tool consumes.\n\nGenerated-by: Claude Code (Claude Opus 4.7)\n\n* fix: address CodeQL findings on PR #6\n\nThree CodeQL findings — two warnings on\n`generate_cve_json.cve_json.classify_reference` and two errors on\n`oauth_draft.setup_creds.main` — addressed in one commit.\n\ngenerate-cve-json — `py/incomplete-url-substring-sanitization`\n(warning, two instances on the same line):\n\n  if \"lists.apache.org\" in url or \"security.apache.org\" in url:\n\nA substring `in url` test would also flag attacker-controlled URLs\nlike `https://evil.example/?q\u003dlists.apache.org` as\n`vendor-advisory`. Fixed by parsing the URL with\n`urllib.parse.urlparse` and checking the hostname exactly:\n\n  host \u003d (urllib.parse.urlparse(url).hostname or \"\").lower()\n  if host in (\"lists.apache.org\", \"security.apache.org\"):\n\nAdded 4 regression tests in `TestClassifyReference`:\n  - `security.apache.org` is tagged (parity with `lists.apache.org`);\n  - `https://evil.example/?q\u003dlists.apache.org` is NOT tagged;\n  - `https://evil.example/security.apache.org` is NOT tagged;\n  - `https://lists.apache.org.evil.example/x` is NOT tagged\n    (subdomain trick);\n  - malformed URL string returns no tags.\n\noauth-draft setup_creds — `py/clear-text-logging-sensitive-data`\n(error, two instances):\n\n  print(f\"Running OAuth flow against {client_secrets} ...\")\n  print(f\"Removed {client_secrets}.\")\n\nThe variable held a `Path` object (the filesystem path to\n`client_secrets.json`), not the secret content. CodeQL\u0027s data-flow\nanalysis flags it because the variable name matches its\nsensitive-data heuristic. Renamed `client_secrets` →\n`client_secrets_path` so the print sites are clearly logging a\npath, not a secret. A short `# Variable named `_path` …` comment\nabove the rename documents the rationale so a future reader (or\nPR reviewer) can see why the name diverges from the CLI argument\nname `client_secrets`.\n\nThe existing tests in `tests/test_setup_creds.py` already cover\nboth branches that touch this variable; no test changes needed.\n\nGenerated-by: Claude Code (Claude Opus 4.7)\n\n* tests(setup_creds): assert log lines on renamed `client_secrets_path`\n\nPin down the two `print(... {client_secrets_path} ...)` log lines\nthat were renamed in the previous commit to address the CodeQL\n`py/clear-text-logging-sensitive-data` findings. Without these\nassertions, a future refactor that drops the path from the log\noutput would silently regress the user-facing UX without any test\ncatching it.\n\nBoth `test_main_writes_credentials_file` and\n`test_main_with_rm_client_secrets_deletes_input` already exercised\nthe relevant code paths; the additions just take a `capsys`\nsnapshot and assert the path appears in the expected line:\n\n  - Startup banner: \"Running OAuth flow against \u003cpath\u003e ...\"\n  - On --rm-client-secrets:    \"Removed \u003cpath\u003e.\"\n\nGenerated-by: Claude Code (Claude Opus 4.7)"
    },
    {
      "commit": "9093baac32d23add3feb48e789db8d8a9966d4b3",
      "tree": "178e44f46f080dbb3b538bd623a7adeba8ba628b",
      "parents": [
        "60efac212d7f3f5523b0db5ceae5ae9356abf5ce",
        "c460e841f9e72e3244250c1e455b725d8eb7007b"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Wed Apr 29 02:35:00 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Apr 29 02:35:00 2026 +0200"
      },
      "message": "Merge pull request #5 from apache/add-ci-asf-yaml\n\nci: add prek/zizmor/codeql/allowlist workflows, dependabot, .asf.yaml"
    },
    {
      "commit": "c460e841f9e72e3244250c1e455b725d8eb7007b",
      "tree": "178e44f46f080dbb3b538bd623a7adeba8ba628b",
      "parents": [
        "f0ccc4a407c3480c28ac22038222858b5ea34beb"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Wed Apr 29 02:31:01 2026 +0200"
      },
      "committer": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Wed Apr 29 02:31:01 2026 +0200"
      },
      "message": "ci: install prek via uv (root pyproject.toml with exclude-newer cooldown)\n\nReplaces the `j178/prek-action` step in the pre-commit workflow with\na `uv sync --group dev` + `uv run prek` invocation, and adds a root\n`pyproject.toml` so the framework\u0027s dependency-resolution settings\nhave a place to live.\n\nWhy the swap:\n\n- `uv tool install` does not honor `[tool.uv]` settings from a\n  pyproject.toml in cwd, so the `exclude-newer` cooldown below would\n  not have applied to a `uv tool install prek` invocation.\n- `uv sync --group dev` does — prek is now a declared dev\n  dependency of the framework root, the resolution honors the\n  `exclude-newer` cooldown, and the resolved version is locked in\n  the root `uv.lock` for reproducibility across CI runs.\n\nRoot `pyproject.toml`:\n\n- `[project]` block declares the framework root as a (non-package)\n  uv-managed project. `[tool.uv] package \u003d false` keeps uv from\n  trying to build a wheel from the framework root.\n- `[dependency-groups] dev \u003d [\"prek\u003e\u003d0.3.5\"]` — lower bound mirrors\n  `minimum_prek_version` in `.pre-commit-config.yaml`; upper bound\n  is enforced implicitly by the cooldown below, so the resolved\n  version is \"the most recent prek that is at least 7 days old\"\n  (currently 0.3.10, released 2026-04-21).\n- `[tool.uv] required-version \u003d \"\u003e\u003d0.11.8\"` pins the minimum uv\n  version (latest at time of writing).\n- `[tool.uv] exclude-newer \u003d \"7 days\"` — relative cooldown, applied\n  uniformly across all uv resolutions (including the per-project\n  resolutions for the two Python projects under `tools/`, which uv\n  re-resolved as a side-effect of the root settings).\n- `[tool.uv] exclude-newer-package \u003d { uv \u003d \"1 day\" }` — per-package\n  override for `uv` itself, since the latest uv (0.11.8, released\n  2026-04-27) is within the 7-day window. The TODO comment marks\n  2026-05-05 as the date when this override becomes redundant and\n  can be dropped.\n\nWorkflow change:\n\n- Drop the `actions/setup-python` step. uv brings its own Python\n  via `uv sync` and the per-project hooks already use `uv run\n  --directory ...` for their Python needs.\n- Drop the `j178/prek-action` step. Replaced with `uv sync\n  --group dev` (resolves prek through the root pyproject.toml\u0027s\n  cooldown settings) followed by `uv run prek run --all-files\n  --show-diff-on-failure --color\u003dalways`.\n\nPer-tool lockfile updates (`tools/gmail/oauth-draft/uv.lock`,\n`tools/vulnogram/generate-cve-json/uv.lock`):\n\nThese re-resolved as a side-effect of the new root `[tool.uv]\nexclude-newer` setting being inherited by the subprojects. The\nversion downgrades (e.g. cryptography 47.0.0 → 46.0.7, certifi\n2026.4.22 → 2026.2.25) are within stable releases and all 122 tests\n+ 8 lint/type-check hooks pass against the new resolution.\n\nGenerated-by: Claude Code (Claude Opus 4.7)\n"
    },
    {
      "commit": "f0ccc4a407c3480c28ac22038222858b5ea34beb",
      "tree": "66ba6e73e84b575fc3c8a5442e4f320d771800af",
      "parents": [
        "60efac212d7f3f5523b0db5ceae5ae9356abf5ce"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Wed Apr 29 01:56:58 2026 +0200"
      },
      "committer": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Wed Apr 29 01:56:58 2026 +0200"
      },
      "message": "ci: add prek/zizmor/codeql/allowlist workflows, dependabot, .asf.yaml\n\nBootstraps the standard ASF/security CI surface for the framework\nrepo, mirroring what airflow-s and apache/airflow run. The framework\nhad no CI of its own — the .pre-commit-config.yaml hooks defined\nhooks (ruff/format/mypy/pytest for the two Python projects) but\nnothing was actually invoking them on PRs.\n\nFiles added:\n\n- .github/workflows/pre-commit.yml — runs `prek` on every PR and\n  push to main. Sets up Python and uv before invoking\n  `j178/prek-action`; uv is required because the per-project hooks\n  under tools/{vulnogram/generate-cve-json,gmail/oauth-draft}/\n  invoke `uv run --directory ...`.\n- .github/workflows/zizmor.yml — GitHub Actions security analysis\n  via zizmorcore/zizmor-action. Reads .zizmor.yml at the repo root.\n- .github/workflows/codeql.yml — weekly + per-PR CodeQL analysis\n  for Python (the only hand-written language in this repo). Uses\n  the `security-and-quality` query suite; no security-extended\n  needed (the code is stdlib-only / single OAuth dep and does not\n  process untrusted runtime input).\n- .github/workflows/asf-allowlist-check.yml — ASF infra\u0027s\n  allowlist-check action, scoped to PRs that touch .github/.\n  Catches actions that haven\u0027t been allow-listed by ASF Infra.\n- .github/dependabot.yml — weekly bumps with a 7-day cooldown for\n  four ecosystems: github-actions (root), pre-commit (root), and\n  uv with a directory entry per Python project\n  (tools/vulnogram/generate-cve-json, tools/gmail/oauth-draft).\n- .zizmor.yml — empty rule overrides, so every finding surfaces\n  initially. Add ignores here when accepting a known false\n  positive.\n- .asf.yaml — repo metadata that ASF Infra picks up: description\n  (replacing the current incorrect \"Apache airflow\"), homepage,\n  labels, feature flags (issues/projects/discussions on, wiki off),\n  squash-only merge, no auto-merge, head branch deletion on merge,\n  and notification routing to airflow.apache.org lists (the\n  framework lives under the Airflow PMC umbrella for now).\n\n  Deliberately **no `protected_branches:` block** — branch\n  protection stays in GitHub UI for now until the project\u0027s\n  release/branching policy stabilises.\n\nAll actions are pinned to SHAs (matching the airflow-s convention);\nversions chosen are the latest as used by airflow-s/airflow at the\ntime of writing.\n\nTest plan:\n\n- `prek` passes on the new workflow files (yaml-lint, doctoc skip,\n  end-of-file-fixer all green).\n- `zizmor` v1.24.1 audit of the four workflow files plus\n  dependabot.yml: no findings.\n- The actual CI runs will fire on the PR and validate the workflows\n  themselves end-to-end.\n\nGenerated-by: Claude Code (Claude Opus 4.7)\n"
    },
    {
      "commit": "60efac212d7f3f5523b0db5ceae5ae9356abf5ce",
      "tree": "52554e509fb86cf6bc2f58ad8d950a7e9ce1ca17",
      "parents": [
        "86036ca7a95528c1f3447bd13ae01f24950a41ee",
        "4cbd3db2abb66b8520edadf1ddb4688ef7a4e741"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Wed Apr 29 01:40:39 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Apr 29 01:40:39 2026 +0200"
      },
      "message": "Merge pull request #4 from apache/port-oauth-draft\n\ntools(oauth-draft): port Gmail OAuth helpers into framework"
    },
    {
      "commit": "4cbd3db2abb66b8520edadf1ddb4688ef7a4e741",
      "tree": "52554e509fb86cf6bc2f58ad8d950a7e9ce1ca17",
      "parents": [
        "86036ca7a95528c1f3447bd13ae01f24950a41ee"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Wed Apr 29 01:26:41 2026 +0200"
      },
      "committer": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Wed Apr 29 01:26:41 2026 +0200"
      },
      "message": "tools(oauth-draft): port Gmail OAuth helpers into framework\n\nPorts the three Gmail OAuth scripts that previously lived at\n`airflow-s/airflow-s:tools/gmail/oauth-draft/` into the framework as\nan installable Python project, mirroring the shape of the\n`generate-cve-json` port (PR #2).\n\nThe tool was already largely project-agnostic — the only adopter-\nspecific bit was the default credentials path\n(`~/.config/airflow-s/gmail-oauth.json`), which was a holdover from\nwhen the tracker repo was named `airflow-s`. It now defaults to\n`~/.config/apache-steward/gmail-oauth.json`, aligning with the\n`\u003cproject-config\u003e/apache-steward/` submodule convention.\n\nFiles added:\n\n- `tools/gmail/oauth-draft/pyproject.toml` — three console scripts\n  (`oauth-draft-setup`, `oauth-draft-create`, `oauth-draft-mark-read`).\n  `google-auth-oauthlib` is the only runtime dep (used by setup);\n  the other two commands are stdlib-only.\n- `tools/gmail/oauth-draft/src/oauth_draft/credentials.py` — shared\n  `Credentials` dataclass, `locate_credentials` resolution, and\n  `refresh_access_token` so the three commands don\u0027t duplicate the\n  OAuth boilerplate.\n- `tools/gmail/oauth-draft/src/oauth_draft/{setup_creds,create_draft,\n  mark_threads_read}.py` — one module per command, each with a thin\n  `main(argv)` callable used by both the console-script entry point\n  and the test suite.\n- `tools/gmail/oauth-draft/src/oauth_draft/__init__.py` — re-exports\n  the three `main` functions.\n- `tools/gmail/oauth-draft/tests/{__init__,test_credentials,\n  test_create_draft,test_mark_threads_read}.py` — 22 tests covering\n  credential loading, path resolution, MIME building, the pure\n  thread-header helper, and arg-parsing defaults. No tests hit the\n  Gmail API. The originals carried no tests; this thin set gives the\n  pre-commit `pytest` hook real coverage.\n- `tools/gmail/oauth-draft/README.md` — Run / Setup / How threading\n  is guaranteed / Test / Lint sections, mirroring the\n  generate-cve-json README. The one-time OAuth setup walkthrough\n  that used to live in the airflow-s `oauth-draft/README.md` is\n  folded in here.\n- `tools/gmail/oauth-draft/{.gitignore,uv.lock}` — standard.\n\nFiles updated:\n\n- `.pre-commit-config.yaml` — added the four `oauth-draft-*` hooks\n  (ruff-check, ruff-format, mypy, pytest), scoped to\n  `^tools/gmail/oauth-draft/(src|tests|pyproject\\.toml)`.\n- `tools/gmail/{draft-backends,operations}.md` — switched legacy\n  script-path references (`tools/gmail/oauth-draft/create_draft.py`,\n  `setup_credentials.py`) to the console-script invocation form\n  (`uv run --project \u003cframework\u003e/tools/gmail/oauth-draft\n  oauth-draft-*`). Default credentials path updated to\n  `~/.config/apache-steward/gmail-oauth.json`.\n- `.claude/skills/{sync,allocate-cve,import,invalidate}-\n  security-issue/SKILL.md` — same default-path update; the sync\n  skill also gets the console-script invocation.\n\nTest plan:\n\n- 22 tests pass against the new suite.\n- All four pre-commit hooks pass (ruff/ruff-format/mypy/pytest)\n  plus the standard repo hooks (doctoc, end-of-file-fixer, etc.).\n- All three console scripts respond to `--help` correctly.\n\nCoordination:\n\nA follow-up PR against `airflow-s/airflow-s` will delete the local\n`tools/gmail/oauth-draft/` scripts there (they live in the framework\nnow via submodule) and update local references to point at the\nframework copy. Existing airflow-s users with credentials at the\nold `~/.config/airflow-s/gmail-oauth.json` path can either move the\nfile to `~/.config/apache-steward/` or set\n`\\$GMAIL_OAUTH_CREDENTIALS` — that follow-up PR\u0027s body will flag\nthis.\n\nGenerated-by: Claude Code (Claude Opus 4.7)\n"
    },
    {
      "commit": "86036ca7a95528c1f3447bd13ae01f24950a41ee",
      "tree": "749b95966f6ea08521ca5151c276319ab144c8e5",
      "parents": [
        "cff3af6c339f7b1fa055467e17912b69984098f6",
        "2ac38726e21471f5e8ef3ec9c6fa464a0742001d"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Tue Apr 28 23:30:03 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Apr 28 23:30:03 2026 +0200"
      },
      "message": "Merge pull request #3 from apache/skills-framework-placeholder\n\ndocs: add \u003cframework\u003e placeholder; update Python invocations to use it"
    },
    {
      "commit": "2ac38726e21471f5e8ef3ec9c6fa464a0742001d",
      "tree": "749b95966f6ea08521ca5151c276319ab144c8e5",
      "parents": [
        "cff3af6c339f7b1fa055467e17912b69984098f6"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Tue Apr 28 23:28:27 2026 +0200"
      },
      "committer": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Tue Apr 28 23:28:27 2026 +0200"
      },
      "message": "docs: add \u003cframework\u003e placeholder; update Python invocations to use it\n\nAfter PR 2 ported the generate-cve-json Python implementation into\nthe framework, skill files invoke it from the path\n`tools/vulnogram/generate-cve-json` — which resolves correctly in\nframework standalone (where `cwd` is the repo root) but not in\nadopting projects (where the framework is at the\n`.apache-steward/apache-steward/` submodule path and `tools/` is\nnowhere near `cwd`).\n\nThis commit introduces a `\u003cframework\u003e` placeholder that resolves\nto:\n- `.apache-steward/apache-steward/` in adopting projects (the\n  submodule path);\n- `.` (the repo root) in framework standalone.\n\nSkills now reference `uv run --project \u003cframework\u003e/tools/vulnogram/\ngenerate-cve-json …`, which works in both contexts after the\nagent\u0027s standard placeholder substitution.\n\nFiles updated:\n\n- AGENTS.md — added a new row to the placeholder convention table\n  for `\u003cframework\u003e`.\n- .claude/skills/{sync-security-issue,allocate-cve,deduplicate-\n  security-issue}/SKILL.md — 6 invocation sites updated.\n- tools/vulnogram/generate-cve-json/SKILL.md — 3 invocation sites\n  updated; preamble note already in place from PR 2 calling out the\n  config-driven design.\n- tools/vulnogram/generate-cve-json/README.md — documented the\n  dual-context invocation pattern with the placeholder.\n\nMarkdown LINKS to SKILL.md (e.g. `[generate-cve-json](../../../tools/\nvulnogram/generate-cve-json/SKILL.md)`) are deliberately not\nrewritten — those work via the .claude/skills/ symlink in adopters\nbecause the kernel follows the symlink to the actual file location\nand resolves the relative path from there.\n\nTest plan:\n\n- Pre-commit (`prek run --all-files`) passes after the changes.\n- All 100 tests in the generate-cve-json package pass against the\n  test fixture config.\n\nThis is the framework-side counterpart to airflow-s PR 3\n(delete-local-cve-json-after-port). After this lands and gets\nmerged, the airflow-s PR 3 needs a final commit to bump the\nsubmodule pointer to this commit\u0027s SHA so adopting projects pick up\nboth PR 2 (the Python implementation) and this PR\u0027s skill text.\n\nGenerated-by: Claude Opus 4.7 (1M context) \u003cnoreply@anthropic.com\u003e\n"
    },
    {
      "commit": "cff3af6c339f7b1fa055467e17912b69984098f6",
      "tree": "84bf7b147c16453e6146de694d1cbf28cc3c74ee",
      "parents": [
        "00850ff2dd0662eb51654623c3ad0113c3512563",
        "a25c36f104e8c39c79c7c70911f3b25ddf3fea6a"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Tue Apr 28 23:17:32 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Apr 28 23:17:32 2026 +0200"
      },
      "message": "Merge pull request #2 from apache/port-generate-cve-json\n\ntools(generate-cve-json): port project-agnostic Python implementation into framework"
    },
    {
      "commit": "a25c36f104e8c39c79c7c70911f3b25ddf3fea6a",
      "tree": "84bf7b147c16453e6146de694d1cbf28cc3c74ee",
      "parents": [
        "00850ff2dd0662eb51654623c3ad0113c3512563"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Tue Apr 28 23:16:10 2026 +0200"
      },
      "committer": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Tue Apr 28 23:16:10 2026 +0200"
      },
      "message": "tools(generate-cve-json): port project-agnostic Python implementation into framework\n\nPR 2 of 3 in the generate-cve-json refactor (PR 1 landed at\nairflow-s/airflow-s — refactored the tool to load all project-specific\nvalues from a TOML config). This commit ports the now-project-agnostic\nPython implementation into the apache/airflow-steward framework so the\nframework can ship the implementation alongside the SKILL.md description.\n\nFiles added:\n\n- tools/vulnogram/generate-cve-json/pyproject.toml — Python package metadata.\n- tools/vulnogram/generate-cve-json/src/generate_cve_json/{cve_json,__init__,__main__}.py\n  — the project-agnostic implementation. Loads config at startup\n  from --config CLI flag → $CVE_JSON_CONFIG → \u003ccwd\u003e/.apache-steward/tools/vulnogram/cve-json-config.toml.\n- tools/vulnogram/generate-cve-json/tests/{__init__,conftest,test_generate_cve_json}.py\n  — full test suite (100 tests). Conftest points at a fixture\n  config in tests/fixtures/.\n- tools/vulnogram/generate-cve-json/tests/fixtures/cve-json-config.toml\n  — TEST FIXTURE config (clearly labeled as such). Mirrors one\n  adopter\u0027s setup so the existing tests\u0027 assertions pass without\n  rewriting; NOT shipped as a default for adopters.\n- tools/vulnogram/generate-cve-json/uv.lock — uv lockfile.\n\nFiles updated:\n\n- .pre-commit-config.yaml — added the four generate-cve-json hooks\n  (ruff-check, ruff-format, mypy, pytest) restored from the airflow-s\n  pre-commit config.\n- tools/vulnogram/generate-cve-json/SKILL.md — preamble note\n  clarifying that examples use Airflow\u0027s config as illustration; the\n  tool itself is config-driven and emits CVE records against any\n  adopter\u0027s product taxonomy.\n\nTest plan:\n\n- All 100 tests pass against the test-fixture config.\n- All four pre-commit hooks pass (ruff/mypy/pytest + the standard set).\n\nKnown follow-ups:\n\n- The SKILL.md still has substantial Airflow-flavoured prose in the\n  body (provider directory examples, `apache-airflow-providers-...`\n  package names, etc.). The preamble note flags this; tightening\n  passes can rephrase example-by-example without changing the\n  contract.\n- The test fixture config is Airflow-shaped because the tests were\n  written against that taxonomy. A future PR could replace it with a\n  synthetic (\"Acme Project\") fixture and rewrite assertions to match.\n\nPR 3 (against airflow-s) will delete the local Python implementation\n(it lives in the framework now via submodule) and update skill\nreferences to invoke the framework copy.\n\nGenerated-by: Claude Opus 4.7 (1M context) \u003cnoreply@anthropic.com\u003e\n"
    },
    {
      "commit": "00850ff2dd0662eb51654623c3ad0113c3512563",
      "tree": "65e3e20d3f9bc59a2b64d124fd720481f14079db",
      "parents": [
        "94cd6b95254da3a9a5b4c9abcf2cc28a30929680",
        "19288e3204ccee5312a36076d91ce719a30dd555"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Tue Apr 28 22:47:26 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Apr 28 22:47:26 2026 +0200"
      },
      "message": "Merge pull request #1 from apache/sanitization-pass-1\n\ndocs: tighten Airflow references to placeholders across framework files"
    },
    {
      "commit": "19288e3204ccee5312a36076d91ce719a30dd555",
      "tree": "65e3e20d3f9bc59a2b64d124fd720481f14079db",
      "parents": [
        "94cd6b95254da3a9a5b4c9abcf2cc28a30929680"
      ],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Tue Apr 28 22:45:56 2026 +0200"
      },
      "committer": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Tue Apr 28 22:45:56 2026 +0200"
      },
      "message": "docs: tighten Airflow references to placeholders across framework files\n\nMechanical sanitization pass replacing literal Airflow URLs and\nASF-domain strings with placeholders, leaving illustrative\n\"(example: …)\" parentheticals intact:\n\n- airflow.apache.org/docs/.../security_model.html → \u003csecurity-model-url\u003e\n- https://airflow.apache.org/ → \u003cproject-website\u003e/\n- security.airflow.apache.org → \u003csecurity-list-domain\u003e\n- domain: \"airflow.apache.org\" (in ponymail/gmail code samples) →\n  \u003cproject-domain\u003e\n- `Apache Airflow:` literal title prefix → described as\n  \u003cvendor\u003e: \u003cproduct\u003e: with Airflow as illustrative example\n\nAffects 13 files (5 skills + 6 tool docs + README + CONTRIBUTING).\nRemaining \"Apache Airflow\" references are in placeholder-convention\ntables, \"(example: …)\" parentheticals, and the vulnogram SKILL.md\ndocumenting the airflow-specific reference implementation — all\nwithin the \"Airflow as example in comments\" rule from\n.../airflow-steward repository conventions.\n\nGenerated-by: Claude Opus 4.7 (1M context) \u003cnoreply@anthropic.com\u003e\n"
    },
    {
      "commit": "94cd6b95254da3a9a5b4c9abcf2cc28a30929680",
      "tree": "2362e5b9d1f0d171199f602a7893971d278ea3a6",
      "parents": [],
      "author": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Tue Apr 28 22:22:04 2026 +0200"
      },
      "committer": {
        "name": "Jarek Potiuk",
        "email": "jarek@potiuk.com",
        "time": "Tue Apr 28 22:22:04 2026 +0200"
      },
      "message": "Initial commit — apache/steward (currently apache/airflow-steward)\n\nThis repository hosts a generic, project-agnostic framework for\nrunning an ASF project\u0027s security-issue handling process: skills\n(.claude/skills/), tool adapters (tools/), generic process docs\n(README.md, AGENTS.md, CONTRIBUTING.md, how-to-fix-a-security-issue.md,\nnew-members-onboarding.md), and a project-template scaffold\n(projects/_template/).\n\nAdopting projects pull the framework in as a submodule of their\ntracker repo at \u003cadopter-tracker\u003e/.apache-steward/apache-steward/\nand configure their project-specific identity, rosters, canned\nresponses, release trains, and security model in the parent\n.apache-steward/ directory. The framework refers to that directory\nvia the \u003cproject-config\u003e placeholder.\n\nSource: this content was lifted from airflow-s/airflow-s (the\nApache Airflow security team\u0027s tracker, which used to host the\nframework directly) and sanitized to remove project-specific\ncontent. Apache Airflow remains as illustrative example in\nHTML-comment placeholder tables and \"(example: ...)\" parentheticals;\nno Apache Airflow content is shipped in this framework.\n\nFiles included:\n\n- LICENSE (Apache 2.0), NOTICE (ASF copyright)\n- README.md — the 16-step handling process\n- AGENTS.md — editorial conventions, confidentiality model,\n  placeholder convention, repository purpose\n- CONTRIBUTING.md\n- how-to-fix-a-security-issue.md\n- new-members-onboarding.md\n- .claude/skills/{import-security-issue,import-security-issue-from-pr,\n  sync-security-issue,allocate-cve,fix-security-issue,\n  deduplicate-security-issue,invalidate-security-issue}/SKILL.md\n- tools/{cve-org,github,gmail,ponymail,vulnogram}/*.md\n- projects/_template/* — scaffold for new adopters\n- .pre-commit-config.yaml, .gitignore\n\nNot included:\n\n- The reference Python implementation of generate-cve-json (was\n  airflow-s-specific; refactor to project-agnostic defaults is a\n  follow-up).\n- The reference oauth_curl Python implementation (same).\n- Any project-specific configuration — those live in the adopter\u0027s\n  \u003cproject-config\u003e/ directory.\n\nGenerated-by: Claude Opus 4.7 (1M context) \u003cnoreply@anthropic.com\u003e\n"
    }
  ]
}
