Finds open conference CFPs relevant to the user across Java/AI/developer conferences, with persistent sent/dismissed/remind state and source-aware Sessionize verification. NanoClaw per-chat overlay, loaded via containerConfig.additionalTiles.
76
95%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Required procedure for check-cfps Step 8, in both scheduled and interactive modes. Execute in order; return to Step 8 for the internal report. Paths beginning references/ are relative to the owner skill directory.
Pre-write: dedup by URL. Run the dedup script against on-disk state to collapse any two entries whose cfp_url normalises to the same <host><path> (lowercase host, scheme/query/fragment dropped, trailing / stripped):
python3 /home/node/.claude/skills/tessl__check-cfps/scripts/dedup-by-url.pyWinner selection, source-priority ranking, and merge-field inheritance are the script's contract — see the scripts/dedup-by-url.py docstring; do not re-derive them in prose. What the skill relies on: user_actioned entries always win and are never mutated, and priority-bearing source attribution plus the name survive the merge (jbaruch/nanoclaw-conferences#23/#25). Groups the script refuses to resolve are reported in skipped_multi_user_actioned (stderr detail) — surface them for manual review.
Then for EVERY in-memory entry you are about to write — new candidates from Steps 2–4 AND stored rows carried through Steps 5–7 — invoke --lookup mode:
printf '%s\n' "<entry-1.cfp_url>" "<entry-2.cfp_url>" ... \
| python3 /home/node/.claude/skills/tessl__check-cfps/scripts/dedup-by-url.py --lookupReads newline-separated URLs from stdin; emits {<input_url>: <existing_slug_or_null>} JSON. For every non-null value, rewrite that entry's key in the in-memory set to the returned slug. This matters for stored rows too: the dedup pass above may have deleted a stored row's slug as a duplicate, and writing it back under its old key would resurrect the duplicate the dedup just removed (jbaruch/nanoclaw-conferences#24). If the rewrite makes two in-memory entries share a slug, they are both updates to that one state row — apply the priority rules below once for that slug. Idempotent.
Then apply priority rules (earlier wins):
user_actioned: true — preserve the entry's decision + metadata fields untouched: the bot does not refresh updated/last_verified (rules 5/6 apply only to entries actively written this run, not to preserved user_actioned ones) and does not re-tag matched_interests. The ONLY field stamped on these is schema_version (owner metadata, rule 10).
Sticky (shown_in_brief: true) — preserve status and the relevance notes in bot_notes. Allowed updates: deadline, city, conf_date, updated, last_verified, stale + ⚠️ STALE DATA prefix. The helper in item 9 applies the permitted exact-date warning update to these preserved notes. Exception: Step 5 confirmed closed or online overrides stickiness.
Existing open/approved without sticky — update status, bot_notes, metadata. Downgrade-to-dismissed MUST set status: "dismissed".
New entries — write status and bot_notes from Steps 6–7. Inherit _verified_this_run: true from Step 5. New entries that fail Sessionize verification are dropped.
Set updated to today on every written entry.
Set last_verified to today for every _verified_this_run: true entry.
_verify_failed: true AND status still open/approved: persist stale: true and prepend the canonical stale prefix per references/contracts.md (idempotent). Cleared on next successful verification.
Persist matched_interests from Step 6 on every open/approved entry it tagged this run. When Step 6 cleared it (priorities config missing/empty), delete the field from those entries; preserve the prior value untouched on user_actioned: true entries.
Commit through the lock-owning writer — never write cfp-state.json directly. First pass the final working set and exact-date availability judgments to the warning helper after the priority merge above. Use the final deduplicated slugs in both maps. On a resumed write, reassess date availability from the final entries' conf_date values.
python3 /home/node/.claude/skills/tessl__check-cfps/scripts/update-travel-warnings.pyStdin: {"entries": {"<slug>": <record>}, "exact_dates_known": {"<slug>": <boolean>}}. Supply the judgments required by the helper's docstring. Stdout is the updated slug → record working set, with no judgment fields added; the helper owns warning normalization and preservation guards. Exit 1 is invalid input: surface the diagnostic and abort before commit. This helper writes no files.
Pipe its successful output (_-prefixed keys excluded) to the committer, which applies per-slug replacements under the shared advisory lock (jbaruch/nanoclaw-conferences#35):
printf '%s' '<working-set json>' | python3 /home/node/.claude/skills/tessl__check-cfps/scripts/commit-state.pyConcurrent writers' updates to other slugs survive, and user_actioned: true is re-checked on disk at commit time so a mid-run user action is never overwritten — surface a non-zero skipped_user_actioned in the run report. Payload validation, the _-key refusal, and the output shape are the script's contract (scripts/commit-state.py docstring). Abort on non-zero exit.
Post-write dedup guard. After the state write, re-run dedup-by-url.py (same invocation as the pre-write pass). This is the deterministic backstop against duplicate resurrection: if any write re-created a slug the pre-write dedup had merged away, this pass collapses it again before the stampers run, so on-disk state never ends a run with two slugs for one CFP (jbaruch/nanoclaw-conferences#24). A clean run reports slugs_dropped: 0; a non-zero count means the lookup rewrite above was missed — surface it in the run report.
Do NOT hand-stamp schema_version. After the state write, run the deterministic stamper — the single source of stamping (owner migration per references/state-management.md "Schema version & ownership"):
python3 /home/node/.claude/skills/tessl__check-cfps/scripts/stamp-schema-version.pyIt stamps schema_version: 1 on EVERY record (incl. user_actioned, dismissed, sent, remind), idempotently, and rewrites the file only when something changed. Output: {"total": M, "stamped": N}. A non-zero exit means the state file is missing/unreadable — surface it.
_last_checked. After stamping schema versions, run the deterministic freshness stamper — the single writer of _last_checked:python3 /home/node/.claude/skills/tessl__check-cfps/scripts/stamp-last-checked.pyIt is evidence-gated (jbaruch/nanoclaw-conferences#8): it advances _last_checked only when the verify-sessionize.py driver left a verify-evidence.json marker for this run showing ≥1 entry resolved from a live response (or there was nothing to verify). Output on a clean stamp: {"_last_checked": "<iso>", "verification": "live"|"none-required"}, exit 0. If verification did not happen (driver skipped, or a total Sessionize outage), it does NOT advance the heartbeat — it writes _last_checked_skipped and exits 3: treat that exit like a stamp failure (do NOT proceed to clear the checkpoint in item 13; report a skipped-verification run). On exit 3, ALSO invalidate the verification stages so a same-day retry re-runs Step 5 live instead of resuming the failed evidence (jbaruch/nanoclaw-conferences#31):
python3 /home/node/.claude/skills/tessl__check-cfps/scripts/run-state.py invalidate verify working_set verify-evidenceEarlier stages (fetch, candidates) stay checkpointed — only the failed verification and everything downstream of it re-runs. Exit 1 means the state file is missing/unreadable — surface it. Freshness lives here, not in per-record updated.
python3 /home/node/.claude/skills/tessl__check-cfps/scripts/run-state.py doneOnly here, after the state write and both stampers succeeded — and only if the freshness stamper (item 12) exited 0. If the stamper exited 3 (verification not evidenced) or an earlier step failed and you stopped, do NOT clear — the saved stages let a same-day retry resume (references/run-state.md).
.tessl-plugin
skills
check-cfps
references
scripts
nightly-cfp-sync