Community health files and automated control center for the AI-powered GitHub bot
Overview · Features · Branding · Getting Started · Repository Structure · Development
Fro Bot is an AI-powered GitHub bot for repository review, maintenance, and control-plane automation. This repository is the org-level control center: community health files, shared configuration, workflow definitions, metadata state, the knowledge wiki, and agent guidance for managed repositories.
What Fro Bot Does:
- Reviews pull requests and triages issues opened/edited by trusted authors (OWNER/MEMBER/COLLABORATOR) under the
@fro-botidentity - Accepts allowlisted collaborator invitations, stars onboarded repositories, and tracks them in auditable metadata
- Ingests collaborator repositories into the knowledge wiki and lints the authoritative snapshot from
data - Dispatches Renovate across tracked repos and refreshes org metadata on schedule
- Enforces consistent repository settings, branch protections, and community-health defaults
🤖 AI-Powered Automation
- Pull request review, issue triage, and scheduled oversight via the Fro Bot agent workflows
- Repo-ingest, journal, and social-broadcast plumbing for autonomous status updates
- Knowledge capture through the Karpathy-style wiki and
docs/solutions/learnings
🔧 Repository Management
- Centralized community health files (
README.md,SECURITY.md,LICENSE.md) - Automated repository settings synchronization via Probot Settings
- Auditable control-plane state in
metadata/*.yamlon thedatabranch
🚀 CI/CD Integration
- GitHub Actions workflows for CI, reconcile/onboarding, metadata promotion, and wiki linting
- Automated dependency management with Renovate plus tracked-repo dispatch
- Security scanning with CodeQL, Dependency Review, and OpenSSF Scorecard
⚙️ Development Standards
- Bun-run TypeScript scripts with strict type-checking and Node strip-only compatibility checks
- ESLint + Prettier enforcement and colocated Vitest coverage for
scripts/*.ts - Canonical AI-assistant guidance in
.github/copilot-instructions.md
This repository is the central hub for Fro Bot's visual identity. It contains the brand assets, design tokens, and automation to apply consistent branding across all Fro Bot repositories.
| Asset | Description |
|---|---|
assets/banner.svg |
Social banner for this repo (1280×640) |
assets/banner-template.svg |
Parametric SVG template with {{PLACEHOLDER}} tokens |
assets/styleguide.md |
Complete design system (colors, typography, spacing, components) |
assets/fro-bot.png |
Brand avatar |
branding/README-template.md |
Skeleton README following brand guidelines |
branding/tokens.css |
CSS design tokens for downstream use |
Use the Apply Branding workflow to apply the template to any Fro Bot repository:
- Go to Actions → Apply Branding
- Enter the target repository (e.g.
fro-bot/some-repo) - Optionally provide a tagline
- Run the workflow
The workflow will generate a branded banner, styled README, and open a PR in the target repo.
The Fro Bot visual identity follows the Afrofuturism × Cyberpunk aesthetic:
| Token | Color | Usage |
|---|---|---|
--frobot-void |
#0D0216 |
Deepest background |
--frobot-purple |
#1A0B2E |
Primary dark surface |
--frobot-cyan |
#00BCD4 |
Primary accent, links |
--frobot-magenta |
#E91E63 |
Secondary accent, CTAs |
--frobot-amber |
#FFC107 |
Highlights, badges |
See the full styleguide for typography, spacing, WCAG compliance, and component patterns.
This repository provides shared configurations and automation for the Fro Bot ecosystem. To contribute or customize:
- Bun (exact version pinned in
package.jsonunderpackageManager; installs dependencies, runs package scripts and runsscripts/*.tswith no build step) - Node.js 24 (pinned in
mise.toml; runs Vitest, Stryker, the private-leak scan, the "Test Scripts Load" job, thecoveragescript andcheck:renovate-config) - Git for version control
- Optional:
miseto auto-install the pinned toolchain
-
Clone the repository:
git clone https://github.com/fro-bot/.github.git cd .github -
Install dependencies:
bun run bootstrap
-
Run quality checks:
# Type checking bun run check-types # Linting (ESLint runs Prettier via eslint-plugin-prettier) bun run lint # Renovate preset gate: validator (--strict --no-global) + allowedVersions range check. # Needs network and Bun: installs the Renovate pinned in .github/workflows/main.yaml # (env.RENOVATE_VERSION) into $RENOVATE_PREFIX (default: <tmp>/renovate-<version>), outside # node_modules and the lockfile, then fetches version-pinned github> presets. bun run check:renovate-config # Tests (Vitest, colocated as scripts/*.test.ts) bun run test # Coverage bun run coverage
[!NOTE] A few
scripts/*.test.tsfiles (e.g.fro-bot-workflow.test.ts) shell out tobashand a fakegitfixture to exercise workflowrun:blocks directly; these are verified on Linux CI and macOS, not tested on Windows. -
Auto-fix issues:
# Auto-fix lint and formatting via ESLint bun run fix
[!TIP] This repository follows strict development standards. Make sure to run quality checks before committing changes.
.github/
├── .agents/ # Repo-scoped agent skills
│ └── skills/ # Self-contained SKILL.md references
├── .github/ # GitHub-specific configurations
│ ├── actions/setup/ # Composite bootstrap action
│ ├── hooks/ # Copilot governance hooks
│ ├── workflows/ # 33 GitHub Actions workflows (see Automation)
│ ├── copilot-instructions.md # Canonical AI-assistant guidance
│ ├── renovate.json5 # Dependency management config
│ └── settings.yml # Repository settings via Probot
├── assets/ # Brand assets (banner, avatar, styleguide)
├── branding/ # Downstream branding templates
│ ├── README-template.md # Skeleton README applied by Apply Branding workflow
│ └── tokens.css # CSS design tokens
├── docs/ # Planning artifacts (brainstorms, plans, solutions, archive)
├── knowledge/ # Karpathy-style LLM wiki
│ ├── schema.md # Conventions for wiki entries
│ ├── index.md # Catalog of all wiki pages
│ ├── log.md # Chronological ingest log
│ └── wiki/{repos,topics,entities,comparisons}/
├── metadata/ # Versioned control-plane state (YAML)
├── packages/ # Workspace packages (see Packages)
│ ├── core/ # @fro-bot/core: guards, GITHUB_OUTPUT and step-summary writers, JSON output, retry, Octokit client factory, goke CLI runner, env reader, the PR wiki-authority check (private)
│ ├── privacy/ # @fro-bot/privacy: the private-leak and wiki-presence gates, the public-output gate, token loaders, operator tools (private)
│ ├── registry/ # @fro-bot/registry: the metadata writers, reconcile, invitations, survey results and data-branch merges (private)
│ ├── test-kit/ # @fro-bot/test-kit: shared test fakes and helpers (private)
│ └── wiki-write-core/ # @fro.bot/wiki-write-core: the published wiki-write primitives
├── persona/ # Fro Bot voice and character definition
├── scripts/ # TypeScript entrypoints (run with Bun) and their tests
├── common-settings.yaml # Shared repository settings for downstream repos
├── eslint.config.ts # Linting configuration
├── mise.toml # Pinned tool versions
├── package.json # Project metadata and scripts
├── tsconfig.json # TypeScript configuration
└── vitest.config.ts # Test runner configuration
packages/ is a Bun workspace ("workspaces": ["packages/*"]). Shared code lives in private @fro-bot/ packages that export .ts source with no build step, so Bun, Node strip-only loading, tsc and Vitest all load them as written. @fro.bot/wiki-write-core stays the only published package. It resolves to source here too; the release build compiles dist/ and publishes a manifest whose exports point at it.
| Package | Status | Purpose |
|---|---|---|
@fro-bot/core |
In the repository | Type guards, $GITHUB_OUTPUT and step-summary writers, JSON line output, retry, Octokit client factory, goke CLI runner, required-env reader, and the PR wiki-authority check. Held at 100% mutation kills. |
@fro-bot/privacy |
In the repository | The privacy family: the PR and promotion private-leak scan, the wiki-presence gate, the public-output gate, the private-token and redacted-ID loaders, and the operator lookup and enumeration tools. scripts/ keeps one entrypoint per CLI. Four modules are held at 100% mutation kills. |
@fro-bot/registry |
In the repository | The writers of metadata/repos.yaml and the data-branch merges: the metadata writers, reconcile-repos (split into nine seams), invitation polling, the survey write-back and reset, the onboarded gate, merge-back-data, merge-data-pr, the promotion self-merge (promotion-qualify, promotion-self-merge), the Renovate approver (renovate-approve) with the required-check logic it shares with the promotion step (required-checks), protection-drift and rename-tracked-repo. scripts/ keeps one entrypoint per CLI. merge-back-data, promotion-qualify, promotion-self-merge, renovate-approve, required-checks and protection-drift are held at 100% mutation kills. |
@fro-bot/dispatch |
In the repository | The dispatchers: cross-repo-dispatch (split into seven modules by responsibility) and dispatch-renovate. scripts/ keeps one entrypoint per CLI. |
@fro-bot/test-kit |
In the repository | Shared test fakes and helpers. Imported by tests only. |
@fro.bot/wiki-write-core |
Published | Wiki-write validation and commit primitives. |
@fro-bot/status-truth |
In the repository | The status-truth loop (status-truth-detect and status-truth-proposals, each split into seams, and status-truth-prs) and the rollout-tracker-snapshot preflight. scripts/ keeps one entrypoint per CLI. It depends on @fro-bot/core, @fro-bot/privacy and @fro.bot/wiki-write-core, whose frontmatter splitter finds the plan frontmatter block. |
@fro-bot/wiki-ops |
In the repository | The wiki handoff (baseline, build, apply), change detection, the wiki and solutions queries, wiki-context-expand, wiki-repair, wiki-lint-issues, the index rebuild, the corrections lifecycle and context, knowledge-mcp, the knowledge MCP server that serves wiki and solutions lookups to agent runs, and route-stderr, the warning router that lifts routed stderr lines into the job summary. scripts/ keeps one entrypoint per CLI. wiki-context-safety, wiki-change-detect-core, knowledge-mcp and route-stderr are held at 100% mutation kills. |
@fro-bot/knowledge |
In the repository | The learning capture (capture-learnings-harvest and -open), the pattern synthesis (capture-patterns-cluster, -open and -synthesis) and the drafted solution docs (drafted-solutions-harvest, -validate-rows, -handoff-build, -publish, -pr-body, -shared). scripts/ keeps one entrypoint per CLI. It imports @fro.bot/wiki-write-core, whose frontmatter splitter finds the solution-doc block, and never @fro-bot/status-truth. |
@fro-bot/reports |
In the repository | The improvement-metrics loop (improvement-metrics-detect, -report and -core), daily-digest-counts and gateway-announce. scripts/ keeps one entrypoint per CLI. The four CLI modules and gateway-announce are held at 100% mutation kills. It depends only on @fro-bot/core, @fro-bot/privacy and @fro.bot/wiki-write-core. |
@fro-bot/ci-checks |
In the repository | The repository checks: the mutation gate (check-mutation-guards, which resolves its root from the package; in CI it runs as three shards, mutation-shard, and a final mutation-merge job) with mutation-guards-reach and mutation-shard-lists, the md-links, override-floors and solutions-examples lints, and the Renovate tooling (install-renovate, check-renovate-config, check-renovate-ranges, renovate-runtime). scripts/ keeps one entrypoint per CLI. The seven CLI modules, barrel-exports, check-override-floors, install-renovate, mutation-guards-reach, mutation-shard, mutation-shard-lists, mutation-merge and repository-root are held at 100% mutation kills. It depends on @fro-bot/core, @fro.bot/wiki-write-core, typescript and yaml. |
scripts/package-layout.test.ts enforces the package rules: every @fro-bot/ package is private and type: module, internal dependencies are workspace:*, every import is declared, no relative import leaves its package, no non-test module imports the test kit, the dependency graph has no cycle, and each manifest matches its bun.lock workspace entry.
The graph below is the authoritative one: scripts/package-layout.test.ts checks the real graph, and this diagram is maintained by hand. An arrow points from a package to what it depends on; the dotted arrow is a devDependencies edge.
flowchart BT
core["@fro-bot/core"]
testkit["@fro-bot/test-kit<br/>(imported by tests only)"]
wwc["@fro.bot/wiki-write-core<br/>(published, unchanged)"]
privacy["@fro-bot/privacy"]
registry["@fro-bot/registry"]
dispatch["@fro-bot/dispatch"]
statustruth["@fro-bot/status-truth"]
wikiops["@fro-bot/wiki-ops"]
knowledge["@fro-bot/knowledge"]
reports["@fro-bot/reports"]
cichecks["@fro-bot/ci-checks"]
core -. "dev: spawn tests" .-> testkit
core -. "dev: authority test" .-> wwc
privacy -. "dev: fixtures" .-> testkit
statustruth -. "dev: fixtures" .-> testkit
wikiops -. "dev: fixtures" .-> testkit
registry -. "dev: fixtures" .-> testkit
knowledge -. "dev: fixtures" .-> testkit
reports -. "dev: fixtures" .-> testkit
cichecks -. "dev: fixtures" .-> testkit
privacy --> core & wwc
registry --> core & wwc
dispatch --> core & wwc
wikiops --> core & wwc & privacy
statustruth --> core & privacy & wwc
knowledge --> core & privacy & wikiops & wwc
reports --> core & privacy & wwc
cichecks --> core & wwc
How the packages relate to the scripts/ entrypoints, the workflows and GitHub. Workflow paths do not change when code moves into a package: each scripts/<name>.ts stays as a thin entrypoint.
C4Container
title Containers of fro-bot/.github
Person(operator, "Operator", "Dispatches workflows and approves changes")
System_Boundary(repo, "fro-bot/.github") {
Container(workflows, "Workflows", "GitHub Actions YAML", "Schedules and events; run scripts/<name>.ts")
Container(scripts, "scripts/", "TypeScript on Bun", "Entrypoints, the build and release scripts, wiki-lockfile-gates (a shim over wiki-lockfile-gates-core) and the four wiki-write-core wrappers")
Container(core, "@fro-bot/core", "Private package", "guards, output, json, retry, github, cli, env")
Container(privacy, "@fro-bot/privacy", "Private package", "Private-leak scan, wiki-presence gate, public-output gate, token loaders, operator tools")
Container(registry, "@fro-bot/registry", "Private package", "Metadata writers, reconcile, invitations, survey results, data-branch merges")
Container(testkit, "@fro-bot/test-kit", "Private package", "Test fakes and helpers, tests only")
Container(wwc, "@fro.bot/wiki-write-core", "Published npm package", "Wiki-write primitives, source here, compiled dist/ when published")
Container(dispatch, "@fro-bot/dispatch", "Private package", "cross-repo-dispatch (seven modules), dispatch-renovate")
Container(statustruth, "@fro-bot/status-truth", "Private package", "status-truth detect, proposals and prs, rollout-tracker-snapshot")
Container(wikiops, "@fro-bot/wiki-ops", "Private package", "Wiki handoff, change detection, queries, context expansion, repair, lint-issue sync, index rebuild, corrections")
Container(knowledge, "@fro-bot/knowledge", "Private package", "Learning capture, pattern synthesis, drafted solution docs")
Container(reports, "@fro-bot/reports", "Private package", "Improvement-metrics detect, report and core, daily digest counts, gateway announce")
Container(cichecks, "@fro-bot/ci-checks", "Private package", "Mutation gate (shard, merge and reach modules), md-links, override-floors and solutions-examples lints, Renovate install, config and range checks")
}
System_Ext(github, "GitHub", "REST API, Git Data API, Actions runner")
Rel(operator, workflows, "dispatches")
Rel(workflows, scripts, "runs")
Rel(scripts, core, "imports")
Rel(scripts, wwc, "imports")
Rel(scripts, privacy, "fronts")
Rel(privacy, core, "imports")
Rel(privacy, wwc, "imports")
Rel(scripts, registry, "fronts")
Rel(registry, core, "imports")
Rel(registry, wwc, "imports")
Rel(scripts, dispatch, "fronts")
Rel(dispatch, core, "imports")
Rel(dispatch, wwc, "imports")
Rel(scripts, statustruth, "fronts")
Rel(statustruth, core, "imports")
Rel(statustruth, privacy, "imports")
Rel(statustruth, wwc, "imports")
Rel(scripts, wikiops, "fronts")
Rel(wikiops, core, "imports")
Rel(wikiops, wwc, "imports")
Rel(wikiops, privacy, "imports")
Rel(scripts, knowledge, "fronts")
Rel(knowledge, core, "imports")
Rel(knowledge, privacy, "imports")
Rel(knowledge, wikiops, "imports")
Rel(knowledge, wwc, "imports")
Rel(scripts, reports, "fronts")
Rel(reports, core, "imports")
Rel(reports, privacy, "imports")
Rel(reports, wwc, "imports")
Rel(scripts, cichecks, "fronts")
Rel(cichecks, core, "imports")
Rel(cichecks, wwc, "imports")
Rel(scripts, testkit, "tests import")
Rel(scripts, github, "calls")
UpdateRelStyle(operator, workflows, $offsetX="-40", $offsetY="-10")
UpdateRelStyle(workflows, scripts, $offsetX="-30", $offsetY="-10")
UpdateRelStyle(scripts, core, $offsetX="-60", $offsetY="20")
UpdateRelStyle(scripts, wwc, $offsetX="-10", $offsetY="20")
UpdateRelStyle(scripts, privacy, $offsetX="-70", $offsetY="-30")
UpdateRelStyle(privacy, core, $offsetX="-50", $offsetY="10")
UpdateRelStyle(privacy, wwc, $offsetX="10", $offsetY="30")
UpdateRelStyle(scripts, registry, $offsetX="-30", $offsetY="-50")
UpdateRelStyle(registry, core, $offsetX="-70", $offsetY="20")
UpdateRelStyle(registry, wwc, $offsetX="30", $offsetY="40")
UpdateRelStyle(scripts, dispatch, $offsetX="40", $offsetY="-60")
UpdateRelStyle(dispatch, core, $offsetX="-90", $offsetY="30")
UpdateRelStyle(dispatch, wwc, $offsetX="50", $offsetY="50")
UpdateRelStyle(scripts, statustruth, $offsetX="-40", $offsetY="-70")
UpdateRelStyle(statustruth, core, $offsetX="60", $offsetY="20")
UpdateRelStyle(statustruth, privacy, $offsetX="-60", $offsetY="40")
UpdateRelStyle(statustruth, wwc, $offsetX="20", $offsetY="-50")
UpdateRelStyle(scripts, knowledge, $offsetX="60", $offsetY="-40")
UpdateRelStyle(knowledge, core, $offsetX="-30", $offsetY="30")
UpdateRelStyle(knowledge, privacy, $offsetX="40", $offsetY="-20")
UpdateRelStyle(knowledge, wikiops, $offsetX="-50", $offsetY="20")
UpdateRelStyle(knowledge, wwc, $offsetX="30", $offsetY="-30")
UpdateRelStyle(scripts, reports, $offsetX="-60", $offsetY="50")
UpdateRelStyle(reports, core, $offsetX="50", $offsetY="-30")
UpdateRelStyle(reports, privacy, $offsetX="-20", $offsetY="-40")
UpdateRelStyle(reports, wwc, $offsetX="-40", $offsetY="20")
UpdateRelStyle(scripts, cichecks, $offsetX="20", $offsetY="10")
UpdateRelStyle(cichecks, core, $offsetX="-20", $offsetY="-10")
UpdateRelStyle(cichecks, wwc, $offsetX="20", $offsetY="-30")
UpdateRelStyle(scripts, testkit, $offsetX="30", $offsetY="30")
UpdateRelStyle(scripts, github, $offsetX="40", $offsetY="-20")
Each package README carries a C4Component diagram of its own modules.
| File | Purpose |
|---|---|
common-settings.yaml |
Shared repository settings applied across Fro Bot projects |
.github/copilot-instructions.md |
Canonical AI-assistant guidance for contributions to this repo |
.github/settings.yml |
Probot-managed settings for this repository (branch protection, required checks) |
.github/renovate.json5 |
Automated dependency management configuration |
eslint.config.ts |
Lint + format configuration (ESLint runs Prettier via eslint-plugin-prettier) |
mise.toml |
Pinned Node, Bun, and Python versions |
tsconfig.json |
TypeScript strict-mode configuration (native TS execution) |
vitest.config.ts |
Vitest configuration for colocated scripts/*.test.ts and packages/*/src/*.test.ts files |
program-trackers.json |
The cross-repo programs the rollout tracker serves: each program's tracker issue, Project, operator and evidence kind. #3512 is the first entry; the file is owned by @marcusrbrown in CODEOWNERS |
stryker.config.json |
The mutated modules and their paired tests (mutate, testFiles); mutation-guards.json lists every package module that is not mutated, with a reason, and the three shards lists that split mutate across the PR gate's shard jobs |
[!NOTE] A schedule in these tables is the declared cron, not a start time. GitHub runs scheduled workflows on a best-effort basis: it starts them late and drops many slots of a frequent schedule. In this repository (observed 2026-11) daily jobs start several hours after their cron time, and schedules more frequent than daily run about three to four times a day whatever the cron. A schedule here is a backstop, so nothing should depend on a scheduled run starting on time.
Quality gates:
| Workflow | Purpose | Trigger |
|---|---|---|
| Main | Lint, type checking, tests, workflow validation, and Renovate preset validation | PR, push to main, dispatch |
| CodeQL | Security vulnerability analysis | PR, push to main, weekly |
| Dependency Review | Block PRs introducing known-vulnerable packages | Pull request |
| Scorecard supply-chain security | OpenSSF supply-chain security posture | Push to main, weekly |
| Copilot Setup Steps | Environment bootstrap for GitHub Copilot coding agent | PR/push touching the file |
| Check Private Leak | Triggered follow-up on private-leak sentinel findings | Workflow run |
| Private Leak Sentinel | Scan PRs for accidental private-data exposure | Pull request |
Fro Bot control plane:
| Workflow | Purpose | Trigger |
|---|---|---|
| Fro Bot | Core agent: PR review, issue triage, scheduled oversight, manual tasks | Trusted-author issues (opened/edited; OWNER/MEMBER/COLLABORATOR), PR events, schedule, dispatch, workflow_call |
| Capture Learnings | Capture and commit knowledge-wiki learnings to the data branch |
Schedule, dispatch |
| Draft Solutions | Draft docs/solutions/ entries from open learning-proposal issues into one docs/drafted-solutions PR; reviewed and merged manually |
Monday 06:00 UTC, dispatch |
| Capture Patterns | Detect recurring correction patterns across accepted learnings and solution docs, then draft human-reviewed pattern proposals | Manual dispatch |
| Poll Invitations | Accept allowlisted collaboration invitations | Scheduled every 15 minutes, dispatch |
| Reconcile Repos | Reconcile collaborator access against metadata/repos.yaml; dispatch surveys for stale repos; auto-stars collab/contrib repos |
Daily 05:17 UTC, dispatch |
| Survey Repo | Ingest a repository into the knowledge wiki; dispatched by Reconcile Repos or manually via gh workflow run survey-repo.yaml -f node_id=<node_id> |
Dispatch (by Reconcile Repos) |
| Merge Data Branch | Promote autonomous data-branch commits to main. A decide job first checks the tree diff: a promotion that touches only knowledge/ and metadata/ is gated, labeled automerge, then approved as fro-bot and squash-merged pinned to the gated commit once the required checks pass (until the repository variable PROMOTION_SELF_MERGE is on it waits and logs what it would approve; a hold-merge label stops one promotion). Anything else is held for review, and only the weekly run updates it |
Daily 22:00 UTC (Sunday is the weekly run), dispatch (refresh-review input) |
| Renovate Approve | Approve open Renovate PRs that carry automerge as fro-bot, one per sweep, pinned to the head whose required checks passed, so the native auto-merge Renovate armed can merge them. It never merges. A PR qualifies only when the App wrote it and every commit was authored by the App and committed and signed by GitHub, from a renovate/ branch in this repository, with no hold-merge label and no change to default.json or .github/renovate.json5, and it is up to date with main (until the repository variable RENOVATE_APPROVE is on it only reports what it would approve in the job summary). While an open pull request is not a draft, comes from a branch in this repository, is against main and is by a user account, nothing is approved, security updates included, and the ready ones are reported yielded: merge, close or draft that pull request to release the sweep |
Main, CodeQL, Dependency Review, Renovate and Check Private Leak completions, scheduled every 30 minutes (:11 and :41), dispatch |
| Protection Drift | Open, update and close one protection-drift issue when the main protection rule is edited outside the settings automation or no longer matches the declared settings; never blocks or reverts |
branch_protection_rule events |
| Update Metadata | Refresh metadata/renovate.yaml from the fro-bot org scan |
Daily 04:30 UTC, dispatch |
| Dispatch Renovate | Dispatch Renovate runs across repos tracked in metadata/renovate.yaml |
Scheduled every 4 hours at :30, dispatch |
| Gateway Rollout Tracker | Keep each cross-repo program's tracker current, starting with the #3512 gateway rollout. On the schedule and when program-trackers.json changes on main, a fan-out job dispatches one run per program. Each run's preflight hashes tracked issue state (and, for the gateway program, agent and dashboard releases, infra deploy runs and the health contract), leaves out items from repos that are not public, and wakes Fro Bot on a change, on an operator @fro-bot comment above the marker's last_mention_id, or on an operator edit of an older comment, or when a confirmed repo of the program has no Project item yet (a newly started program); Fro Bot verifies claims, rewrites the issue body, updates the Project, maintains the acceptance list and replies, or edits the latest marker comment in place when nothing changed. A comment is pending only when the program's operator wrote it on the tracker issue. A mention left unanswered by a failed hop or preflight is answered by the next scheduled run or a manual dispatch |
Daily 06:00 UTC, push to main that changes program-trackers.json, dispatch (program, and comment_id with comment_event from Rollout Tracker Mention) |
| Rollout Tracker Mention | Dispatch a program's tracker when its operator (the user ID in program-trackers.json) comments @fro-bot on its tracker issue or edits any of their comments there, because issue_comment runs are credential-withheld; the Fro Bot reply job skips the operator's comments and issue edits on those issues |
Issue comment created or edited |
| Status Truth | Detect drift in typed public coordination claims and manage proposal issues with counts-only summaries | Sunday 21:00 UTC, dispatch |
| Improvement Metrics | Measure whether recurring fixes actually decline: discovery, confirmed recidivism, and a pending-confirmation backlog on one perpetual report issue | Manual dispatch |
| Rename Tracked Repo | Apply a repository rename that Reconcile Repos detected: renames the metadata/repos.yaml row and moves the repo's wiki page in one commit on data (gh workflow run rename-tracked-repo.yaml -f node_id=<node_id>) |
Manual dispatch |
| Reset Survey Status | Manually clear stale survey state for one or more tracked repos on data |
Manual dispatch |
| Wiki Lint | Lint the authoritative wiki snapshot restored from origin/data |
Sunday 20:00 UTC, dispatch |
| Cross-Repo Dispatch | Decompose, dispatch, and track cross-repo goal items via worker receipts | Issue labeled/reopened, scheduled every 6 hours, dispatch |
| Publish Wiki | Build the Quartz site from the wiki and deploy it to GitHub Pages | Push to main touching knowledge/wiki/**, knowledge/index.md, knowledge/schema.md, or quartz-site/**; dispatch |
| Unpublish Wiki (emergency takedown) | Replace the live wiki site with a static "unavailable" page | Manual dispatch |
| Release Wiki Write Core | Publish @fro.bot/wiki-write-core to npm with provenance when a merge changes packages/wiki-write-core/: a credential-free job decides the release and a job in the release environment publishes the package the build job compiled and verified |
Push to main; dispatch |
Repository management:
| Workflow | Purpose | Trigger |
|---|---|---|
| Apply Branding | Apply brand template (banner + README) to a Fro Bot repo | Manual dispatch |
| Update Repo Settings | Sync .github/settings.yml via Probot |
Push to main, daily 04:05 UTC, dispatch |
| Manage Cache | Clean up workflow caches | PR close, Sunday 00:00 UTC, dispatch |
| Manage Issues | Issue-hygiene automation | Daily 01:30 UTC, reusable, dispatch |
| Renovate | Automated dependency updates | PR/issue events, Main completion, dispatch |
[!NOTE] The Fro Bot PR-review workflow triggers on
ready_for_reviewandreview_requestedto reduce duplicate runs. For ad hoc reviews outside those events, mention@fro-botin the PR conversation.
Each agent step reads its model from the agent-model action. Each job resolves the model once, before its first agent step, from one of three repository variables chosen by the kind of work. When that variable is empty, the action uses FRO_BOT_MODEL.
| Variable | Kind of work | Read by |
|---|---|---|
FRO_BOT_MODEL_REVIEW |
Review | The Fro Bot content job on a pull request: a pull_request run, a pull_request_review_comment and an issue_comment on a pull request |
FRO_BOT_MODEL_ACT |
Act | The daily Remediate and Observe passes, every Survey Repo attempt, and a dispatched run with a custom prompt, including the rollout tracker's and the branding apply's, which run in Remediate or Observe |
FRO_BOT_MODEL_LIGHT |
Light | The Fro Bot content job on an issue, an issue_comment on an issue and a discussion_comment; Capture Learnings; Capture Patterns; Draft Solutions |
FRO_BOT_MODEL |
Fallback | Every kind of work whose own variable is empty |
A run with no model fails before the agent starts, and the error names the variable it read and FRO_BOT_MODEL. A value is trimmed, and a value of only whitespace counts as empty. A value that still has a line break inside it after trimming fails the run.
The resolver prints one line, Agent model: <model> (from <variable>), which is the record of which model a run used. A re-run keeps its event and uses the same variable, and a full re-run reads the variable's current value. Draft Solutions resolves the model in its harvest job and passes it to draft; a re-run of draft alone reuses it.
Changing a model is a variable edit under the repository's Actions variables. Values are model IDs in provider/model form and are not kept in this repository. scripts/agent-model-callers.test.ts lists every agent step with its kind of work and fails on a step it does not list.
Fro Bot uses Probot Settings to synchronize repository configurations across managed repositories. The settings enforce consistent:
- Branch protection rules
- Required status checks
- Security policies
- Collaboration settings
Runtime state lives in version-controlled YAML under metadata/ (allowlist, tracked repos, Renovate targets, social cooldowns). See metadata/README.md for schemas and update conventions. Autonomous writes target the data branch and promote to main via the Merge Data Branch workflow, which runs daily. A repository ruleset blocks direct update and creation writes to data from any identity other than the Fro Bot App (fro-bot[bot]) — though a repo admin can still edit or remove the ruleset itself. Update Metadata, invitation handling, reconcile, social cooldown writes, and wiki ingest all follow that model.
Correction lifecycle operations use scripts/correction-lifecycle.ts. Agents can record, retire, reconfirm, or supersede entries in knowledge/corrections.yaml; pass help or --help for machine-readable command and failure-code documentation. The command emits JSON and derives attribution from the authenticated GITHUB_ACTOR. It prepares the guarded sidecar locally but never pushes or commits to data itself. Ingest correction findings are JSONL with target plus recovery.lifecycle and recovery.action fields, so agents can act without scraping prose.
Fro Bot maintains a Karpathy-style LLM wiki (schema.md, index.md, log.md, plus wiki/{repos,topics,entities,comparisons}/) that compounds cross-repo knowledge. The Survey Repo workflow ingests repositories into the wiki; Reconcile Repos schedules those surveys; Wiki Lint validates the authoritative snapshot restored from origin/data before reporting findings.
The daily Remediate and Observe passes start with a wiki index: one line per wiki page (path, title, type and the page's opening sentence when it has one), at most 12 KiB, built from the checkout by scripts/wiki-index.ts. When it cannot be built the prompt says so. The pull request, issue and comment run gets no wiki context. The agent prompts name when to use the knowledge MCP server, which has two tools: a run that reviews changed code or starts a fix searches the documented solutions first (knowledge_solutions_search), the daily Remediate and Observe passes search the wiki (knowledge_wiki_search) before they conclude anything about another tracked repository, and each survey attempt searches both before it writes a page. Each call returns a bounded, read-only set of excerpts — capped at 3 pages and 8 KiB per call for the wiki, filtered through the public-context safety gate. Returned text is untrusted reference material and never outranks the prompt's own rules. The server runs the same code as scripts/wiki-context-expand.ts, which stays as an operator CLI.
When a completed scan reports a purely mechanical finding, Wiki Lint's wiki-repair job self-heals it in the same run: index-drift and orphan-page regenerate the wiki index, and missing-frontmatter/invalid-frontmatter get a per-page frontmatter repair limited to two derivable fields — type (from the page's directory) and title (copied verbatim from an existing H1). Every other finding, including any other frontmatter field, requires judgment and stays on the issue-only lifecycle (open/update/reopen/close via wiki-lint-issue-sync). Repairs commit to the data branch through the existing atomic-commit envelope and ride the normal data → main promotion path (the daily run approves and merges the promotion when its tree diff is knowledge/metadata-only); they never touch main directly. Repair commit messages are a fixed, counts-free template — never page slugs, titles, or repo names. Issue closure for a repaired finding still runs through the next Wiki Lint scan's close-on-clear behavior, not the repair job itself.
The wiki above is also published as a browsable site at fro-bot.github.io/.github, built with Quartz (config in quartz-site/) and deployed via the Publish Wiki workflow. It rebuilds whenever wiki content lands on main — which happens through the daily data → main promotion — or on manual dispatch.
Be clear-eyed about what this is: the wiki only ever contains knowledge from public repos (private repos are blocked upstream, at survey, ingest, and the data → main promotion gate), so nothing here is newly exposed. But publishing it to a public, search-indexed site is still a deliberate choice — it aggregates and indexes content that was previously scattered across individual repo pages.
Spot an error on a page? Every page has an "Edit this page on GitHub" link pointing at the data branch — the wiki's authoritative source. There's no web-based editing; fixes land as commits to data and promote to main on the usual schedule.
If the site ever needs to come down fast, see docs/wiki-site-runbook.md for the emergency-unpublish procedure.
To manually re-survey a repo, pass its GitHub GraphQL node_id as the dispatch input:
# Look up the node_id
gh api graphql -f query='query{repository(owner:"<owner>",name:"<repo>"){id}}' --jq '.data.repository.id'
# Dispatch the survey
gh workflow run survey-repo.yaml -f node_id=<node_id>The node_id for each tracked repo is stored in metadata/repos.yaml on the data branch.
The optional rewrite input (-f rewrite=true, default false) makes the survey rewrite the repo page from the repository's current state and discard its existing body instead of updating it; use it when a page describes the wrong repo after a rename collision. Other pages stay additive, and a conflict re-run keeps the setting.
A persist step that another writer beats to data (a page the survey edited changed, or a concurrent survey changed knowledge/log.md) exits 3 and re-runs the survey from the current data, up to 3 runs in total. Any other persist failure takes the normal cadence.
Machine-verifiable claims live in docs/status.md.
The Status Truth workflow detects drift between documented status claims and live GitHub state, then opens fingerprinted proposal issues for review. Each proposal contains structured evidence fields (kind, claimed state, live state, proposed correction) and a hidden fingerprint marker for lifecycle tracking.
Reviewing a proposal:
Apply one of the following labels to record your decision. The loop reads these labels on the next run.
| Label | Meaning | Effect on future runs |
|---|---|---|
status-truth:accepted |
Drift is real; correction is valid | Non-terminal: issue may reopen if drift recurs |
status-truth:rejected |
Claim was correct; finding was wrong | Terminal: suppresses future matching findings |
status-truth:false-positive |
Finding is a systematic false alarm | Terminal: suppresses future matching findings |
status-truth:manually-fixed |
Drift corrected by hand | Non-terminal: auto-closes when drift clears |
status-truth:superseded |
A newer finding replaces this one | Non-terminal: preserved for history; may reopen if the same drift recurs |
Terminal labels (rejected, false-positive) permanently suppress future findings with the same fingerprint. Use them when the claim kind is systematically wrong for this source, not just transiently stale.
Non-terminal labels (accepted, manually-fixed, superseded) preserve history and allow the loop to reopen the issue if the same drift returns, or auto-close it when the drift clears.
Marking a proposal false-positive:
- Open the proposal issue.
- Apply the
status-truth:false-positivelabel. - Optionally add a comment explaining why (e.g., "this PR reference is intentionally historical").
- Close the issue. The loop will not reopen it for the same fingerprint.
Marking a proposal accepted:
- Apply the
status-truth:acceptedlabel. - Make the correction (update the doc, close the PR, etc.).
- Close the issue or leave it open — the loop will auto-close it when the drift clears on the next complete scan.
Marking a proposal superseded:
Use status-truth:superseded when a newer finding or a broader correction makes this specific proposal obsolete, but you want to preserve the history.
- Apply the
status-truth:supersededlabel. - Optionally add a comment linking to the replacement issue or correction.
- Close the issue. The loop treats
supersededas non-terminal: if the same drift fingerprint returns, the issue will be reopened.
Workflow summary: Each run emits aggregate counts by claim kind (opened, updated, reopened, closed, suppressed). No file paths, fingerprints, or claim text appear in workflow logs — evidence lives in the proposal issues after privacy gating.
Plan-consistency proposals:
Every plan under docs/plans/ is checked automatically for consistency between its frontmatter status and its implementation-unit checkboxes (- [x] **Unit N: ...**). No prose claim is required — the checkboxes are the claim.
The drift rule: a plan marked active whose units are all checked gets a proposal to flip its status to complete. Everything else is either current or unresolved — unchecked units on a complete plan, unsupported status values, and unrecognizable unit markers are all counted as unresolved and never proposed.
Applying status-truth:rejected or status-truth:false-positive to a plan-consistency proposal permanently exempts that plan from future consistency proposals. Removing or renaming a plan file clears its finding on the next scan; any open proposal for it auto-closes as resolved.
Correction PRs (currently disarmed):
plan-consistency is the first graduated claim kind, but graduation alone doesn't open PRs — the repository variable and manual dispatch input below still both have to be set.
The Status Truth workflow can, once armed, open a pull request that applies a proposal's correction directly instead of waiting on a human edit. No PR opens today. Three independent, reviewed keys must all be true at once before one can:
- The repository variable
STATUS_TRUTH_PRS_ENABLEDis set totrue. - At least one claim kind is present in the graduated-kinds set in
packages/status-truth/src/status-truth-prs.ts— added only via a reviewed code change, never a config toggle. - A manual
workflow_dispatchrun explicitly sets theopen_prsinput totrue. Scheduled runs never open PRs, no matter how the other two keys are set.
Any single key missing produces zero PR actions; eligible findings fall back to proposal-only, same as today.
Graduation policy: a claim kind becomes eligible for the graduated set only after it has accumulated at least one explicit status-truth:accepted outcome on a real proposal — resolved-positive outcomes count toward this bar but can't satisfy it alone. Graduating a kind is a one-line reviewed code change adding it to the set, made after that evidence exists. A status-truth:false-positive outcome on a graduated kind removes it from the set via another reviewed change; re-graduating it later requires fresh accepted signal, not just reverting the removal.
Execution posture, once armed:
- At most one new correction PR opens per run; further eligible findings are counted as blocked and stay proposal-only. An already-open PR being rediscovered doesn't consume that slot, so a long-lived PR can't starve other findings.
- Each correction PR touches exactly one file, within the same allowed-path prefixes as proposals. Forbidden paths and privacy-gate failures downgrade to proposal-only, same as the detect step.
- Before any push, the corrected content is re-verified against the live base-branch file, not just the report snapshot — stale drift never gets force-corrected.
- If a fingerprint's drift clears on a complete scan while its correction PR is still open, the bot closes its own PR with a brief comment and deletes the branch. If the linked proposal later gets a terminal label (
status-truth:rejectedorstatus-truth:false-positive), the same closure happens regardless of drift state. Merged PRs are never touched. - The bot never merges, approves, enables automerge, force-pushes, or retargets a correction PR (the PRs it does approve are the
data → mainpromotion whose diff touches onlyknowledge/andmetadata/, which the Merge Data Branch run also merges, and the Renovate PRs that carryautomerge, which the Renovate Approve sweep approves and GitHub's auto-merge merges) — closing its own stale PRs and deleting its own branches are the only PR-state mutations it can make. A human always merges.
The Draft Solutions workflow turns open learning-proposal issues (up to five per run, oldest first) into docs/solutions/ changes on one stable branch, docs/drafted-solutions, with one PR. A read-only agent verifies each claim against the merged PR, its reviews and CI runs, and current main, then drafts or extends docs; a separate no-agent job validates the handoff (only docs/solutions/<category>/*.md, no deletions), runs the private-repo gate on every file, the PR title and body, and every comment, and only then writes. The agent edits a commit pinned by the harvest job, and the publish job refuses to overwrite a file that changed on the live branch or main while it drafted (the run fails and the next one redrafts). The branch is never force-pushed or reused: with no open PR it must not exist, so a leftover from a PR closed without merging fails the run closed until you delete it (merged PRs delete theirs), and a PR that appears mid-run fails the run closed too. The PR body carries a per-proposal evidence table and one Closes #N line per recorded proposal; it is bounded to GitHub's 65,536-character limit, and a new decision for a proposal replaces its earlier row.
Proposals whose claims cannot be verified are commented on and closed as not planned right away, never recorded in the PR. Proposals an existing doc already covers are recorded in the PR when one is open or the run changes docs, and are otherwise commented on and closed as completed. The operator reviews and merges the drafted PR manually; Fro Bot does not review it, because the agent cannot review a PR authored by the first-party App (fro-bot/agent#1520). Nothing is auto-merged, and merging closes the proposals.
The Capture Patterns workflow looks for repeated correction behavior across the accepted learning corpus — docs/solutions/ entries and learning-proposal issues in this repo — and drafts human-reviewed proposals for patterns worth codifying as durable lessons. It never authors or edits docs/solutions/ directly; every pattern proposal is a decision log entry a human reviews and, if accepted, turns into a doc themselves.
The loop is proposal-only and manual-first: workflow_dispatch defaults to dry-run, and there is no scheduled trigger in this initial slice. A dry run plans and drafts candidates but never opens issues or mints an issue-writing token; only an explicit live dispatch (dry_run: false) does both.
Reviewing a proposal:
Apply exactly one outcome label to record your decision:
| Label | Meaning |
|---|---|
pattern-proposal:accepted |
Pattern is real and durable; source material is retired from future clustering immediately |
pattern-proposal:deferred |
Not yet — needs more independent evidence before reconsideration |
pattern-proposal:rejected |
Pattern is not real, or too weak to codify |
pattern-proposal:superseded |
A newer proposal replaces this one; permanently suppressed |
A closed proposal without one of these labels is needs-outcome — a derived state, not an operator label, that conservatively suppresses re-proposal of the same sources until new independent evidence appears.
Caps and evidence: at most three new proposals open per run, ranked by independent source count, accepted-doc presence, and recency. Deterministic scripts own source loading, deduplication, suppression, privacy gating, and issue mutations; the agent only judges evidence and drafts bounded proposal bodies from a curated, privacy-gated digest — it never edits repo files, reads raw solution text, or receives private tokens.
None of the loops above answer the one question that actually matters: does the same class of fix keep coming back? The Improvement Metrics workflow measures that directly, as one paired reading rewritten to a single perpetual report issue each run.
What it measures:
- Discovery — distinct classes of fix newly codified into
docs/solutions/in the current window. A class only counts once, on first codification; repeated proposals for something already documented never re-count. - Confirmed recidivism — codified classes that recurred, where a human confirmed the recurrence. Fro Bot never confirms its own findings.
- Backlog — candidate recurrences it has detected but you haven't confirmed yet, with the oldest one's age. A growing, aging backlog is the loop calling out its own neglect, not a clean read.
Confirming a recurrence:
Each candidate (event → class) link is its own checklist line on the report issue. Tick the box to confirm it recurred. The tick persists across every future rewrite — the report is upserted in place by a hidden version marker, and any edge still present in a later run keeps whatever tick state you gave it. Nothing you confirm gets silently reset by the next run.
Running it:
workflow_dispatch only, no schedule. dry_run defaults to true — a dry run computes and logs counts but touches nothing. Set dry_run to false to actually create or update the report issue.
Report states, one per run, never a blend:
| State | Meaning |
|---|---|
insufficient-signal |
Too little codified history or discovery this window to say anything — no trend claimed |
healthy |
Discovery holding or rising, confirmed recidivism low relative to discovery |
ambiguous |
Discovery fell from the prior window, or the backlog has gone stale — not a claim of improvement or regression |
failing |
Confirmed recidivism has caught up to or passed discovery — the same classes are recurring as fast as new ones get codified |
Fro Bot coordinates a single goal across multiple owner repos (fro-bot/*, marcusrbrown/*) through an open → decompose → approve → dispatch → track lifecycle:
- Open a goal issue. Describe a multi-repo goal and mention
@fro-bot. Label the issuecross-repo-goal. - Review the proposed decomposition. The bot posts a per-repo work-item checklist as a comment. Edit it freely before approving — nothing dispatches yet.
- Approve. Applying the
dispatch-approvedlabel triggers dispatch. Only an approval applied by the repository owner (the operator) is honored; any other applier has the label removed and nothing runs. - Dispatch. Each approved item runs as a worker agent in its target repo. Targets are restricted to owner repos already onboarded to Fro Bot's automation.
- Track to completion. A periodic tracker snapshots each item's run outcome to the issue. The issue closes automatically once every item reaches a terminal state (completed, failed, or blocked).
If you need to re-approve after reopening a closed goal issue, reopening automatically clears the prior approval — reapply the dispatch-approved label to fire a fresh dispatch. Re-approval dispatches only pending items; needs-attention items are not re-dispatched. To re-run a goal, close it and open a fresh goal issue, which gets a new decomposition and marker. A late receipt still moves a needs-attention item to its terminal status.
Each dispatch carries only the universal prompt input — no correlation_id or other custom input, since target repos are autonomous and only guarantee prompt. The correlation id and a per-item nonce ride inside the prompt itself; the worker reports completion by posting a receipt comment on the coordination issue, which a periodic tracker verifies (author, correlation id, and hash(nonce)) before resolving the item.
The nonce binds a receipt to its item: the coordination issue stores only hash(nonce), and the raw nonce reaches the worker only through the prompt. One caveat worth stating plainly — a target repo running Fro Bot with OPENCODE_PROMPT_ARTIFACT enabled publishes the rendered prompt (raw nonce included) as a downloadable Actions artifact. Where that's set on a target, item-level nonce isolation falls back to trusting that target's worker and its FRO_BOT_PAT.
Why push, not poll. Tracking used to correlate a dispatched item to its worker run by matching a correlation_id against the run name — until target repos turned out to only guarantee a prompt input, and GitHub's dispatch API gives you a 204 with no run id back anyway. So the model flipped: the worker already knows how it went, and it already holds a credential that can write back to the coordination issue. The worker posts, a scheduled tracker reads. Tracking never polls PR state or the Actions API for completion; a pr URL in a receipt is operator-facing metadata, nothing more.
The receipt. When a worker finishes an item — success, no-op, or failure — it posts a comment on the coordination issue containing a delimited region:
<!-- fro-bot:cross-repo-result:start -->
<!-- fro-bot:cross-repo-result {"correlation_id":"...","nonce":"...","status":"success","summary":"...","pr":"..."} -->
<!-- fro-bot:cross-repo-result:end -->status is exactly one of success | noop | failed — a no-op is a valid, mandatory receipt, not a missing one. The parser prefers the region but tolerates surrounding prose and a trailing run-summary block, same as the goal-decomposition checklist parser; a bot-authored comment that has a marker but botches the fields is unparseable-receipt, not silently accepted and not treated the same as no receipt at all.
Three gates before a receipt moves anything. All of these have to hold: the comment is authored by fro-bot, its correlation_id matches a dispatched item, and hash(nonce) matches that item's stored nonceHash. Author alone isn't enough — every worker shares the same bot identity and correlation ids sit in plain sight on the issue — so a per-item nonce is the real lock. Its hash is the only thing that ever touches the public marker; the raw value only ever appears in that item's dispatch prompt. Reading the marker gets an attacker nothing to forge with. Whichever authentic receipt arrives first, by comment order, is the one that resolves the item, and a resolved item never flips again — which matters because the raw nonce becomes public the moment the real worker posts it, and a later replay of that now-public nonce still can't undo a completed or failed item.
24h SLA and the "did it even run" question. An item with no authentic receipt within 24 hours of confirmed dispatch surfaces as needs-attention rather than sitting silently dispatched forever. It's reversible — a late, genuine receipt still resolves it — so a slow worker isn't punished for being slow. Because a missing receipt is the most likely failure mode (agents drift off prompt formats more often than you'd like), a diagnostic run-lookup kicks in at that point, purely for operator context: it tags the no-receipt item dispatch-accepted-no-receipt by default, or run-observed-no-receipt if the Actions API shows a completed correlated run. That lookup never resolves state on its own, and it never claims never-ran — a missing run-name correlation is not proof the worker didn't run, just that nothing was observed.
Receipt accountability is metadata-declared, not prompt-declared. Whether a missing receipt is a broken contract is an operator-managed capability in metadata/repos.yaml (cross_repo_receipts, see metadata/README.md), snapshotted onto each item at dispatch time — never inferred from the dispatch prompt and never self-reported by the target. Targets without the capability are legacy-best-effort: still dispatchable, and accepted nonce-verified receipts from them still resolve items, but a missing receipt is diagnostic-only, not a broken contract. This distinction exists because #3652, A3's first real cross-repo goal, proved some targets have local response policies (e.g. "post exactly one comment," or treating cross-repo comment instructions as suspicious) that a prompt cannot override — three targets posted accepted receipts, one completed local triage and refused the receipt as prompt-injection-shaped, one completed with no visible receipt. A local-only completion comment or a green Actions run is useful operator signal, but it is never a completion oracle; only an accepted, nonce-verified receipt resolves an item.
When an item gets stuck wrong. First-authentic-receipt-wins is a deliberate anti-spoof choice, but it means a bad early receipt (or your own mistake) can lock an item somewhere you don't want it. There's no undo command for that yet — the fix is editing the coordination issue's state marker comment directly to correct the item's status.
What's still open. Every worker authenticates with the FRO_BOT_PAT available to that target repo, so this is a shared worker-trust design, not per-item authorization. A compromised target worker or PAT can still forge a receipt for any item whose nonce it can obtain. GitHub does not provide an issue-scoped receipt token, and pushing a separate token through the dispatch prompt would only move the leak. Further hardening requires a real target-side policy change or a receipt broker, not control-plane theater.
This repository enforces strict quality standards:
- TypeScript: Strict mode enabled with comprehensive type checking
- ESLint: Custom configuration based on
@bfra.me/eslint-config - Prettier: Consistent code formatting with 120-character line length
- Security: Regular security scanning and vulnerability assessments
.github/copilot-instructions.md is the canonical guidance for AI coding agents (GitHub Copilot, OpenCode, and others) contributing to this repo. It covers:
- Canonical-context reading order and repository contract
- Required verification commands and quality gates
- High-risk do/don't patterns (package manager, workflow setup, type safety, scope control)
- Security and safety constraints
- Platform-specific notes (GitHub Copilot coding agent, Copilot hooks)
Repo-scoped agent skills live in .agents/skills/ for techniques specific to this repository's conventions. The fro-bot, Remediate and Observe agent steps in fro-bot.yaml (the rollout tracker runs through the first) set repo-skills: 'true', so the agent loads these skills from the default branch, freshly fetched and never from the checkout. Triggers: schedule and workflow_dispatch always; issues, issue_comment and pull_request only from an owner, member or collaborator on a same-repo head.
[!NOTE] These guidelines ensure consistent AI assistant behavior and maintain code quality across the project.
Fro Bot Ecosystem:
- Fro Bot Organization - Main organization page
- Security Policy - Vulnerability reporting and security guidelines
Development Tools:
- TypeScript - Type-safe JavaScript development
- ESLint - JavaScript/TypeScript linting
- Prettier - Code formatting
- Renovate - Automated dependency management
GitHub Resources:
- GitHub Actions - CI/CD workflows
- Probot - GitHub app framework
- OpenSSF Scorecard - Security assessment