daily-brief — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited daily-brief (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Status. The author has migrated orchestration to an hourly Claude Code cloud routine — see orchestration/README.md (Pattern A). This skill documents the local / GitHub-Actions-era pattern (Pattern B), which remains fully usable for setups without cloud routines.
This skill resolves placeholders against ~/.claude/state/personal_config.json. See _config/README.md and _config/personal_config.example.json for setup. If the config is missing or a needed field is unset, the skill must surface an error to the user and refuse to proceed rather than guess.
Required config fields:
personal_config.notion.tasks_data_source_id — the data-source ID of the Tasks DB.personal_config.paths.claude_state_dir — local directory holding ephemeral state files (e.g. ~/.claude/state).<claude_state_dir>/telegram.json with fields {bot_token, chat_id}. Bot tokens never go in personal_config.json.This is the outbound half of the task orchestration system. Once per weekday morning (and on demand), it converts the unified Notion Tasks DB into a 3–7 item ranked list and pings Telegram. The brief is the daily contract: if it's not on the brief, it doesn't need to happen today.
The capture half (/capture) reads replies and writes back. This skill is also the writer of today_brief.json, which /capture reads to resolve "<N> done"-style replies.
/daily-brief — full run: query → score → pick → send Telegram → write today_brief.json./daily-brief --hours <N> — override available deep-work hours./daily-brief --dry — print the brief to chat, do not call Telegram, do not write state. Useful for previewing.Flags can combine: /daily-brief --hours 4 --dry.
collection://{{personal_config.notion.tasks_data_source_id}}{{personal_config.notion.tasks_data_source_id}}<claude_state_dir>/telegram.json — read and parse as JSON. Fields: bot_token, chat_id.https://api.telegram.org/bot<bot_token>/<claude_state_dir>/today_brief.jsonUse mcp__notion__notion-query-database-view with:
data_source_url: collection://{{personal_config.notion.tasks_data_source_id}}Status to To-do or In progress.Hydrate each row's Task, Status, Priority, Due, Est min, Type, Project, Energy, created_time, and page_id. Tasks missing a Priority should be treated as P2; missing Est min as 30; missing Energy as Shallow.
Order of precedence:
--hours <N> flag wins. Use <N> × 60 as available_minutes.available_hours = 3. (If a calendar source is available, replace this with a real busy-time query; see docs/outlook-gmail.md.)available_minutes = available_hours × 60.
For each task, compute:
priority_weight = {P0: 8, P1: 4, P2: 2, P3: 1}[Priority]
days_until_due = (Due - today).days if Due present else 999
urgency_factor = max(1.0, 7.0 / max(1, days_until_due))
# due-today → 7×; in 3 days → 2.33×; in 7+ days or no due → 1×
type_balance[T] = 1.5 if T not in last_3_briefs
else 0.85 if T in all of last_3_briefs
else 1.0
energy_match = 1.2 if (Energy == "Deep" and available_hours >= 2) else 1.0
days_since_created = (today - created_time.date()).days
wait_factor = min(2.0, 1 + 0.1 × days_since_created)
# cap at 2.0× so wait_factor never dominates a P0 deadline
score = priority_weight × urgency_factor × type_balance × energy_match × wait_factorlast_3_briefs is read from <claude_state_dir>/last_3_briefs.json. Each entry is {date, types: [Type, ...]}. If the file is missing, treat as empty — every Type is "not seen", so all get the 1.5× boost. Compute the set of Types appearing in the last 3 entries to evaluate the rule.
wait_factor uses Notion's created_time property; for any task missing it, fall back to days_since_created = 0 (→ 1.0×).
Overdue tasks (days_until_due < 0) get urgency_factor = 7.0 (capped — equivalent to due-today). Don't let the math explode for tasks 30 days overdue.
Sort tasks descending by score. Walk down the list, adding tasks to the brief while cumulative Est min ≤ available_minutes. Stop at 7 tasks even if more fit (the brief loses its bite past 7). Always include at least 3 tasks if at least 3 are open, even if they overflow available_minutes slightly — the user would rather see 3 things than be told "your plate is full" with one item.
Number the selected tasks 1..N in the order they appear in the brief.
Follow this exact template:
Morning brief — <Day> <Mon> <DD> — <H> deep hrs available
P0 > (1) <Task title> (<est>) [#<Project or Type>]
P1 > (2) <Task title> (<est>) [#<Project or Type>]
P2 > (3) <Task title> (<est>) [#<Project or Type>]
...
Reply: "1 done", "2 push thu", "add: X P2 30min #personal"Formatting rules:
Tue May 12).<H> shows one decimal only if non-integer.<est> formatted as 90min or 3h (use hours when >= 60min and a clean multiple, else minutes).[#<tag>] — prefer Project name (relation resolved); if no Project, use Type (#Personal, #Admin, etc.).$tg = Get-Content "<claude_state_dir>\telegram.json" | ConvertFrom-Json
$body = @{ chat_id = $tg.chat_id; text = $message } | ConvertTo-Json
Invoke-RestMethod -Uri "https://api.telegram.org/bot$($tg.bot_token)/sendMessage" -Method Post -Body $body -ContentType "application/json"Bash equivalent:
TG=$(cat "$CLAUDE_STATE_DIR/telegram.json")
TOKEN=$(echo "$TG" | jq -r .bot_token)
CHAT=$(echo "$TG" | jq -r .chat_id)
curl -s -X POST "https://api.telegram.org/bot$TOKEN/sendMessage" \
-d "chat_id=$CHAT" --data-urlencode "text=$MESSAGE"Skip this entirely if --dry is set.
Always (unless --dry) write the current selection to <claude_state_dir>/today_brief.json:
{
"date": "2026-05-12",
"available_hours": 3,
"tasks": [
{"n": 1, "page_id": "...", "title": "Submit R&R revision", "priority": "P0", "est_min": 180, "due": "2026-05-12"},
{"n": 2, "page_id": "...", "title": "Email collaborator re: travel", "priority": "P1", "est_min": 10, "due": "2026-05-13"}
]
}Use UTF-8 (no BOM) encoding so /capture reads it cleanly. Write atomically (tmp + rename) — cloud-sync clients can corrupt mid-write JSON on Windows, and /capture reading a half-flushed file is the failure mode this guards against:
$path = "<claude_state_dir>\today_brief.json"
$tmp = "$path.tmp"
Set-Content -Path $tmp -Value $json -Encoding utf8
Move-Item -Force $tmp $pathApply the same tmp + rename pattern to last_3_briefs.json when updating the rolling type-balance log (prepend today's entry {date, types}, truncate to the last 3, write atomically).
Overwrite, don't append today_brief.json — each day's brief replaces the last.
In the chat (not Telegram), report: how many tasks were considered, the chosen N, available_hours and where it came from (flag / default), and any anomalies (overdue tasks not selected, etc.). Keep it to a few lines.
Example 1 — full run, default hours:
Example 2 — `--hours 1`:
Example 3 — `--dry`:
today_brief.json. Suggest retry in a few minutes.today_brief.json so the system has state, then report the Telegram failure in chat and suggest re-running. Don't retry the send automatically — the user might be in a meeting and a stale ping is annoying.Inbox empty <date> — Add tasks with "add: ..." or queue some in Notion. Write a today_brief.json with tasks: [] so /capture still has a valid file.--hours flag overrides. Mention in chat report only if relevant.<claude_state_dir>/telegram.json. Don't make up a chat_id or token./schedule for daily 7am runs and /loop for on-demand polling. /daily-brief is a single-shot./capture./notion-log.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.