CtrlK
BlogDocsLog inGet started
Tessl Logo

jbaruch/nanoclaw-travel

Travel assistant for NanoClaw: byAir flight notifications (delay, gate, connection risk, inbound aircraft delay, time-to-leave, arrival logistics), traffic-aware drive planning for in-person meetings (auto drive blocks + leave-by traffic rechecks), travel-booking gap checks, and nightly TripIt sync. Per-chat overlay plugin.

Quality

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

state-schema.mdskills/nightly-travel-sync/

Nightly Travel Sync — State Schema

Per coding-policy: stateful-artifacts. This skill owns two cross-invocation JSON artifacts: travel-schedule.json (cross-plugin) and travel-trips-seen.json (the new-trip-detection snapshot, this skill only). (The day-indexed travel-db.json it rebuilds in Step 4 is owned by the sibling check-travel-bookings skill — see that skill's state-schema.md.)

/workspace/group/travel-schedule.json

Flat list of upcoming TripIt events, projected from the live ICS feed.

  • Owner skill: nightly-travel-sync (this skill)
  • Writer: scripts/refresh-travel-schedule.py (Step 2) — the sole writer
  • Readers:
    • tessl__check-travel-bookings/scripts/build-travel-db.py (same plugin, Step 4 rebuilds travel-db.json from this file)
    • scripts/check-travel-freshness.py (same plugin, Step 3 reads mtime only, never the body)
    • nanoclaw-admin's morning-brief and check-cfps (cross-plugin via the shared /workspace/group/ mount, reading Trip-type records for travel-conflict checks)

Shape (schema_version 2)

A JSON array. Each element is one event record:

[
  {
    "schema_version": 2,
    "summary": "Madrid trip",
    "start": "2026-06-01",
    "end": "2026-06-05",
    "location": "Madrid, ES",
    "type": "Trip",
    "uid": "trip-100@tripit.com",
    "description": "..."
  }
]
FieldTypeRequiredDescription
schema_versionintegeryesCurrently 2. Present on every record (the artifact is a bare array, with no top-level object to hold a single version).
summarystringyesEvent title from the ICS SUMMARY.
start / endstringyesYYYY-MM-DD for date-only VEVENTs (trip wrappers). YYYY-MM-DDTHH:MM:SSZ for timed VEVENTs (flights, lodging check-ins, rentals).
locationstringnoICS LOCATION.
typestringyesTrip (trip-level wrapper) or the item [Type] from the ICS DESCRIPTION (Flight, Lodging, Rail, Car Rental, …). Unknown when absent.
uidstringyesICS UID. Trip wrappers lack item-. Items contain it.
descriptionstringnoThe ICS DESCRIPTION verbatim (added in v2). Carries the [Type] <DEP> to <ARR> line drive-engine's TripIt-union parser reads for a Flight's route (#156 R2). Additive — readers that don't use it are unaffected.

v1 → v2

Additive: the record gains the optional description field. No stored state to migrate — the schedule is regenerated in full each run, so the next Step 2 emits v2 records. Cross-plugin readers that gate on the version must accept v2: travel-core/trip_origin.py bumped its SCHEDULE_SCHEMA_VERSION to 2 (it does not read description, so it reads v1 and v2 identically).

Lifecycle

  • Regenerated, not accumulated — Step 2 overwrites the whole file from the live ICS feed every run. There is no read-modify-write continuity. The writer never migrates an existing file in place. It always emits the current SCHEMA_VERSION.
  • Write atomicity — the writer stages a same-dir .tmp sibling, validates it is a list with the required keys, then os.replaces it into place. A failed fetch/parse or a partial write exits non-zero and leaves the prior file (and its mtime) untouched, which Step 3's two-tier probe detects.

Migration policy

Bump SCHEMA_VERSION in refresh-travel-schedule.py for any record-shape change. The file is fully regenerated each run. The next successful Step 2 rewrites every record at the new version. No stored old-version state exists to upgrade. Readers are non-owners per coding-policy: stateful-artifacts. They extract the fields they need and tolerate the per-record schema_version without migrating it. A reader that gates on the version treats an unfamiliar value as "no usable schedule" and falls back to its own degraded path.

/workspace/group/travel-trips-seen.json

Last-seen snapshot of the upcoming trip set, used by Step 5 to detect trips that appeared since the previous nightly run (#204). New-trip detection only — never a source of truth for trip data (that is travel-db.json).

  • Owner skill: nightly-travel-sync (this skill)
  • Writer: scripts/detect-new-trips.py --commit (Step 5) — the sole writer
  • Readers: scripts/detect-new-trips.py (Step 5 detect mode) — the sole reader; no cross-plugin reader

Shape (schema_version 1)

{
  "schema_version": 1,
  "generated_at": "2026-07-20T06:00:00Z",
  "trips": {
    "madrid-trip-2026-06": {
      "summary": "Madrid trip",
      "start": "2026-06-01",
      "end": "2026-06-05"
    }
  }
}
FieldTypeRequiredDescription
schema_versionintegeryesCurrently 1.
generated_atstringyesUTC write time, YYYY-MM-DDTHH:MM:SSZ. Diagnostic only — never read back.
tripsobjectyesMap of trip slug{summary, start, end}. Keys are the make_slug slugs from build-travel-db.py; a new key on the next run is a new trip.

Lifecycle

  • Snapshot, not accumulator — each --commit rewrites trips to exactly the current upcoming set (end on/after today), so a trip aging into the past drops out. It is bounded by the number of upcoming trips, not ever-seen trips.
  • Seed run — when the file is absent (or unreadable, or carries an unrecognised schema_version), detect mode reports seeded: true with an empty new_trips; the whole itinerary is never logged as new. The following --commit writes the first snapshot. Only trips appearing after the seed count as new.
  • Write ordering — the skill logs new trips before --commit, so a logging failure leaves the snapshot untouched and the next run retries. The trusted-memory daily-log helper's line-dedup guards against a double entry across that retry.
  • Write atomicity — same-dir .tmp sibling + os.replace, matching the other travel writers.

Migration policy

Bump SCHEMA_VERSION in detect-new-trips.py (and this section) for any shape change. The owner is the sole reader and writer, and the snapshot is disposable — an unrecognised version reads as "no usable prior state" and triggers a seed rewrite at the current version, so no in-place migration path is needed. A lost snapshot costs at most one seed run (nothing logged that cycle).

README.md

tile.json