Repository navigation
Fro Bot #5732
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| name: Fro Bot | |
| # Optional correlation-id suffix lets a cross-repo-dispatch caller identify | |
| # this exact run by a unique token instead of coincidental epoch/actor | |
| # matching (see scripts/cross-repo-dispatch.ts). Empty by default — a purely | |
| # cosmetic, backward-compatible run-name change. | |
| run-name: >- | |
| Fro Bot${{ github.event.inputs.correlation_id && format(' ({0})', github.event.inputs.correlation_id) || '' }} | |
| on: | |
| issue_comment: | |
| types: [created] | |
| pull_request_review_comment: | |
| types: [created] | |
| discussion_comment: | |
| types: [created] | |
| issues: | |
| types: [opened, edited] | |
| pull_request: | |
| types: [synchronize, ready_for_review, reopened, review_requested] | |
| schedule: | |
| - cron: '0 0 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| prompt: | |
| description: 'Custom prompt for the Fro Bot agent (leave empty to run the default daily oversight + autohealing pass)' | |
| required: false | |
| default: '' | |
| correlation_id: | |
| description: Optional opaque correlation id echoed into run-name (used by cross-repo-dispatch to identify this run) | |
| required: false | |
| default: '' | |
| workflow_call: | |
| inputs: | |
| prompt: | |
| description: Prompt passed by reusable caller workflow | |
| required: true | |
| type: string | |
| secrets: | |
| FRO_BOT_PAT: | |
| description: GitHub PAT for Fro Bot write-tier actions | |
| required: true | |
| OPENCODE_AUTH_JSON: | |
| description: OpenCode authentication configuration | |
| required: true | |
| OMO_PROVIDERS: | |
| description: OhMyOpenCode provider configuration | |
| required: true | |
| OPENCODE_CONFIG: | |
| description: OpenCode runtime configuration | |
| required: true | |
| GATEWAY_WEBHOOK_SECRET: | |
| description: HMAC-SHA256 signing key for the gateway presence announce (optional) | |
| required: false | |
| permissions: | |
| contents: read | |
| # FRO_BOT_PAT carries cross-repo write-tier authority for the agent. It must | |
| # include repo access, read:org for organization queries, and project scope for | |
| # tracker Project updates when called by Gateway Rollout Tracker. | |
| concurrency: | |
| group: >- | |
| fro-bot-${{ | |
| github.event.issue.number || | |
| github.event.pull_request.number || | |
| github.event.discussion.number || | |
| ((github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && 'daily') | |
| || github.run_id | |
| }} | |
| cancel-in-progress: false | |
| env: | |
| PR_REVIEW_PROMPT: | | |
| You are a skeptical reviewer for fro-bot/.github (org control center). | |
| Focus on correctness, security, and automation safety. Prefer false positives | |
| over false negatives for security issues. | |
| Scope: | |
| - Workflow/CI changes: pin actions to full commit SHA where possible; avoid | |
| pull_request_target unless explicitly justified; prevent workflow injection | |
| (untrusted input in run: blocks or shell eval); minimal permissions; no | |
| secrets in logs. | |
| - Repo rules: no `any`; no `@ts-ignore`/`@ts-expect-error`; avoid ES6 classes; | |
| explicit function return types; use `consola` instead of `console`. | |
| - Dependencies: identify direct additions/updates; flag known advisories; | |
| avoid unnecessary or duplicate deps; ensure license compatibility. | |
| - Tests: verify new/changed behavior has tests covering happy path, errors, | |
| and boundaries. If tests aren't needed, explain why. | |
| Hard boundaries: | |
| - Review only. Do NOT push commits, modify files, create branches, or open PRs. | |
| - Do NOT use labels, assignments, or milestone changes. | |
| - Use inline comments for file-specific findings. Use review body for summary only. | |
| - Do NOT speculate. If uncertain, state what evidence is missing. | |
| Do NOT comment on formatting, lint, or style nits handled by CI. | |
| Output: | |
| ## Verdict: PASS / CONDITIONAL / REJECT | |
| ### Blocking issues | |
| ### Non-blocking concerns | |
| ### Missing tests | |
| ### Risk assessment (LOW/MED/HIGH) + rationale | |
| Requirements: | |
| - Include every heading exactly once and in the order above. | |
| - Write "None" under headings with no findings. | |
| - Keep findings actionable: file path, impact, and concrete remediation. | |
| GOAL_DECOMPOSITION_PROMPT: | | |
| This issue carries the `cross-repo-goal` label: it describes a goal spanning | |
| multiple owner repos (`fro-bot/*`, `marcusrbrown/*`). Propose a per-repo | |
| work-item decomposition as a single comment on this issue. Do NOT dispatch, | |
| trigger, or execute anything — this run has no dispatch capability and this | |
| output is a proposal only, subject to the operator's edit and approval. | |
| Read the goal description and the managed-repo registry | |
| (`metadata/repos.yaml`) to identify eligible owner-repo targets. | |
| Post exactly one checklist item per proposed work item, in this literal | |
| format (each line independently parseable): | |
| - [ ] <owner>/<name>: <prompt> | |
| Wrap the checklist in a delimited region so the parser can find it | |
| precisely even when you also write prose around it: | |
| <!-- fro-bot:cross-repo-items:start --> | |
| - [ ] <owner>/<name>: <prompt> | |
| <!-- fro-bot:cross-repo-items:end --> | |
| Rules: | |
| - <owner>/<name> must be an owner-repo target (`fro-bot/*` or | |
| `marcusrbrown/*`) present in `metadata/repos.yaml`. | |
| - <prompt> is the exact task to hand to that repo's worker run — concise, | |
| concrete, self-contained. | |
| - Free text outside the delimited region is fine — an intro, an | |
| explanation of why each target was chosen, a footer — as long as it | |
| doesn't itself look like a checklist item. | |
| - If the goal cannot be decomposed into owner-repo targets, say so plainly | |
| instead of inventing items. | |
| Downstream, once an item is approved and dispatched, the target repo's | |
| worker run receives a prompt carrying this item's work plus a receipt | |
| contract. That contract is generated automatically — you do not need to | |
| restate it in <prompt> — but it is documented here so the shape of the | |
| full loop is clear: | |
| A dispatched worker must, after doing the item's work, post a completion | |
| receipt as a comment on THIS coordination issue, wrapped in its own | |
| delimited region: | |
| <!-- fro-bot:cross-repo-result:start --> | |
| <!-- fro-bot:cross-repo-result {"correlation_id":"...","nonce":"...","status":"success","summary":"...","pr":"https://github.com/owner/repo/pull/123"} --> | |
| <!-- fro-bot:cross-repo-result:end --> | |
| Receipt rules the worker follows: | |
| - `status` is exactly one of `success`, `noop`, or `failed` — nothing else. | |
| - A receipt is MANDATORY even when the outcome is a no-op; a missing | |
| receipt reads as a failure to report, not as success. | |
| - Before finishing, the worker echoes the coordination issue it resolved | |
| (owner/repo/number) and re-reads its own posted comment to confirm the | |
| marker round-trips; if the marker is missing, it edits the comment to | |
| add it before declaring the item done. | |
| - `summary` must be plain text: no markdown formatting, backticks, or | |
| backslash escapes, and the marker as a whole must be valid JSON — an | |
| escaped character like `\#` breaks the parser and turns a successful | |
| run into an unparseable receipt. | |
| SCHEDULE_PROMPT: | | |
| Run the daily Fro Bot pass for fro-bot/.github — the autonomous control | |
| center for the fro-bot GitHub account. This single run does BOTH proactive | |
| oversight (detect and report) AND reactive autohealing (fix what is safe). | |
| Prioritize minimal, reversible changes. Read the canonical context first: | |
| README.md, .github/copilot-instructions.md, and AGENTS.md if present. | |
| EXECUTION MODEL | |
| Analyze every category, but perform write actions serially. Never keep more | |
| than one branch checked out at a time — complete one mutation, return to a | |
| clean working tree, then continue. The control plane's data branch is the | |
| authoritative writer for knowledge/** and metadata/**; never write those | |
| paths from this run except through the established wiki/metadata scripts. | |
| For version comparisons, current-version truth comes from the authoritative | |
| registry or upstream release source; name that source and state whether major | |
| drift was included. | |
| DEDUPLICATION (applies to ALL categories) | |
| Before creating any new PR or issue, search for an existing open | |
| Fro-Bot-authored PR/issue for the same root cause and reuse or update it | |
| instead of creating a duplicate. | |
| SCOPE CAP | |
| Only auto-heal a fix that is clearly minimal and reversible. If the smallest | |
| safe fix is a broad refactor, an architecture change, or a many-file edit, | |
| do NOT auto-heal it — record it under "Needs Human Attention" with enough | |
| detail for an LLM agent to act on later (see AGENT NOTES). | |
| DEPENDENCY OWNERSHIP | |
| Renovate owns routine dependency and version bumps. Change dependency | |
| versions only to remediate a confirmed critical/high security advisory or to | |
| repair an existing security-update PR. Never open a non-security version-bump | |
| PR and never batch unrelated dependency changes into a security fix. | |
| TRUSTED AUTHORS | |
| Only repair a PR branch authored by the repository owner/a collaborator with | |
| write access, or an approved automation bot (renovate[bot], dependabot[bot], | |
| fro-bot). If author trust cannot be established, skip the PR and log it under | |
| "Needs Human Attention". | |
| CATEGORIES (work in order) | |
| 1. ERRORED PRs | |
| Find open PRs with failing CI. Skip dependency/security PRs here (handle | |
| in category 2). Only fix PRs whose head branch is in this repo, writable | |
| by this token, and authored by a trusted source. For each fixable PR: | |
| inspect both sources: check runs and legacy commit statuses. `gh pr checks` | |
| and `/commits/{sha}/check-runs` enumerate check runs only; legacy statuses | |
| are separate `/commits/{sha}/status` records, so inspect both and report | |
| failures from either source under "Errored PRs". | |
| check out the branch, read the failing check logs, diagnose the root | |
| cause, apply a focused fix, push to that PR branch, and post a concise PR | |
| comment (root cause, fix applied, remaining risk). If the PR touches | |
| .github/workflows/, automation prompts, lockfiles, or execution scripts, | |
| do NOT run project commands from that branch — skip and log why. | |
| 2. SECURITY | |
| Review advisories and alerts for direct dependencies and Actions. If a | |
| security PR is failing or conflicted, fix and push to it. For unaddressed | |
| critical/high advisories with no existing PR, open one dedicated | |
| remediation PR. Never bundle unrelated upgrades. If advisory data is | |
| unavailable to the token, note "security alerts unavailable" under "Needs | |
| Human Attention" — do not guess. | |
| 3. CONTROL-PLANE INTEGRITY (highest-value for this repo) | |
| a. SHA pinning: every third-party action in .github/workflows/*.yaml and | |
| .github/actions/**/action.yaml must be pinned to a full commit SHA with | |
| a version comment (@<sha> # vX.Y.Z). Fix floating tags on a PR. | |
| b. Strip-only TypeScript: scripts/*.ts must avoid enum, namespace, | |
| parameter properties, and TS import aliases (Node 24 strip-only). The | |
| eslint erasable-syntax rule and the Test Scripts Load job enforce this; | |
| flag any drift. | |
| c. Least privilege: workflows should declare minimal permissions and use | |
| the shared ./.github/actions/setup. Flag over-broad permissions. | |
| d. Guard integrity: do not weaken the wiki-authority guard, the privacy | |
| gates, or branch protection. Report gaps; never relax them. | |
| 4. CODE QUALITY | |
| Run the repository's own validation: pnpm bootstrap, pnpm check-types, | |
| pnpm lint, pnpm test. Apply safe mechanical fixes (lint/format autofixes) | |
| on a PR branch — never commit directly to main. If a check fails and the | |
| fix is not minimal/reversible, open or update an issue and log it. | |
| 5. OVERSIGHT (org-wide detection, report-only) | |
| Scope: all repositories the fro-bot account can see. Enumerate them with | |
| paginated repository listing API calls for the authenticated account and its | |
| organizations; define "can see" as repositories returned with at least read | |
| access, and report anything that could not be enumerated. Summarize with | |
| links only. Detect: new issues, open PRs, stale issues (>30d) and PRs (>7d | |
| aging, >14d stale), failing main checks, security alerts, unassigned bugs, | |
| and the top three repo hotspots (ranked by qualifying finding count in the | |
| current daily snapshot; at least one linked finding required). For PRs, | |
| measure aging from creation time and staleness from the most recent update | |
| activity (commit, review, comment, or state change). Recommend a next step | |
| for each. Do NOT modify individual issues/PRs or apply labels in this | |
| category. | |
| 6. CROSS-PROJECT INTELLIGENCE (report-only) | |
| Fro Bot surveys tracked repositories into the wiki. Treat every entry in | |
| metadata/repos.yaml (not an example list) as tracked; scan every accessible | |
| entry for automation patterns, prompt strategies, or workflow improvements | |
| that could benefit this repository. Report inaccessible or otherwise | |
| unscanned entries and mark coverage partial. Report adoptable findings only; | |
| make no changes in this category. | |
| 7. PROGRESSIVE IMPROVEMENT (report-only) | |
| Scan for durable improvement opportunities: tool-version drift in | |
| package.json (ESLint, Prettier, TypeScript, Vitest more than a minor | |
| behind), missing or degraded CI jobs, convention drift from | |
| copilot-instructions.md, and stale TODO/FIXME annotations. Also scan | |
| open issues labeled `learning-proposal`: the expected action for each | |
| is authoring the learning into `docs/solutions/`, so any older than 14 | |
| days, or two or more open at once, means the compounding pipeline has | |
| stalled. Report the count and ages. Do not treat a `healthy` reading on | |
| the Improvement Metrics report (#3674) as evidence this is fine — | |
| unauthored proposals never become classes there, so that report stays | |
| green precisely while this is stalled. Report under "Progressive | |
| Improvement"; make no changes here. | |
| 8. GATEWAY OPERATOR CONTROL-SURFACE ROLLOUT TRACKER | |
| Review awareness only. Compare #3512's checklist/status claims with the | |
| corresponding GitHub Project 1 item status and the latest rollout evidence | |
| in managed repositories; report any mismatch with both values. Summarize | |
| drift in the Daily Org Oversight Report if found. Do not post tracker | |
| comments or edit Project fields from this daily report path; the dedicated | |
| Gateway Rollout Tracker workflow owns tracker writes. | |
| HARD BOUNDARIES | |
| - Never force-push, rewrite history, delete branches, merge PRs, submit | |
| approvals, or close/reopen PRs. | |
| - Never push directly to main. Direct pushes are allowed only to an existing | |
| non-default PR branch you are repairing under category 1 or 2. | |
| - Never modify secrets, org settings, environments, or branch protection. | |
| - Never make checks pass by disabling tests, deleting assertions, lowering | |
| thresholds, weakening lint/type rules, or editing config only to suppress | |
| failures. If the smallest safe fix would weaken a guardrail, skip and log. | |
| - Do not modify .github/workflows/ or automation prompt files unless a | |
| failing run is caused by a genuine bug in that file, the change is narrowly | |
| scoped, and the summary explains why. | |
| AGENT NOTES (instead of per-agent follow-up tasks) | |
| Do NOT create follow-up tasks addressed to a specific agent (Fro Bot, | |
| Copilot, etc.). When work is deferred, write the note so any LLM agent can | |
| pick it up cold: state the exact file paths, the root cause, the smallest | |
| safe fix, any constraints or "do not retry" warnings, and how to verify. | |
| Put these under "Needs Human Attention" in the report. If a finding is | |
| durable repository knowledge, also persist it to the wiki per the | |
| knowledge_persistence block, following knowledge/schema.md. | |
| OUTPUT — ONE PERPETUAL SUMMARY ISSUE | |
| Maintain exactly one daily report. Create an issue titled | |
| "Daily Fro Bot Report — YYYY-MM-DD (UTC)" for today. Before or after | |
| creating it, CLOSE every older open issue whose title starts with | |
| "Daily Fro Bot Report —" or "Daily Org Oversight Report —" (the previous | |
| naming), leaving a one-line closing comment that links the newest report. | |
| There must be exactly one open daily report when the run finishes. | |
| The issue body MUST use this structure (write "None" for empty sections, | |
| links only — no full content duplication): | |
| ## Daily Fro Bot Report — YYYY-MM-DD (UTC) | |
| ### Run Summary | |
| | Category | Status | Notes | | |
| | --- | --- | --- | | |
| | Errored PRs | ✅/⚠️/❌/❔ | ... | | |
| | Security | ✅/⚠️/❌/❔ | ... | | |
| | Control-Plane Integrity | ✅/⚠️/❌/❔ | ... | | |
| | Code Quality | ✅/⚠️/❌/❔ | ... | | |
| | Oversight | ✅/⚠️/❌/❔ | ... | | |
| | Cross-Project Intelligence | ✅/⚠️/❌/❔ | ... | | |
| | Progressive Improvement | ✅/⚠️/❌/❔ | ... | | |
| ### Errored PRs | |
| ### Security | |
| ### Control-Plane Integrity | |
| ### Code Quality | |
| ### Oversight | |
| ### Cross-Project Intelligence | |
| ### Progressive Improvement | |
| ### Needs Human Attention | |
| Status legend: ✅ = verified-clean; ⚠️/❌ = warning or error; ❔ = | |
| could-not-check. Use ✅ only when all required sources were checked and | |
| nothing was found. Use ❔ when any source is unavailable, stale, empty, | |
| access denied, or scan failed; name the affected source and reason. Never | |
| render ✅ for could-not-check. | |
| jobs: | |
| fro-bot: | |
| name: Fro Bot | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| if: >- | |
| ( | |
| github.event.pull_request == null || | |
| ( | |
| !github.event.pull_request.head.repo.fork && | |
| !endsWith(github.event.pull_request.user.login || '', '[bot]') | |
| ) | |
| ) && ( | |
| ( | |
| github.event_name == 'issues' && | |
| !endsWith(github.event.issue.user.login || '', '[bot]') && | |
| (github.event.issue.user.login || '') != 'fro-bot' | |
| ) || | |
| ( | |
| github.event_name == 'pull_request' && | |
| !endsWith(github.event.pull_request.user.login || '', '[bot]') && | |
| (github.event.pull_request.user.login || '') != 'fro-bot' | |
| ) || | |
| github.event_name == 'schedule' || | |
| github.event_name == 'workflow_dispatch' || | |
| inputs.prompt != '' || | |
| ( | |
| (github.event_name == 'issue_comment' || | |
| github.event_name == 'pull_request_review_comment' || | |
| github.event_name == 'discussion_comment') && | |
| contains(github.event.comment.body || '', '@fro-bot') && | |
| (github.event.comment.user.login || '') != 'fro-bot' && | |
| contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association || '') | |
| ) | |
| ) | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.FRO_BOT_PAT }} | |
| - name: 📦 Setup | |
| uses: ./.github/actions/setup | |
| - name: Sync wiki from data branch | |
| run: | | |
| if git ls-remote --exit-code origin data >/dev/null 2>&1; then | |
| git fetch origin data | |
| git restore --source FETCH_HEAD --worktree -- knowledge | |
| fi | |
| - name: Capture wiki baseline | |
| id: wiki-baseline | |
| run: | | |
| git diff --no-ext-diff -- knowledge/index.md knowledge/log.md knowledge/wiki > "$RUNNER_TEMP/wiki-baseline.diff" | |
| echo "hash=$(shasum -a 256 "$RUNNER_TEMP/wiki-baseline.diff" | cut -d ' ' -f1)" >> "$GITHUB_OUTPUT" | |
| - name: 🎭 Load Persona | |
| id: persona | |
| run: | | |
| delimiter="EOF_$(openssl rand -hex 8)" | |
| { | |
| echo "content<<$delimiter" | |
| cat persona/fro-bot-persona.md | |
| echo "$delimiter" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Query wiki context | |
| id: wiki-query | |
| env: | |
| WIKI_QUERY_EVENT_NAME: ${{ github.event_name }} | |
| WIKI_QUERY_OWNER: ${{ github.repository_owner }} | |
| WIKI_QUERY_REPO: ${{ github.event.repository.name }} | |
| WIKI_QUERY_TITLE: >- | |
| ${{ | |
| github.event.pull_request.title || | |
| github.event.issue.title || | |
| github.event.discussion.title || | |
| '' | |
| }} | |
| WIKI_QUERY_BODY: >- | |
| ${{ | |
| github.event.pull_request.body || | |
| github.event.issue.body || | |
| github.event.comment.body || | |
| '' | |
| }} | |
| # Run-local handoff for optional wiki context expansion. | |
| # Contains only selected wiki paths — never logged, never echoed. | |
| WIKI_CONTEXT_HANDOFF_PATH: ${{ runner.temp }}/wiki-context-handoff-${{ github.run_id }}-${{ github.run_attempt }}.json | |
| run: node scripts/wiki-query.ts | |
| # Overlay metadata/ from the data branch so the scheduled run can derive | |
| # digest counts from live metadata/repos.yaml. Mirrors the precedented guard | |
| # in reconcile-repos.yaml: tolerate a missing data branch (skip, don't fail). | |
| # Runs BEFORE solutions-query so the privacy scan reads the freshest repos.yaml. | |
| - name: ⤵ Overlay metadata from data branch | |
| if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' | |
| run: | | |
| set -euo pipefail | |
| if git ls-remote --exit-code origin data >/dev/null 2>&1; then | |
| overlay_err=$( | |
| { git fetch --no-tags origin data && git checkout origin/data -- metadata/; } 2>&1 | |
| ) || { | |
| echo "::warning::metadata overlay fetch/checkout failed (continuing — counts will use stale/missing metadata): $(echo "$overlay_err" | head -c 200)" | |
| } | |
| else | |
| echo "data branch not yet established; skipping metadata overlay." | |
| fi | |
| - name: Query solutions context | |
| id: solutions-query | |
| env: | |
| SOLUTIONS_QUERY_EVENT_NAME: ${{ github.event_name }} | |
| SOLUTIONS_QUERY_OWNER: ${{ github.repository_owner }} | |
| SOLUTIONS_QUERY_REPO: ${{ github.event.repository.name }} | |
| SOLUTIONS_QUERY_TITLE: >- | |
| ${{ | |
| github.event.pull_request.title || | |
| github.event.issue.title || | |
| github.event.discussion.title || | |
| '' | |
| }} | |
| SOLUTIONS_QUERY_BODY: >- | |
| ${{ | |
| github.event.pull_request.body || | |
| github.event.issue.body || | |
| github.event.comment.body || | |
| '' | |
| }} | |
| run: node scripts/solutions-query.ts | |
| - name: Run Fro Bot | |
| id: fro-bot-agent | |
| uses: fro-bot/agent@a31e0ad13a77e4f815b73c25dbcb214e82796728 # v0.109.0 | |
| env: | |
| OPENCODE_PROMPT_ARTIFACT: 'true' | |
| PERSONA: ${{ steps.persona.outputs.content }} | |
| WIKI_CONTEXT: ${{ steps.wiki-query.outputs.excerpt }} | |
| SOLUTIONS_CONTEXT: ${{ steps.solutions-query.outputs.excerpt }} | |
| # Handoff path only — never the file contents or selected paths themselves. | |
| WIKI_CONTEXT_HANDOFF_PATH: ${{ runner.temp }}/wiki-context-handoff-${{ github.run_id }}-${{ github.run_attempt }}.json | |
| TASK_PROMPT: >- | |
| ${{ | |
| (github.event_name == 'workflow_dispatch' && github.event.inputs.prompt) | |
| || inputs.prompt | |
| || ((github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && env.SCHEDULE_PROMPT) | |
| || (github.event_name == 'pull_request' && env.PR_REVIEW_PROMPT) | |
| || (github.event_name == 'issues' && contains(github.event.issue.labels.*.name, 'cross-repo-goal') && env.GOAL_DECOMPOSITION_PROMPT) | |
| || '' | |
| }} | |
| with: | |
| github-token: ${{ secrets.FRO_BOT_PAT }} | |
| auth-json: ${{ secrets.OPENCODE_AUTH_JSON }} | |
| model: ${{ vars.FRO_BOT_MODEL }} | |
| omo-providers: ${{ secrets.OMO_PROVIDERS }} | |
| opencode-config: ${{ secrets.OPENCODE_CONFIG }} | |
| prompt: | | |
| <persona> | |
| ${{ env.PERSONA }} | |
| </persona> | |
| ${{ env.TASK_PROMPT }} | |
| <wiki_context> | |
| ${{ env.WIKI_CONTEXT }} | |
| </wiki_context> | |
| <solutions_context> | |
| ${{ env.SOLUTIONS_CONTEXT }} | |
| </solutions_context> | |
| <wiki_context_expansion> | |
| The wiki context above is baseline-injected and usually enough on its own — do not | |
| expand by default. If baseline context is insufficient, following linked pages would | |
| help, or you have a short grounded query, you may optionally run one of: | |
| `node scripts/wiki-context-expand.ts linked` | |
| `node scripts/wiki-context-expand.ts query "<short grounded query>"` | |
| Linked mode reads the baseline-selected pages from the path in | |
| `WIKI_CONTEXT_HANDOFF_PATH`; do not print or echo that path, its file contents, the | |
| query text, returned excerpts, or any token/env values into your output or logs. Use | |
| returned wiki paths only to ground a recommendation. | |
| </wiki_context_expansion> | |
| <knowledge_persistence> | |
| Use the wiki context when it materially improves the response. | |
| Consult the solutions context for prior learnings relevant to this work; | |
| when one applies, follow it and cite its `Path:` in your reasoning. Treat | |
| entries marked as candidate suggestions as hints to verify, not settled facts. | |
| If this interaction surfaces durable knowledge worth keeping, update only | |
| `knowledge/wiki/**`, `knowledge/index.md`, and `knowledge/log.md` following | |
| `knowledge/schema.md`. Keep changes additive, preserve contradictions with | |
| dates and sources, and leave the wiki untouched when the signal is weak. | |
| </knowledge_persistence> | |
| # Discover the daily report issue URL created by the agent this run. | |
| # MUST run after the agent step (the agent creates the issue mid-run). | |
| # Matches the EXACT title "Daily Fro Bot Report — <today-UTC> (UTC)" (em-dash U+2014). | |
| # Two title families coexist — post-filter JSON for byte-equality to avoid false matches. | |
| # Shape-validates the URL before use; skips the post on zero/multiple matches (fail-soft). | |
| - name: 🔍 Discover daily report URL | |
| id: report-url | |
| if: >- | |
| (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && | |
| inputs.prompt == '' | |
| env: | |
| GH_TOKEN: ${{ secrets.FRO_BOT_PAT }} | |
| run: | | |
| set -euo pipefail | |
| TODAY_UTC="$(date -u +%Y-%m-%d)" | |
| EXPECTED_TITLE="Daily Fro Bot Report — ${TODAY_UTC} (UTC)" | |
| # Fetch open issues, post-filter for exact title match (byte-equality). | |
| # Fail-soft: any gh/jq error emits empty report_url + warning and exits 0. | |
| if ! MATCHES="$(gh issue list \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --state open \ | |
| --limit 50 \ | |
| --json number,title,url \ | |
| 2>/tmp/discover-err \ | |
| | jq --arg title "$EXPECTED_TITLE" \ | |
| '[.[] | select(.title == $title)]' \ | |
| 2>>/tmp/discover-err)"; then | |
| echo "report_url=" >> "$GITHUB_OUTPUT" | |
| echo "::warning::daily-digest: report URL discovery failed: $(head -c 200 /tmp/discover-err 2>/dev/null)" | |
| exit 0 | |
| fi | |
| COUNT="$(echo "$MATCHES" | jq 'length')" | |
| if [ "$COUNT" -ne 1 ]; then | |
| echo "report_url=" >> "$GITHUB_OUTPUT" | |
| echo "::notice::daily-digest: expected 1 report issue match, got ${COUNT}; skipping announce" | |
| else | |
| CANDIDATE_URL="$(echo "$MATCHES" | jq -r '.[0].url')" | |
| # Shape-validate: must be https://github.com/fro-bot/.github/issues/<n> | |
| if echo "$CANDIDATE_URL" | grep -qE '^https://github\.com/fro-bot/\.github/issues/[0-9]+$'; then | |
| echo "report_url=${CANDIDATE_URL}" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "report_url=" >> "$GITHUB_OUTPUT" | |
| echo "::notice::daily-digest: report URL failed shape validation; skipping announce" | |
| fi | |
| fi | |
| # Derive repos_tracked, surveys_today, should_post, count_status from metadata/repos.yaml. | |
| # Surfaces count_status=='error' as a step warning so a broken data overlay is visible. | |
| - name: 📊 Derive daily digest counts | |
| id: digest-counts | |
| if: >- | |
| (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && | |
| inputs.prompt == '' | |
| run: | | |
| set -euo pipefail | |
| TODAY_UTC="$(date -u +%Y-%m-%d)" | |
| export TODAY_UTC | |
| RESULT="$(node scripts/daily-digest-counts.ts metadata/repos.yaml "$TODAY_UTC")" | |
| REPOS_TRACKED="$(echo "$RESULT" | jq '.repos_tracked')" | |
| SURVEYS_TODAY="$(echo "$RESULT" | jq '.surveys_today')" | |
| SHOULD_POST="$(echo "$RESULT" | jq -r '.should_post')" | |
| COUNT_STATUS="$(echo "$RESULT" | jq -r '.count_status')" | |
| { | |
| echo "result=${RESULT}" | |
| echo "repos_tracked=${REPOS_TRACKED}" | |
| echo "surveys_today=${SURVEYS_TODAY}" | |
| echo "should_post=${SHOULD_POST}" | |
| echo "count_status=${COUNT_STATUS}" | |
| } >> "$GITHUB_OUTPUT" | |
| if [ "$COUNT_STATUS" = "error" ]; then | |
| echo "::warning::daily-digest-counts: count_status=error — metadata overlay may have failed; treating as quiet day" | |
| fi | |
| # Best-effort Discord presence: announce the daily digest to the gateway. | |
| # DORMANT until vars.DAILY_DIGEST_ENABLED is set (default unset at merge). | |
| # The dedicated step gate (not GATEWAY_ANNOUNCE_DISABLED) keeps this dormant | |
| # so the two live events (survey_completed, invitation_accepted) are unaffected. | |
| # Also runs on workflow_dispatch for on-demand validation. | |
| # Go-live: set DAILY_DIGEST_ENABLED after the gateway daily_digest variant is | |
| # deployed and verified in fro-bot/agent + marcusrbrown/infra. | |
| - name: 📣 Announce daily digest to gateway | |
| if: >- | |
| (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && | |
| inputs.prompt == '' && | |
| steps.digest-counts.outputs.count_status == 'ok' && | |
| steps.report-url.outputs.report_url != '' && | |
| vars.DAILY_DIGEST_ENABLED == 'true' | |
| env: | |
| EVENT_TYPE: daily_digest | |
| REPOS_TRACKED: ${{ steps.digest-counts.outputs.repos_tracked }} | |
| SURVEYS_TODAY: ${{ steps.digest-counts.outputs.surveys_today }} | |
| REPORT_URL: ${{ steps.report-url.outputs.report_url }} | |
| GATEWAY_PRESENCE_URL: ${{ vars.GATEWAY_PRESENCE_URL }} | |
| GATEWAY_WEBHOOK_SECRET: ${{ secrets.GATEWAY_WEBHOOK_SECRET }} | |
| GATEWAY_ANNOUNCE_DISABLED: ${{ vars.GATEWAY_ANNOUNCE_DISABLED }} | |
| run: | | |
| EVENT_CONTEXT_JSON="$(jq -nc \ | |
| --argjson repos_tracked "$REPOS_TRACKED" \ | |
| --argjson surveys_today "$SURVEYS_TODAY" \ | |
| --arg report_url "$REPORT_URL" \ | |
| '{repos_tracked: $repos_tracked, surveys_today: $surveys_today, report_url: $report_url}')" | |
| export EVENT_CONTEXT_JSON | |
| node scripts/gateway-announce.ts | |
| - name: Detect wiki insight changes | |
| id: wiki-changes | |
| if: always() && !cancelled() | |
| run: | | |
| git diff --no-ext-diff -- knowledge/index.md knowledge/log.md knowledge/wiki > "$RUNNER_TEMP/wiki-current.diff" | |
| current_hash=$(shasum -a 256 "$RUNNER_TEMP/wiki-current.diff" | cut -d ' ' -f1) | |
| if [ "$current_hash" = "${{ steps.wiki-baseline.outputs.hash }}" ]; then | |
| echo "changed=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "changed=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Capture ingest timestamp | |
| if: >- | |
| success() && | |
| steps.wiki-changes.outputs.changed == 'true' && | |
| contains(fromJSON('["issue_comment","pull_request_review_comment","discussion_comment","issues","pull_request","schedule","workflow_dispatch"]'), github.event_name) | |
| id: ingest-ts | |
| run: echo "now=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT" | |
| - name: Ingest wiki insight changes | |
| if: >- | |
| success() && | |
| steps.wiki-changes.outputs.changed == 'true' && | |
| contains(fromJSON('["issue_comment","pull_request_review_comment","discussion_comment","issues","pull_request","schedule","workflow_dispatch"]'), github.event_name) | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.FRO_BOT_PAT }} | |
| WIKI_OPERATION: event | |
| WIKI_TARGET: 'repo:${{ github.repository }}' | |
| WIKI_SUMMARY: Persisted durable knowledge from the ${{ github.event_name }} interaction on ${{ github.repository }}. | |
| WIKI_COMMIT_MESSAGE: 'feat(knowledge): ingest ${{ github.event_name }} context for ${{ github.repository }}' | |
| WIKI_SOURCES: '[{"url":"https://github.com/${{ github.repository }}","sha":"${{ github.sha }}","accessed":"${{ steps.ingest-ts.outputs.now }}"}]' | |
| run: | | |
| warning_file="$RUNNER_TEMP/wiki-ingest-warnings" | |
| if node scripts/wiki-ingest.ts 2>"$warning_file"; then | |
| status=0 | |
| else | |
| status=$? | |
| fi | |
| cat "$warning_file" >&2 | |
| if grep -q '^wiki-ingest:warning:' "$warning_file"; then | |
| { | |
| echo '### Wiki ingest warnings' | |
| grep '^wiki-ingest:warning:' "$warning_file" | sed 's/^wiki-ingest:warning:/- /' | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| exit "$status" |