Skip to content
cgwalters-botPublic
forked from cgwalters/homegit

About

My Home Directory

Resources

Stars

0 stars

Watchers

0 watching

Forks

ย 
ย 

Latest commit

ย 

History

722 Commits

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

cgwalters-bot

This is a fork of cgwalters/homegit used as the dotfiles and prompt repository for the cgwalters-bot GitHub account, an agentic bot that helps with upstream contributions. Commits made from this environment are authored as Colin Walters <walters+llm@verbum.org> (see bin/bot-git), and the agents' work is coordinated through the Workstream project board: a human moves items into Todo, the bot claims them, pushes tested branches to forks in the cgwalters-forge organization and proposes them there as draft PRs (Draft), and only once cgwalters approves one does it go upstream (In Review); only a human's acceptance makes something Done.

The accounts, org, board and identities named here are the defaults of the operator config (~/.config/bot-harness/operator.json, printed by bot-operator); another operator sets up their own as docs/bootstrap.md describes.

Principles and direction

The forge board and issues are the control plane, rather than Paperclip.ing's built-in tracker: we need the forge for collaboration anyway, so coordination belongs alongside the work and its review. The board is the operator's primary interface. Periodic summaries belong in its project status updates; chat replies are one line of status plus the update URL. Keep item fields and asks current on the board so the summary points to the work and the decisions where they can be acted on. Agent identity remains an open question: a GitHub App per role (coordinator, engineer), or labels on the board to enforce access control. Paperclip avoids the question with its own identity system.

The direction is sandboxed, forge-native devspace runners with separately validated and applied safe-outputs, moving toward gh-aw. Devspace agent runs describe the current contract; the scheduled dispatcher is the planned move from a local controller session to scheduled and event-driven Actions jobs. Which runners may be used for what is the CNCF-runner rule.

Longer term, a standalone Forgejo + Actions deployment with a control-plane UI could run on Podman Quadlets or Kubernetes.

