Skip to content

Fro Bot

Fro Bot #5732

Workflow file for this run

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