en un clic
prj-report-daily
// PR Jangler daily email reporter. Use when the orchestrator dispatches the daily report at the configured local hour or when the user runs 'prj-report-daily' for manual debug.
// PR Jangler daily email reporter. Use when the orchestrator dispatches the daily report at the configured local hour or when the user runs 'prj-report-daily' for manual debug.
PR Jangler hub. Cron-driven autonomous workflow for triaging, verifying, reviewing, fix-planning, adversarially validating, fix-implementing, deciding, and daily-reporting on a GitHub PR backlog. Use when the user mentions "PR jangler", "pr-jangler", "PR backlog workflow", "PR triage", "autonomous PR review", "fix-plan", "fix-PR", "adversarial validator", "PleaseAdvise", or wants to install, configure, or extend the PR Jangler BMad module. Routes to 12 phase skills, owns the cross-cutting state-file conventions, idempotency rules, GitHub-label vocabulary, and 1Password/SMTP/`gh`/`op` dependency map shared by every phase. Triggers on prj-setup, prj-orchestrator, prj-discover, prj-triage, prj-verify-claim, prj-review, prj-detect-overlap, prj-plan-fix, prj-validate-adversarial, prj-implement-fix, prj-decision, prj-report-daily.
PR Jangler final-call workflow. Use when the orchestrator dispatches the decision phase for a reviewed PR or when the user runs 'prj-decision' to label, comment, and log a ready-to-merge / request-changes / close-as-not-now verdict.
PR Jangler duplicate and overlap detector. Use when the orchestrator dispatches the overlap check after triage marked a PR as possible-duplicate, or when the user runs 'prj-detect-overlap' for manual debug.
PR Jangler GitHub-to-queue sync. Use when the orchestrator dispatches the discovery sweep (on cadence or empty queue) or when the user runs 'prj-discover' for manual debug.
PR Jangler Tier-2 implementer. Use when prj-validate-adversarial passed for a PR and the orchestrator dispatches FixImpl. Pushes a fix branch and opens a fix-PR into the contributor's branch; falls back to a comment if the contributor's branch refuses PRs.
PR Jangler cron heartbeat dispatcher. Use when cron invokes one heartbeat of the PR backlog workflow or when the user runs 'prj-orchestrator' for manual debug.
| name | prj-report-daily |
| description | PR Jangler daily email reporter. Use when the orchestrator dispatches the daily report at the configured local hour or when the user runs 'prj-report-daily' for manual debug. |
This skill is the daily reporter for the PR Jangler module. One invocation aggregates the persisted queue at {project-root}/_bmad-output/pr-workflow/state.json, every per-PR cache under {project-root}/_bmad-output/pr-workflow/prs/, and the last 24 hours of run-log lines; renders a multipart email (HTML with inline CSS plus a plain-text fallback); resolves SMTP credentials from 1Password via op read; sends through prj_smtp_host on prj_smtp_port with STARTTLS; archives the rendered body to {project-root}/_bmad-output/pr-workflow/reports/{YYYY-MM-DD}.md; and updates state.last_report_sent on success.
Act as a calm, no-bury operations summarizer. PleaseAdvise rises first. "Needs your eyes" is capped at 5 with an overflow pointer to the archive. If nothing happened, send the brief "all quiet" liveness email rather than skip; the user must hear from the bot every day.
scripts/run.py) resolve from the skill root.{project-root}/... resolves from the project working directory.[modules.prj] in {project-root}/_bmad/config.toml.prj-orchestrator's state_io module (imported via sys.path); this skill never touches state.json directly.Load configuration from {project-root}/_bmad/config.toml (the [modules.prj] block). Required keys:
prj_repo (used in the subject + header for context)prj_email_to, prj_email_fromprj_smtp_host, prj_smtp_portprj_smtp_creds_ref (1Password reference, resolved via op read for username and password fields)If any required key is missing, exit with status misconfigured, log it, and do not touch SMTP. The runlog entry names the missing key.
Execute the report:
python3 scripts/run.py
That single script handles the full pipeline: aggregate, render, resolve creds, send, archive, persist last_report_sent, append run-log. Flags:
--dry-run render and archive locally but skip SMTP and do not update last_report_sent--verbose emit progress diagnostics to stderr--project-root <path> override autodetect--now <iso> override "now" for deterministic testing/replaySee python3 scripts/run.py --help for full detail.
Six Python modules, each with a single concern. The LLM activation context exists so the orchestrator's Skill-tool dispatch resolves cleanly when the daily-report cadence fires.
| Concern | Lives in |
|---|---|
| Shared state I/O (atomic load, validate, save, runlog) | state_io (imported from prj-orchestrator/scripts) |
| Aggregation: group PRs by status; build the shortlist; detect all-quiet | scripts/aggregate.py |
Rendering: subject + HTML + plain-text from assets/email-template.html | scripts/render.py |
Credential resolution: op read subprocess for prj_smtp_creds_ref | scripts/creds.py |
SMTP send: stdlib smtplib with STARTTLS + retry counter | scripts/smtp_send.py |
Archive write: markdown wrapper around HTML + plain-text under reports/ | scripts/archive.py |
| Orchestration | scripts/run.py |
This skill writes to:
{project-root}/_bmad-output/pr-workflow/state.json (only last_report_sent and report_send_failures){project-root}/_bmad-output/pr-workflow/reports/{YYYY-MM-DD}.md (frozen archive of the rendered report){project-root}/_bmad-output/pr-workflow/logs/{YYYY-MM-DD}.jsonl (append-only run-log)This skill reads from:
{project-root}/_bmad/config.toml{project-root}/_bmad-output/pr-workflow/state.json{project-root}/_bmad-output/pr-workflow/prs/{n}/ (review.md, decisions.log, verification.md, fix-plan.md, adversarial.md, implementation.md, meta.json){project-root}/_bmad-output/pr-workflow/logs/*.jsonl (last 24 hours)Codified in scripts/aggregate.py; summarized here for activation review.
needs-attention: PRs in PleaseAdvise plus PRs in FixImpl (an automated fix-PR was opened and is awaiting human eyes). Sorted by priority then by oldest phase_entered_at.in-progress: any PR in a non-terminal, non-PleaseAdvise phase (Discovered, Triaged, OverlapCheck, ReviewPending, Reviewed, CommentsTriage, ClaimVerify, FixPlan, AdversarialCheck).resolved-today: PRs whose phase transitioned to a terminal phase (ReadyToMerge, Blocked, Rejected, Archived) within the last 24 hours, sourced from last_action_at.no-change: PRs whose last_action_at is older than 24 hours and which are not already classified above. Count only (no per-PR detail in the email).needs-attention. Overflow rendered as "+ N more, see archive."needs-attention, no resolved-today, no in-progress activity in the last 24 hours (zero run-log entries with action=dispatch for per-PR skills). When all-quiet, the subject becomes [PR Jangler] {date}: all quiet and the body is a one-paragraph liveness confirmation.<link>, no <script>, no external assets.string.Template.substitute against assets/email-template.html. No Jinja2.MIMEMultipart('alternative') with the HTML.$ placeholder remains; an exception raises rather than send a broken email.[PR Jangler] {date}: {n} need attention, {m} in progress. All-quiet override: [PR Jangler] {date}: all quiet.op read "{prj_smtp_creds_ref}/username" and op read "{prj_smtp_creds_ref}/password". Failures raise CredsError with a clear message; the runlog records status: "creds-error" and the script exits 4.smtplib.SMTP(host, port), starttls(), login(user, pass), sendmail(from, [to], message.as_string()), quit().status: "smtp-error" and increment state["report_send_failures"] (initialized to 0 if absent). last_report_sent is NOT updated. The script exits 5. The next heartbeat will retry. After 3 consecutive failures, the script also injects a synthetic system entry into state["prs"]["0"] (the digits-key sentinel; pr_number: 0) at phase PleaseAdvise, surfacing the failure to the user via the next successful report.state.last_report_sent is updated ONLY after smtplib returns without raising. On retry the next heartbeat re-renders against the latest state (so the report content always reflects current reality, not a stale snapshot).report_send_failures counter resets to 0 on successful send and on the synthetic __system__ entry being created.state_io.save_state. Direct writes to state.json are forbidden.Smoke-test against a configured project (does not require SMTP; uses --dry-run):
python3 scripts/run.py --dry-run --verbose
Expected: exit 0, runlog entry with status: "dry-run", archive file present at reports/{YYYY-MM-DD}.md, state.last_report_sent UNCHANGED.
Run the unit tests (no SMTP, no op calls; all subprocess invocations mocked):
python3 -m unittest discover scripts/tests