Install with make install as usual. The shared agent prompt is still AGENTS.md. The skills are plain Agent Skills in dotfiles/.agents/skills, installed as ~/.agents/skills where opencode, Codex, Gemini CLI and Cursor look; Claude Code reads only ~/.claude/skills, so dotfiles/.claude/skills has a symlink to each (tests/skills.sh checks they match). They are workstream (using the board), backlog-planning (refilling the board from recent GitHub activity), bot-feedback (surfacing reactions on the bot's work), bot-notify (routing mentions of and requests to the bot), upstream-pr (how to contribute as the bot, with bot-pr), devspace-work (building and testing on a remote runner), coordinator (running a top-level session that polls, dispatches worker and reviewer subagents, and writes project status updates; the preambles it briefs them with live next to it), topic-lead (a session of its own for one topic, which owns the board items whose Lead field names it and coordinates with the coordinator only through the board and issues), plus the upstream ones like commit-review.

The bot uses two agents. opencode is configured for the Praxis proxy, which supplies its authentication. Claude Code uses a Claude subscription; for headless runs generate a long-lived token with claude setup-token and export it as CLAUDE_CODE_OAUTH_TOKEN. (Anthropic subscription credentials must not be used through opencode.) Both need the gh CLI logged in as cgwalters-bot, either via gh auth login or a GH_TOKEN, with the project scope in addition to the usual repo access.

To run a single unattended session, use bot-work, which by default runs opencode and picks up or advances the next board item; bot-work --agent claude uses Claude Code instead, and any extra arguments are passed along as additional instructions. bot-work --plan instead reviews recent GitHub activity (the last week, or --since YYYY-MM-DD) and adds candidate items to the board for triage. Only one session runs at a time, across all machines: besides a local lock, bot-work holds a lease in an archived board item (bot-state: lease) while the agent runs, which lapses 20 minutes after its machine stops renewing it.

People often react to the bot with an emoji rather than a comment, so the bot-feedback skill has an agent review each new ๐Ÿ‘Ž or ๐Ÿ˜• on everything cgwalters-bot wrote and file it, with an assessment, as an issue on cgwalters-bot/cgwalters-bot for cgwalters; backlog-planning runs it first, and a scheduled job to run it is planned.

Likewise bot-notify polls the bot's notifications: a mention, review request or assignment by cgwalters becomes a Todo item on the board, while one by anyone else is filed as an issue for cgwalters and never acted on. GitHub doesn't reliably notify the bot, so it also checks cgwalters' public events and searches for mentions. Its poll state lives on the board itself, in an archived draft item (bot-state: notifications), so any machine can pick up where the last run stopped. Agents run it at the start of each session and planning pass, and bot-sweep.timer runs it on a schedule as described below.

Pings are only part of it: bot-watch sweeps the issues and PRs of the items on the board and reports what changed on each since it last looked (new comments and reviews, merges and closes, pushes by others, CI going red), and with --apply, when it sees an item's upstream PR merge or close, moves it to Done or Needs human (from Todo, Draft or In Review only). Its last-seen state is another archived item, bot-state: watch. Every sweep also lists the bot's PRs that just need a rebase (behind their base with CI failing, or conflicting); bot-pr rebase URL does that for a conflict-free one, keeping my sign-off, and the coordinator runs it on its own. And it starts with the "Priority health" of the P0 and P1 PRs (bot-priority-health): failing or stuck CI, conflicts, missing sign-offs, and PRs nobody has touched in a day (P0) or three (P1), so that a red P0 PR can't sit unnoticed. Its "P0 drive" (bot-drive) re-derives each bot P0 PR's merge blocker on every sweep, since a PR that falls behind again raises no event, and rebases a conflicting PR, or a behind one nothing else blocks, at most hourly. Its "Operator activity" (bot-operator-activity) reads my own GitHub activity (my public events feed, and the bot's notifications for private repositories), keeps only what touches the bot's work (its issues and PRs, board items, threads it commented in), and has a small model classify each such event once, so a review or reply of mine that no edge-triggered rule caught still wakes the coordinator. With --apply, it also runs bot-pr signoff on the bot's upstream PRs whose DCO check fails although I approved their head (bot-signoff-due), so my approval is all a sign-off takes, and bot-pr promote on the fork PRs whose head I approved for a bot-ok repository (bot-promote-due), so my approval is all a promotion takes too; no model is in that loop. For a human-text repository it promotes only on my /promote --human-text, once the text is mine. When an approval of mine can't promote (the repository is human-text and I only approved, its policy record is missing or stale, the head moved since, or bot-pr promote refused), it comments once on the fork PR with the reason and my next step, and notes it in the board item's News; a new approval that still can't promote gets a new answer. Before promoting (or holding) a fork PR, it also comments there once per policy record with the upstream's LLM rules as the record quotes them: advisory, blocking only where they disallow bot accounts (no-go). And it keeps one tracker issue with its view of each upstream's policy (verdict, what the check said, the last upstream PR it promoted there), marking a record whose sources changed since for a recheck.

The coordinator doesn't run these by hand: bot-sweep, run every 10 minutes by the bot-sweep.timer systemd user unit (in dotfiles/.config/systemd/user), sweeps with bot-watch, bot-notify and bot-pr inbox apart from any agent session, and publishes each completed sweep under ~/.local/state/bot-sweep. bot-poll --from-sweep (Rust, in crates/, installed by make install) reads them and exits only when something is new since it last reported (an approval, a review, a new P0 health problem, ...), or when the sweeps fail or stop, which wakes the coordinator; bot-poll --summary lists what's new. Between sweeps it polls a hot set (the bot's open PRs, my open asks, what I touched lately) every 90 seconds with conditional requests, so my approvals and answers wake the coordinator within a couple of minutes, while a quiet cycle costs two 304s (and a few more per item I'm active on).

Overnight working model

Local Claude workers run with bot-claude start (or run, which also waits). Their default prompt requires noninteractive completion and foreground, blocking waits for dispatched work and verification. bot-claude wait JOB returns 5 for an incomplete job: Claude exited successfully but its final report promises further work, or its stream still shows outstanding background tasks. Failed runs remain failed. Use bot-claude resume JOB, then bot-claude wait JOB, to continue the saved Claude session with the original options. Resume rejects running or successful jobs and jobs without a session id; it archives prior evidence under the job's attempts/N/ and resets current results.

Local agent sessions run on a trusted machine that holds the clones and the bot's credentials, and borrow compute for building and testing; dispatched agent runs use sandboxed devspaces instead. bot-devspace (installed by make install like the rest of bin/) dispatches an ephemeral RHEL 10 runner from bootc-dev/cgwalters-devspace-sandbox, waits for it to be reachable over the tailnet and hands out an ssh_config for its unprivileged runner-sandbox user (the workflow installs the toolchain: podman, gcc, Rust, bcvk, ...), so an agent edits locally, pushes its branch to the devspace with plain git over SSH, runs the project's tests there (KVM is available for VM tests) as detached jobs it blocks on with bot-devspace wait, and pushes the tested branch to the forge fork from the local machine. No credentials are ever copied to a devspace, and the SSH user can't reach the runner's own (see the devspace-work skill); tests/bot-devspace.sh tests which user bot-devspace picks and how jobs end. bot-devspace stop cancels the runner, since they're billed while they run.

Dispatched runs put the agents themselves on devspaces: an agent.yml workflow runs an agent on one board item, with its condensed transcript in the Actions log and a summary artifact per run (plan). bot-runs, modeled on gh aw logs and gh aw audit, dispatches those runs and reads them back (list, show, log, transcript, diff and stats, over REST only). A dispatched run is recorded in its board item's Run field, and bot-runs reconcile (on every bot-watch sweep) moves the item on when the run is over. What a run leaves behind is specified in docs/devspace-agent-runs.md, and tests/bot-runs.sh tests the tool offline against fixtures that follow it.

bot-cost estimates what all of this costs, per UTC day and per task: runner time of the devspace and agent workflows (core-hours at GitHub's published per-minute Linux runner rates, a notional figure for runners that may be self-hosted), and inference tokens from the local transcripts and agent runs' token-usage.jsonl, at the per-model prices from models.dev. The coordinator puts yesterday's estimate in the morning brief. bot-footer turns one task's share into a run footer that fork PRs carry in their bot-meta section (see "Run footer" in the same doc).

Planning is by cost and capacity. Items carry an Est. cost bucket (XS to XL, in fresh inference tokens; see "Cost estimates" in the workstream skill), once an agent works on them a Budget tokens number (bot-pace), and their Actual tokens, which bot-actuals sums from the transcripts of workers whose prompt has an Item: URL line. bot-capacity shows the week's usage, burn rate and projection (from the percent bot-heartbeat statusline saves) next to the open P0/P1 estimates, which the coordinator uses to decide what to dispatch.

Each inference pool is paced on its own, as a budget controller that aims to have used pacing.pools.NAME.target (95%) of the pool's window by its reset, evenly: at any time the pace allows target * elapsed / window, plus a few points of burst. The Claude subscription is read from the status line's weekly window and the OpenAI Codex one from the praxis broker's /usage. A pool whose usage is above its pace holds new work routed to it and nothing else: Claude's the local workers and the apply and review steps, OpenAI's the devspace runs. An item labeled urgent goes anyway, and a pool without a reading is limited only by the agent target. bot-capacity also records what a run costs each pool, which says how many more fit.

The coordinator's loop is a controller: bot-reconcile compares the observed state (the board, the latest sweep, the heartbeat, the pace of each inference pool, answered questions) with the desired one and prints the actions that close the gap, under rules in lib/reconcile.js: keep the operator config's pacing.agents work agents busy, split between harness and upstream work; keep the heartbeat in step with the board; carry over the sweep's P0 drive and approvals; apply answered questions; set items Done whose issue or PR closed (which bot-watch --apply does itself). bot-poll-loop --until-actions waits on the sweeps and wakes the coordinator on news and on new (or, after 2 hours, still open) actions, run as one background command that exits only then (or TIMEOUT at its --max-wait, to be rerun).

bot-heartbeat publish puts the coordinator's loop state and its local workers (names, board item links, devspaces) in one pinned comment on the "Bot heartbeat" issue in cgwalters-forge/tracker, edited in place, where the review app's ops view reads them as "Local agents"; it validates the input against a strict schema and leaves out items in private repositories. It also writes a usage snapshot, the ops view's "Usage", but never to that public comment: to one comment on the "Bot usage" issue in the private cgwalters-forge/bot-ops, which the review app reads with the viewer's own token. The snapshot is the plan's 5-hour and 7-day percent used and reset times, which bot-heartbeat statusline saves as the Claude Code status line, and the tokens local transcripts spent in each window and per worker (aggregate numbers only). tests/bot-heartbeat.sh and tests/bot-heartbeat-usage.sh test it offline.

bot-board is a small CLI over gh project for the Workstream board (list, show, add, draft, set, and state-get/state-put for the scripts' state items), which caches reads because the bot's GraphQL quota is shared by every agent. The state items are what lets any machine run a session: nothing a script needs to remember lives only on one machine, and state-put --checked merges the changes of two machines writing the same item at once. tests/bot-state.sh tests that state handling offline, against a fake gh. Each item's Workflow field says what the bot delivers: a tested branch proposed as a draft PR on a forge fork (branch, the default), a write-up in a secret gist (analysis), a draft PR straight upstream (pr, set only by a human), or nothing (manual). In the morning, Draft items link to what's ready for review.

Project status updates

bot-board status-update creates a native GitHub project status update and prints the project's URL. It defaults to Workstream; put --project BOARD before status-update to target another board. The generated report has Changed, In progress, and Waiting on the operator sections: merges, closes, completions, promotions and changed News; active items with agents or runs and upstream review; Draft reviews and Needs human decisions/actions with Why and Branch/Gist links. Worker names and states come only from the local heartbeat's privacy-filtered publication, with activity (or start time) less than six hours old and an exact association to an unfinished item in this project (item URL/ID or a Run/Branch URL). Unmatched workers are omitted; Lead is the fallback for an In Progress item without a matching worker. Waiting asks follow the documented board statuses and labels, not exact review-app parity: that app is unavailable locally. An open Needs human ask with a first-line Blocks: ITEM link replaces its Needs human parent in the waiting section.

Each section shows at most ten one-line items in deterministic priority/title order, followed by an accurate +N more count and a full-board link. Titles and details are shortened; long or unsafe item URLs are omitted rather than truncated (follow the board link). The stale-P0 footer shows at most three titles and the remaining count. Generated posts, including their hidden recovery marker, are limited to 3,800 characters, below 4,000.

Explicit posts can combine these flags:

  • --status on-track|at-risk|off-track overrides the derived project status.
  • --since ISO chooses the change window; use a full ISO timestamp with seconds and a timezone, e.g. 2026-10-03T00:00:00Z. By default the window starts at the last project post (or the successful local post if newer), or 24 hours ago when there is no post. Completion/promotion transitions and News comparisons also use the saved observation baseline; --since applies timestamp filtering instead of the usual baseline-only checks. Day-only News without a reliable News field timestamp is excluded if the date's UTC midnight predates the explicit cutoff. The default window still retains same-day News without a baseline and compares text with one.
  • --body FILE replaces the generated text with a nonempty UTF-8 file, for a curated periodic summary. Status is still derived unless overridden, and the tool still appends its recovery marker and saves the observation.

Flags, the P0 threshold, and custom body readability, UTF-8 encoding, nonempty content and the standalone size limit are checked locally before project resolution or any API call. The full body size, including the recovery marker, is checked after reading the board and before posting. Custom --body posts retain GitHub's 65,536-character limit, including the marker; the generated-post budget does not apply to curated text.

Without --status, an unfinished P0 is stale after 24 hours without observed material movement: Status, Priority, Lead, Branch, Why/Gist or News content, title/body/state changes, or the latest comment's content. Token counters, Run changes, timestamps and News date-prefix churn do not reset this clock. On first observation (or recovery from an older marker), project/content timestamps provide the initial evidence; bookkeeping before that observation cannot be distinguished from real activity and can delay risk detection. Once a material baseline exists, generic updatedAt never overrides it. Changes between observations that revert before the next read are not seen; comment activity is limited to the latest comment, not all PR review activity. Any stale item makes the project at-risk; otherwise it is on-track. Done, closed and merged items are finished. BOT_BOARD_STATUS_P0_HOURS changes this threshold and must be a positive number. off-track requires an explicit override.

bot-sweep runs bot-board status-update --auto as its final phase after the watch/notify/inbox and gc/actuals/fill-org phases, with a two-minute timeout. Earlier step failures do not gate that phase. --auto cannot be combined with --status, --since or --body: it skips if the latest project update or successful local post was less than four hours ago, including a human's project update, then skips if the material observation is unchanged. The digest covers item fields, state, ask class, worker names/item URLs/states and derived status; timestamps and the News date prefix alone do not count. Aging a P0 into at-risk therefore counts as a change. A skip prints status-update: skipped (four-hour throttle) or status-update: skipped (no material change) and succeeds. Explicit posts bypass both checks. A failed status-update step is a sweep problem but does not by itself make the watch/notify/inbox news incomplete; its output is in runs/ID/status-update.txt, not a latest-*.txt news feed. A successful publisher's project URL is also printed in the sweep's final stdout.

Run one publisher on one machine per project. bot-board serializes local posts with a per-project flock and stores status-update.json under ${XDG_STATE_HOME:-~/.local/state}/bot-board/KIND/OWNER/NUMBER/. Project updates carry an HTML recovery marker with the digest and minimal item baseline, including material fingerprints and movement times, when that fits the posted-body budget. Otherwise the marker contains only the digest (marked digest-only). The local snapshot always retains the full baseline. If local state is lost after a compact-marker post, remote recovery still provides automatic deduplication and throttling, but cannot infer completion/promotion transitions or compare News against that post's per-item baseline. It uses timestamp evidence for News and initial P0 inactivity, and never manufactures transitions. Losing movement history can delay stale-risk detection after timestamp-only activity. A subsequent successful local post restores the full local baseline. A separate status-update.json.observations.json records movement on every observation, including auto skips, without losing the last posted transition baseline. The marker lets the next run recover after a post succeeded but saving the local snapshot failed. Reading those updates does not provide a cross-machine lock or an atomic check-and-post: concurrent publishers on different machines can still duplicate a post.

The GraphQL payload uses createProjectV2StatusUpdate with projectId, status and body, returning statusUpdate { id body createdAt creator { login } project { url } }. These fields and the enum were checked against the live schema by introspection. The project URL avoids assuming a status-update url field or deep-link format.

bot-tmt-number hands out bootc's tmt test numbers (the # number: N header that becomes plan-N and test-N), which concurrent bot branches used to collide on. The next free number is one past the highest taken on main, in the added lines of every open PR upstream and on the forge fork (read with conditional REST requests, each PR rescanned only when its head moves), and in the reservations workers make with --reserve BRANCH before they open a PR. Those live in the tmt-numbers board state item rather than in a committed file: a reservation has to be visible to the other workers within seconds, and landing a homegit PR takes minutes of CI, while a strict checked state-put (retried when another machine wrote meanwhile) plus a local lock gives that at the cost of one GraphQL call each. --gc releases a reservation once its number is on main or in its branch's open PR, or its PR closed; tests/bot-tmt-number.sh tests it offline against a fake gh.

Reviewing the bot's work

The bot doesn't open upstream PRs on its own. A finished branch item is a draft PR from bot/<slug> into the upstream base branch of a fork in cgwalters-forge (cgwalters-forge/REPO), linked from the item's Branch field, whose title and description are already written as the upstream PR. A trailing section between <!-- bot-meta --> markers names the upstream repository and base branch, and the board item. On that PR:

  • Approve it, or comment /promote on a line of its own, to have it opened upstream, ready for review. Other wording ("go ahead", "ship it") is not an approval; the bot's inbox flags it so you get asked for /promote.
  • Add a /draft line (in the /promote comment, or earlier) to have it opened upstream as a draft (a later /ready takes that back).
  • Comment (inline or on the PR) to ask for changes, including commit message rewording; the bot answers with squashed fixups and a reply. An approval or /promote covers the commits pushed before it, so changes pushed after it need another one.
  • Edit the title and description as you like: they are copied upstream as they stand at approval, minus the bot-meta section.
  • Close it to drop the change; the item becomes Done as dropped.

Forge forks start with a clean main synced from upstream, and both accounts can push to them. They run no CI: every workflow is disabled (bot-pr fork-setup), because the forks share one runner pool that a single bootc PR's matrix fills, and the bot tests every change on a devspace, whose results the PR description gives. Upstream CI runs once the PR is promoted. A change to a workflow itself is exercised by opting that workflow in (bot-pr fork-setup REPO --ci FILE, undone with --no-ci). A fork is either a midstream (the default: repository topic bot-midstream, main mirrors upstream, its PRs are drafts that are never merged, and fork-setup adds a ruleset that blocks merging them) or a real, possibly temporary, fork (bot-fork, set with bot-pr fork-setup REPO --fork), whose main may carry its own commits. The bot's personal forks (cgwalters-bot/REPO) are only for scratch work.

bot-pr implements the bot's side: fork-pr creates the forge fork if needed and opens the fork PR, get-body and set-body update its description without overwriting your edits, refresh-meta brings its bot-meta section up to date when the template changes, inbox lists your activity on them since its last run (only the cgwalters login counts, so the bot's own edits, pushes and comments are left out), and promote rebases onto upstream, opens the upstream PR, closes the fork PR and moves the item to In Review. Agents run bot-pr inbox at the start of every session. What inbox has already reported is kept in the archived bot-state: pr-inbox board item, so it doesn't matter which machine runs it.

Every forge PR also gets a review guide from the reviewer subagent that checked it: the ordered hotspots worth reading closely (a head-side line range, a severity, a category and a one-sentence reason) and what is safe to skim. bot-review-guide prints what a reviewer needs to write one (context), validates it against the PR (check), posts it as a COMMENT review with the JSON in an HTML comment (post) and reads back the latest one (show). The review app trusts only the bot's guides, and only for the head they name, walks their hotspots and tints them in the diff; tests/bot-review-guide.sh tests the tool offline.

The bot's own repositories

The bot's own repositories (this one, cgwalters-bot/cgwalters-bot, debug-bootc-to-disk-virtiofsd, ostree-missing-refs and praxis-credential-broker, plus cgwalters-forge/review, cgwalters-forge/agentic-job and cgwalters-forge/.github) take changes only as pull requests: a ruleset on main requires one with green required checks, up to date with main, and linear history, with no bypass, and blocks force-pushes and deleting main. They only allow rebase merges, to keep the commits as written, with auto-merge on and branches deleted on merge. bot-land lands a branch: it pushes it, opens the pull request, enables auto-merge and waits, rebasing when main moved on. With --base PARENT (and --no-auto) it stacks a branch on another unmerged one, and once the parent merged it retargets the pull request to main, dropping the parent's commits. A rebase merge re-commits the commits unsigned, with the merging account (the bot) as committer. Here the ci workflow (.github/workflows/ci.yml) runs shellcheck, node --check, the crates' checks and every tests/*.sh in parallel jobs, and the ruleset requires only its required-checks job, a gate that fails unless every other job succeeded or was skipped by its own condition, as bootc's CI does: jobs can be added, split or made conditional without touching the ruleset (tests/ci-gate.test.js checks that the gate needs them all and runs always()). Other repositories may require just a ci job; bot-land waits for whatever checks the ruleset requires.

Contribution policy gate

Some projects accept AI-assisted code but want the PR text to be a human's, and some want no AI contributions at all. So promote and signoff first run upstream-policy check OWNER/REPO, which reads the record in upstream-policy/OWNER/REPO.md here: a verdict (bot-ok, human-text, human-only or no-go), the AI trailer and DCO expectations, and the blob ids of every policy file it was based on (CONTRIBUTING, AGENTS.md, PR templates, AI policies, the org's .github defaults), with the quotes and rationale in its body. They refuse unless the record is committed, every source is unchanged on upstream's default branch, no new policy file has appeared, and the verdict is bot-ok. A separate read-only subagent writes the records (dotfiles/.agents/skills/coordinator/policy-check.md); to overrule one, I have the bot open a pull request that edits its verdict, and approve it. Records count only as merged to homegit's main, and a commit there that loosens a verdict (say human-text to bot-ok) must come from a pull request whose merged head I approved (or be one of mine that GitHub shows as verified), so the bot can record and tighten verdicts but not loosen them: a record's verdict may be no looser than the strictest it held since I last loosened it, across edits, deletions and moves (git's rename detection, and paths that differ only in case), so anyone else's loosening blocks the gate until it's tightened back. Records must be regular files, not symlinks. A ruleset on this repository blocks force-pushes to main and deleting it (and takes changes only as pull requests, see below), and the check also refuses a main that doesn't descend from the last one it accepted (kept in ~/.local/state/upstream-policy/), since rewritten history could make a loosened record look newly created.

For human-text, I retitle the fork PR, rewrite its body and reword the commits myself, push them myself, and approve with a /promote --human-text line; promote checks that GitHub shows me as the pusher of the approved head and the last editor of the title and body, and adds nothing to the body. Or I open the upstream PR myself. The one exception is a thread where I summon the bot (an @-mention with an ask, or a review request): that is my consent for it to answer there itself, as a reply or a COMMENT review, never as a tracker question or a gist for me to post. tests/upstream-policy.sh tests the check offline.

Open decision: a project whose policy files say nothing about AI is recorded as human-text for now, to be safe. If that turns out too strict, the policy-check prompt is where to change it.

Signing off the bot's PRs (DCO)

The bot never adds Signed-off-by itself. When I approve a fork PR (or comment /promote) for a project that requires DCO (bootc-dev, among others), that approval is my sign-off: bot-pr promote adds my Signed-off-by to the bot's commits, with me as committer, before opening the upstream PR, and names the approval in its body. Others' commits get it only with --include-others. A project requires DCO if its branch rules require the DCO check or if the DCO app's check runs there anyway. For an upstream PR promote opened without my sign-off, bot-pr signoff <upstream-pr-url> adds it on the same approval, as long as the PR's head is still what promote opened it with.

Upstream PRs that didn't go through promote still sit with a failing DCO check until a human signs off. bin/dco-signoff, run by me with my own git identity and gh login, finds them and fixes that:

dco-signoff --dry-run      # list them and prepare the rewrite, push nothing
dco-signoff                # ask before pushing each one
dco-signoff https://github.com/bootc-dev/bootc/pull/2494 --yes

It looks at the open PRs authored by cgwalters-bot and those from cgwalters-forge branches, keeps those whose base branch requires a DCO status check, and for each one missing my sign-off rebases its commits with git rebase --signoff onto their existing merge-base (hooks off), checks that every rewritten commit has the original tree and author, and force-pushes with a lease on the head it fetched. It refuses to run as the bot, and to sign off other people's commits without --include-others; tests/dco-signoff.sh tests it offline.

To pick up changes from the upstream repository:

git fetch upstream && git merge upstream/main

Colin's dotfiles and misc. scripts

I keep basic config files like ~/.emacs and ~/.bashrc in git. There might be something useful in there for you. I also have a collection of miscellaneous scripts in bin/, mostly git wrappers.

If you like this repository, you might also find my personal Ansible https://github.com/cgwalters/ansible-personal repository useful.

Agent configuration

This repository also contains AGENTS.md, my default system prompt for agents.

Praxis credential broker Quadlet

The rootless Praxis credential broker Quadlets are tracked in dotfiles/.config/containers/systemd/praxis-credential-broker/. The normal install-dotfiles.sh deployment places them under ~/.config/containers/systemd/. Each pod and container unit is gated with ConditionHost=xenon, so the user manager will not start it on another host.

The Praxis checkout must remain at ~/src/github/cgwalters-bot/praxis-credential-broker: the gateway unit mounts %h/src/github/cgwalters-bot/praxis-credential-broker/praxis.yaml, which systemd expands to the user manager's home directory. Podman secrets and the persisted authentication volume are deliberately not tracked here.

Do not install this alongside an older manually managed Quadlet directory or symlink. Replace that deployment only in a planned migration, then run systemctl --user daemon-reload; this prevents duplicate unit sources and does not require putting credentials in this repository.

About

My Home Directory

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages