|
|
|
@ -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. |
|
|
|
|