From e7f044e52598b20305495d1386ffc888c82f57e8 Mon Sep 17 00:00:00 2001 From: Daniel Kucinski Date: Mon, 14 Sep 2026 18:25:59 +0100 Subject: [PATCH] chore: sync Scout skills 2026-09-14 --- teams-export/SKILL.md | 113 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 2 deletions(-) diff --git a/teams-export/SKILL.md b/teams-export/SKILL.md index 754f2f4..12d2af7 100644 --- a/teams-export/SKILL.md +++ b/teams-export/SKILL.md @@ -205,6 +205,111 @@ Conventions: - Timestamps in America/Los_Angeles, 24-hour. - Separate root messages with a horizontal rule (`---`). +### Auto-nominate new sources (opt-in, read-only) + +Off by default. The caller enables it (`nominate: true`); ad-hoc `/teams-export` +runs should leave it off so they stay fast. The nightly archive automation +turns it on. + +Purpose: `sources.yaml` is an allowlist, so its failure mode is **silent +omission** — a new important thread archives nothing and the user never finds +out. This pass closes that gap by surfacing candidates for a one-line opt-in. + +**This pass NEVER edits `sources.yaml`.** It only reports. + +Run it *after* the export pass, so a failed export never costs the user their +nominations (and vice versa). + +**Steps:** + +1. Build the set of known identifiers from `sources.yaml`: every `chat_id` and + `channel_id` from all three sections — **including entries with + `enabled: false`**. A deliberately-retired source must never come back as a + nomination. +2. **Resolve `oneOnOne` entries that have no cached `chat_id`.** Several entries + are identified only by `email`, so they have no ID to match against and will + be falsely nominated as "new" unless handled. For every 1:1 candidate, get + the other participant's email — call `workiq_list_chats` with + `expand: "members"`, or `workiq_get_chat` on the candidate — and treat the + chat as known if that email matches an `email` in `sources.yaml` + (case-insensitive). When you resolve one this way, report its `chat_id` in + the run summary under the existing "newly resolved 1:1 chat_id" item, so the + user can cache it and remove the ambiguity permanently. +3. Read the optional top-level `ignore:` list (entries with `chat_id` or + `channel_id`). Treat everything in it as known. A missing or empty `ignore:` + is normal — don't error. +4. Call `workiq_list_chats` ordered by most recent activity, paging with + `skipToken` until you reach chats whose last activity predates the run + window. Don't page further back than the window. Filter on + `lastUpdatedDateTime` first — this is what removes the long tail of dormant + chats before you spend any calls counting messages. +5. For each chat not in the known set, count messages inside the run window. + Nominate only if **≥5 messages** land in the window — this is what keeps the + single-reply meeting chats out. Skip any chat whose in-window messages are + all system/bot events (joins, leaves, call records, recording notices) with + no human authorship. +6. Load the state file (below) and apply the repeat rules. +7. Report survivors in the run summary. + +**State file:** `D:\Repos\Obsidian\00 - Chats\.nominations.json` + +The one file this skill may write outside the monthly export folders. Shape: + +```json +{ + "19:meeting_abc...@thread.v2": { + "topic": "Patch Plan V-Team Sync", + "first_seen": "2026-09-14", + "last_reported": "2026-09-14", + "times_reported": 1 + } +} +``` + +**Repeat rules** — these exist so the pass never becomes nagging: + +- Report a candidate the first time it's seen. +- Re-report only if it's still unlisted, still active, and ≥7 days since + `last_reported`. +- After `times_reported` reaches **3**, stop reporting it permanently. Keep the + record so it isn't re-nominated from scratch later. +- Prune records for chats that have since been added to `sources.yaml` or + `ignore:`, so re-adding a source later starts the count fresh. + +If the state file is missing or unparseable, treat it as empty and rewrite it — +never let a bad state file abort the run. + +**Report shape** — give the user something they can paste, and say *why* it +was nominated: + +``` +Nominated (not in sources.yaml): + • "Patch Plan V-Team Sync" — 23 msgs since 09-07, 4 participants (1st time seen) + - alias: patch-plan-vteam + type: group + chat_id: "19:meeting_abc...@thread.v2" + topic: Patch Plan V-Team Sync + • "Lee/Daniel Chat" — 8 msgs since 09-07 (reported 2x, will stop after 1 more) + - alias: lee-daniel + type: group + chat_id: "19:...@thread.v2" + +To silence one permanently, add its chat_id under `ignore:` in sources.yaml. +``` + +Suggest a kebab-case `alias` derived from the topic or participant names, and +check it doesn't collide with an existing alias — aliases are filenames and must +stay unique. + +**Topics are not unique.** Distinct chats routinely share a topic (Daniel's +tenant currently has two separate `Patch Plan V-Team Sync` meeting chats with +different `chat_id`s). Never assume topic identifies a chat. Always key state, +dedup, and known-set matching on `chat_id` / `channel_id`. When two candidates +in the same report share a topic, disambiguate the suggested aliases with a date +or sequence suffix (e.g. `patch-plan-vteam-2026-09`) and say plainly in the +report that these are two different chats, so the user doesn't assume it's a +duplicate and opt in only one. + ### Run summary After every run, print a short summary to the user: @@ -214,11 +319,15 @@ After every run, print a short summary to the user: - Any errors - **Any newly resolved 1:1 `chat_id` values** (so the user can paste them into `sources.yaml` to skip the lookup next time) +- **Nominations**, if the nominate pass ran and produced any (see above) ### Guardrails -- Never write outside `D:\Repos\Obsidian\00 - Chats\`. -- Never edit `sources.yaml`. +- Never write outside `D:\Repos\Obsidian\00 - Chats\`. The only non-export file + this skill may write is `00 - Chats\.nominations.json` (nominate-pass state). +- **Never edit `sources.yaml`** — not to add a nominated source, not to prune a + dead one, not to update `ignore:`. Report and let the user decide. This is the + boundary that keeps the allowlist trustworthy. - Respect MIP sensitivity labels — if a source surfaces classified content, note it in the run summary so the user knows the output file inherits that sensitivity.