kushi-agents 3.4.2 → 3.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/.github/copilot-instructions.kushi.md +38 -0
  2. package/README.md +33 -0
  3. package/bin/cli.mjs +2 -0
  4. package/package.json +17 -4
  5. package/plugin/agents/kushi.agent.md +155 -147
  6. package/plugin/instructions/ado-bootstrap-discovery.instructions.md +111 -0
  7. package/plugin/instructions/ado-engagement-tree.instructions.md +73 -0
  8. package/plugin/instructions/answer-from-evidence.instructions.md +1 -1
  9. package/plugin/instructions/auth-and-retry.instructions.md +51 -16
  10. package/plugin/instructions/azure-auth-patterns.instructions.md +13 -6
  11. package/plugin/instructions/bootstrap-status-format.instructions.md +113 -0
  12. package/plugin/instructions/capture-learnings.instructions.md +95 -0
  13. package/plugin/instructions/cleanup-on-resolution.instructions.md +69 -0
  14. package/plugin/instructions/crm-bootstrap-discovery.instructions.md +79 -0
  15. package/plugin/instructions/crm-internal-vs-confirmed.instructions.md +79 -0
  16. package/plugin/instructions/evidence-confidence-ladder.instructions.md +66 -0
  17. package/plugin/instructions/evidence-layout-canonical.instructions.md +115 -0
  18. package/plugin/instructions/evidence-thoroughness.instructions.md +82 -12
  19. package/plugin/instructions/full-view-gate.instructions.md +91 -0
  20. package/plugin/instructions/m365-id-registry.instructions.md +134 -0
  21. package/plugin/instructions/meetings-verbatim-required.instructions.md +176 -0
  22. package/plugin/instructions/run-reports.instructions.md +129 -0
  23. package/plugin/instructions/scope-boundaries.instructions.md +218 -0
  24. package/plugin/instructions/snapshot-vs-stream.instructions.md +2 -0
  25. package/plugin/instructions/update-ledger.instructions.md +132 -0
  26. package/plugin/instructions/verbatim-by-default.instructions.md +73 -0
  27. package/plugin/instructions/workiq-first.instructions.md +15 -31
  28. package/plugin/instructions/workiq-only.instructions.md +193 -0
  29. package/plugin/learnings/README.md +50 -0
  30. package/plugin/learnings/ado.md +45 -0
  31. package/plugin/learnings/crm.md +96 -0
  32. package/plugin/learnings/cross-cutting.md +36 -0
  33. package/plugin/learnings/email.md +33 -0
  34. package/plugin/learnings/meetings.md +30 -0
  35. package/plugin/learnings/misc.md +46 -0
  36. package/plugin/learnings/onenote.md +215 -0
  37. package/plugin/learnings/sharepoint.md +5 -0
  38. package/plugin/learnings/teams.md +5 -0
  39. package/plugin/plugin.json +22 -2
  40. package/plugin/prompts/apply-ado.prompt.md +14 -0
  41. package/plugin/prompts/propose-ado.prompt.md +12 -0
  42. package/plugin/reference-packs/fde/crm-field-manifest.md +165 -0
  43. package/plugin/skills/apply-ado-update/SKILL.md +125 -0
  44. package/plugin/skills/ask-project/SKILL.md +2 -0
  45. package/plugin/skills/bootstrap-project/SKILL.md +81 -3
  46. package/plugin/skills/propose-ado-update/SKILL.md +108 -0
  47. package/plugin/skills/pull-ado/SKILL.md +173 -23
  48. package/plugin/skills/pull-crm/SKILL.md +168 -15
  49. package/plugin/skills/pull-email/SKILL.md +139 -22
  50. package/plugin/skills/pull-meetings/SKILL.md +109 -25
  51. package/plugin/skills/pull-misc/README.md +84 -0
  52. package/plugin/skills/pull-misc/SKILL.md +257 -0
  53. package/plugin/skills/pull-misc/runner.mjs +280 -0
  54. package/plugin/skills/pull-onenote/README.md +90 -0
  55. package/plugin/skills/pull-onenote/SKILL.md +400 -51
  56. package/plugin/skills/pull-onenote/runner.mjs +356 -0
  57. package/plugin/skills/pull-onenote/scripts/recapture-section-url.mjs +295 -0
  58. package/plugin/skills/pull-onenote/write-snapshot.mjs +271 -0
  59. package/plugin/skills/pull-sharepoint/SKILL.md +44 -12
  60. package/plugin/skills/pull-teams/SKILL.md +40 -11
  61. package/plugin/skills/refresh-project/SKILL.md +33 -2
  62. package/plugin/skills/self-check/run.ps1 +186 -4
  63. package/plugin/templates/ado-update/discussion-comment.template.md +26 -0
  64. package/plugin/templates/ado-update/integrations-ado-writes.example.yml +49 -0
  65. package/plugin/templates/ado-update/proposed.template.md +78 -0
  66. package/plugin/templates/init/external-links.template.txt +30 -0
  67. package/plugin/templates/init/project-integrations.template.yml +57 -2
  68. package/plugin/templates/snapshot/meeting-verbatim.template.md +110 -0
  69. package/plugin/templates/snapshot/meetings-series-index.template.md +3 -1
  70. package/plugin/templates/snapshot/onenote-page.template.md +92 -23
  71. package/plugin/templates/weekly/meetings-stream.template.md +11 -6
  72. package/src/copilot-instructions.mjs +80 -0
  73. package/src/main.mjs +18 -1
@@ -0,0 +1,134 @@
1
+ ---
2
+ applyTo:
3
+ - "plugin/skills/bootstrap-project/**"
4
+ - "plugin/skills/refresh-project/**"
5
+ - "plugin/skills/pull-*/**"
6
+ priority: HARD
7
+ ---
8
+
9
+ # M365 ID Registry — discover once, consume deterministically
10
+
11
+ **Doctrine:** Bootstrap discovers; refresh consumes. Refresh runs MUST NOT re-discover canonical M365 identifiers — that is the source of "it worked the first time but not the second" non-determinism.
12
+
13
+ ## The registry
14
+
15
+ `<engagement-root>/.project-evidence/m365/m365-mutable.json#knownSections.<projectKey>` is the **single source of truth** for canonical M365 identifiers per project.
16
+
17
+ Schema (populate every key the source supports):
18
+
19
+ ```jsonc
20
+ "<projectKey>": {
21
+ // OneNote
22
+ "one_sectionName": "<displayName>.one",
23
+ "one_sectionFileId": "<wdsectionfileid GUID>", // primary, extracted from Doc.aspx URL fragment
24
+ "one_sectionGroupId": "<wdsectiongroupid GUID>", // when boundary is a section group
25
+ "one_sectionOneNoteGuid": "<wdsectiononenoteguid GUID>", // alternate
26
+ "one_sectionPath": "/<group>/<section>.one",
27
+ "one_sectionWebUrl": "<full Doc.aspx URL>",
28
+ "one_notebookSourceDoc": "<notebook sourceDoc GUID>",
29
+ "one_notebookName": "<notebook display name>", // browser path needs display name
30
+ "one_notebookSpoBaseUrl": "https://<tenant>.sharepoint(-df)?.com/personal/<upn>", // browser path
31
+ "one_pages": [ // per-page retry registry (dual-ID schema, pull-onenote v2.6.0)
32
+ {
33
+ "title": "<page title>",
34
+ "wdpartid": "<SharePoint search-index page GUID — used by WorkIQ>",
35
+ "webPageId": "<OneNote-for-Web page GUID — used by browser scrape>",
36
+ "lastModified": "<as reported by source>",
37
+ "last_status": "captured | user-pasted | auth-required | workiq-degraded | BODY-NOT-EXPOSED | short-suspect | enumeration-only",
38
+ "captured_via": "browser | workiq | user-paste",
39
+ "attempts": 0,
40
+ "last_attempt_at": "<ISO-8601>",
41
+ "snapshot_path": "Evidence/<alias>/onenote/snapshot/pages/<safe-title>.md",
42
+ "captured_at": "<ISO-8601, only when last_status ∈ {captured, user-pasted}>"
43
+ }
44
+ ],
45
+ // Email
46
+ "emailContext": "Inbox/<folder-path>",
47
+ // Teams
48
+ "teamsChatContext": {
49
+ "chatHints": ["..."],
50
+ "channelHints": ["..."],
51
+ "participantHints": ["..."]
52
+ },
53
+ // SharePoint
54
+ "sp_siteId": "<siteId>",
55
+ "sp_webId": "<webId>",
56
+ "sp_listId": "<listId>",
57
+ "sp_path": "/<site>/<library>/<folder>",
58
+ // CRM
59
+ "crm_envUrl": "<env>.crm.dynamics.com",
60
+ "crm_entitySet": "<entityset>",
61
+ "crm_recordId": "<guid>",
62
+ "crm_requestId": "<FE-YYYY-NNNNNN>",
63
+ // ADO
64
+ "ado_org": "<orgName>",
65
+ "ado_project": "<projectName>",
66
+ "ado_engagementId": "<int>",
67
+ "ado_workItemIds": [<int>, ...],
68
+ // Misc — user-curated link list (pull-misc v0.1.0+)
69
+ "misc_links": [ // per-link retry registry
70
+ {
71
+ "type": "loop | web | learn | pdf | github | file | onenote | sharepoint | ado",
72
+ "owner": "<as in external-links.txt>",
73
+ "title": "<as in external-links.txt>",
74
+ "url": "<as in external-links.txt>",
75
+ "notes": "<as in external-links.txt>",
76
+ "last_status": "captured | placeholder | auth-required | fetch-failed | skipped-binary | removed | not-yet-attempted | delegated",
77
+ "captured_via": "browser | http | file | delegated",
78
+ "delegated_to": "pull-onenote | pull-sharepoint | pull-ado",
79
+ "http_status": 200,
80
+ "content_type": "text/html",
81
+ "char_count": 12345,
82
+ "etag": "<HTTP ETag>",
83
+ "last_modified_http":"<HTTP Last-Modified>",
84
+ "attempts": 0,
85
+ "last_attempt_at": "<ISO-8601>",
86
+ "captured_at": "<ISO-8601, only when last_status=captured>",
87
+ "snapshot_path": "Evidence/<alias>/misc/snapshot/<safe-type>__<safe-title>.md"
88
+ }
89
+ ]
90
+ }
91
+ ```
92
+
93
+ ## Bootstrap contract (HARD)
94
+
95
+ For every enabled source, bootstrap MUST:
96
+
97
+ 1. **Probe** WorkIQ / source-native API with the user-supplied seed (folder name, channel name, request id, work item id) using the per-source discovery procedure in `pull-<source>/SKILL.md` § "Bootstrap discovery".
98
+ 2. **Validate** by re-issuing the source's Step A enumerate query against the resolved ID. The resolved ID is accepted ONLY if the validate query returns ≥ 1 row.
99
+ 3. **Persist** the resolved IDs to `m365-mutable.json#knownSections.<projectKey>` AND mirror them into the corresponding `boundaries.<source>.*` keys in `<project>/integrations.yml`.
100
+ 4. **Record** one line per source in `bootstrap-status.md`: `<source>: resolved <key>=<value> via seed "<seed>" (<count> items enumerated)`.
101
+ 5. If discovery fails after trying all alternate identifiers documented in the per-source SKILL, write the source as `disabled` in `integrations.yml`, park the question in `OPEN-QUESTIONS-DRAFT.md` (or `State/09_open-questions.md`), and continue. Never write speculative or partial IDs.
102
+
103
+ ## Refresh contract (HARD)
104
+
105
+ Every refresh MUST:
106
+
107
+ 1. **Read** `m365-mutable.json#knownSections.<projectKey>` first. If the entry is missing or the per-source key is empty → DO NOT probe. Re-dispatch through `bootstrap-project` for that source's discovery only, then resume.
108
+ 2. **Pass the canonical IDs to each pull-* skill in the form that skill's WorkIQ queries actually use.** Different sources need different forms:
109
+ - **OneNote (corrected v3.8.0):** PRIMARY path is browser-scrape via Playwright with persisted profile (returns reliable verbatim bodies — 16/16 captured for HCA on 2026-05-14 vs WorkIQ's 1/18). FALLBACK path is WorkIQ natural-language by display name (used only when Playwright profile is auth-expired). Per-page identifiers are stored in BOTH forms: `webPageId` (used by browser-scrape navigation) AND `wdpartid` (used by WorkIQ correlation and stream events). See `pull-onenote/SKILL.md` v2.6.0 § "Empirical contract" and `learnings/onenote.md` 2026-05-14 for proof. The v3.7.8 wdsectionfileid-filter doctrine and the v3.7.9 WorkIQ-primary doctrine are both retracted.
110
+ - **Email / Teams / SharePoint / CRM / ADO:** the registry IDs are passed as documented in each pull-* skill — typically as Graph IDs in API calls, not WorkIQ prose.
111
+ - **Misc (pull-misc v0.1.0+):** the link list is the user-curated `<project>/external-links.txt` file. Refresh re-parses the file every run, matches existing `misc_links[]` entries by `(type, url)` tuple, marks deleted entries as `removed` (preserves snapshot), creates new entries as `not-yet-attempted`, and routes per-type to runner branches (browser for loop, http for web/learn/docs/pdf/github, file for local, delegated for onenote/sharepoint/ado). See `pull-misc/SKILL.md` § "Step A — enumerate".
112
+ 3. **Never re-discover.** A refresh that probes WorkIQ with seeds (folder name, channel name) instead of canonical IDs is a defect. The only exception is the cleanup-on-resolution path documented in `cleanup-on-resolution.instructions.md`, which UPSERTS newly-resolved IDs into the registry mid-run.
113
+
114
+ ### Browser-fields self-heal exception (kushi v3.10.0+, OneNote-only)
115
+
116
+ OneNote requires FIVE registry fields to drive the Playwright runner — not just the WorkIQ `wdsectionfileid`. Pre-doctrine registry entries (bootstrapped before kushi v3.10.0) and entries written from synthesized URL templates are missing or wrong on `one_notebookSourceDoc`, `one_notebookSpoBaseUrl`, and `one_sectionWebUrl`. Refresh MAY (and MUST) dispatch `plugin/skills/pull-onenote/scripts/recapture-section-url.mjs` mid-run when these fields are absent. This is **not** re-discovery — `wdsectionfileid` is preserved if already present. It is a one-time backfill via user URL paste, cached forever after.
117
+
118
+ The script's `--check` mode is the gate; the interactive mode is the heal. See `pull-onenote/SKILL.md` Pre-flight C for the contract.
119
+
120
+ 4. **Cite** the registry path in the per-user refresh report: `Resolved IDs sourced from m365-mutable.json#knownSections.<projectKey> (last_updated: <ts>)`.
121
+
122
+ ## Anti-patterns (HARD-rule violations)
123
+
124
+ 1. ❌ Refresh probes WorkIQ for `"List sections in OneNote notebook"` instead of reading `one_sectionFileId` from the registry.
125
+ 2. ❌ Bootstrap persists an ID without validating it via the source's Step A enumerate query.
126
+ 3. ❌ Pull-* skill uses the wrong WorkIQ phrasing for the source. For OneNote specifically: using `wdsectionfileid = <id>` as filter syntax routes WorkIQ to summary mode — the working WorkIQ pattern is natural-language naming `one_sectionName` and notebook display name. But per `pull-onenote/SKILL.md` v2.6.0, browser-scrape is the PRIMARY path; WorkIQ is fallback only.
127
+ 6. ❌ A pull-* skill silently drops pages/items that returned BODY-NOT-EXPOSED, auth-required, or workiq-degraded instead of registering them for retry. Per-page retry registries (e.g. `one_pages[]` for OneNote) are the durable state that survives across refreshes — without them, refresh runs cannot tell pending-retry from never-attempted.
128
+ 7. ❌ A pull-* skill stores only ONE form of an identifier when the registry doctrine requires multiple forms. For OneNote specifically: `one_pages[]` entries MUST persist BOTH `webPageId` (browser navigation) AND `wdpartid` (WorkIQ correlation) when both are observed. Storing only one creates a single-path lock-in that the empirical record (browser-vs-WorkIQ retrieval gap) explicitly forbids.
129
+ 4. ❌ Bootstrap writes a partial / speculative ID with a `_note` field instead of marking the source disabled.
130
+ 5. ❌ Refresh "rediscovers" because it didn't find the project key — must re-dispatch through bootstrap, not improvise.
131
+
132
+ ## Self-check enforcement
133
+
134
+ `plugin/skills/self-check/run.ps1` D12 (v3.7.8 / retuned v3.7.9 / extended v3.8.0) verifies that each `pull-*/SKILL.md` cites `m365-id-registry.instructions.md` at least once and contains source-specific contract tokens. For `pull-onenote` v2.6.0 the required tokens are: `m365-id-registry`, `one_pages`, `webPageId`, `auth-required`, `playwright-profile` — these prove the skill carries the dual-ID retry registry + browser-primary contract.
@@ -0,0 +1,176 @@
1
+ ---
2
+ applyTo: "**"
3
+ description: "HARD rule — meetings are an EXPIRING evidence class. Every meeting MUST have a sibling verbatim/ folder containing the raw chat thread + transcript text + recording URL. Curated 7-section snapshot is NOT a substitute; it cannot be rebuilt once the recording/transcript is purged by tenant retention."
4
+ ---
5
+
6
+ # Meetings verbatim is REQUIRED (HARD RULE, v3.10.0)
7
+
8
+ Meetings differ from every other evidence class in one critical way: **the source expires.**
9
+
10
+ - Teams recordings are deleted by tenant retention (default 60 days for many tenants; sometimes shorter).
11
+ - VTT transcripts are tied to the recording and disappear with it.
12
+ - Copilot recap cards persist only as long as the meeting object does.
13
+ - Attendee memory degrades to zero within weeks.
14
+
15
+ Email bodies, OneNote pages, SharePoint files, CRM records, and ADO work items all persist in their source systems indefinitely (or for years). Meetings do not. If the curated 7-section meeting summary is the only artifact kushi captures, and the recording later expires, **the evidence is unrecoverable**. This has happened in production (see `learnings/meetings.md` — FDE Intake John Deere, 2026-05-18).
16
+
17
+ ## The rule
18
+
19
+ For every meeting captured by `pull-meetings`, the skill MUST produce **two** outputs, not one:
20
+
21
+ 1. **Curated snapshot** — `Evidence/<alias>/Meetings/snapshot/<slug>.md` (existing) or `Evidence/<alias>/Meetings/stream/<week>_meetings-stream.md` per-meeting block (existing). 7-section doctrine summary. Human-readable.
22
+ 2. **Verbatim folder** — `Evidence/<alias>/Meetings/verbatim/<YYYY-MM-DD-HHMM>_<slug>/` (NEW, REQUIRED). Raw immutable artifacts that survive source expiry.
23
+
24
+ A per-meeting block in `stream/` that has NO sibling `verbatim/<...>/` folder is a **defect**, even if the curated block is rich and well-cited. Self-check rule **D13** enforces this in deep mode.
25
+
26
+ ## "Verbatim" means the FULL TRANSCRIPT TEXT — not just chat (HARD RULE)
27
+
28
+ The defining artifact of a meeting verbatim folder is **the full speaker-by-speaker transcript text of what was said in the meeting**. Chat messages are SUPPORTING evidence; they are **NOT** a substitute for the transcript.
29
+
30
+ Order of preference for the transcript artifact (highest fidelity first):
31
+
32
+ 1. **Raw VTT** (`transcript.vtt`) — verbatim, timestamped, per-speaker turns. Captured via `m365_get_transcript(joinUrl)` or the Graph `/me/onlineMeetings/{id}/transcripts/{tid}/content` endpoint with `Accept: text/vtt`. **Required first attempt.**
33
+ 2. **Plain-text transcript** (`transcript.txt`) — full speaker-by-speaker text without VTT formatting. Allowed only when raw VTT cannot be retrieved but a plain text dump is available (e.g. WorkIQ returns the full text but not the VTT bytes). Must include speaker names + at least timestamp markers.
34
+ 3. **Copilot recap summary** (`transcript-source.md`) — Copilot-generated SUMMARY returned by WorkIQ when both VTT and plain transcript are unavailable. Must carry the `WARNING: NOT a verbatim transcript` header. This is the LOWEST acceptable transcript-class artifact and only counts when nothing higher exists.
35
+ 4. **Chat reconstruction** (`chat-messages.json` + `chat-messages.md`) — the chat thread alone. **This is NOT a transcript.** A verbatim folder whose only "transcript" is chat reconstruction is marked `transcript-missing` in `coverage.md` and the meeting block in `stream/` carries `Source basis: ❌ no-transcript-recovered-chat-only`.
36
+
37
+ A verbatim folder is **transcript-complete** only when one of files 1, 2, or 3 exists with non-empty content. A folder with only chat is `transcript-missing` — a degraded state, not the goal. Self-check D13 distinguishes these two outcomes.
38
+
39
+ ## Transcript capture cascade — WorkIQ-only (HARD RULE, v3.11.0+)
40
+
41
+ Per `workiq-only.instructions.md`: kushi does NOT use `m365_get_transcript`, `m365_get_facilitator_notes`, `m365_list_meetings`, `m365_list_events`, or Graph REST for transcripts. They fail almost every call in this workspace. The capture cascade is short and codified:
42
+
43
+ 1. **WorkIQ — full transcript pull (REQUIRED first attempt)**:
44
+ ```
45
+ workiq ask -q "Find the Teams meeting titled \"<subject>\" that occurred on <YYYY-MM-DD>. Return the full transcript verbatim with speaker labels and timestamps. Do not summarize."
46
+ ```
47
+ - If output contains speaker turns (≥ ~10 distinct `Name:` lines) → strip the `request-id:` prefix lines, save body as `transcript.txt` with the standard header (source, query, request-id, fidelity).
48
+ - If output is a summary (paragraphs only, no speaker turns) → run the **doubled-strict retry** from `workiq-only.instructions.md`. If still summary-only, save as `transcript-source.md` with the `WARNING: NOT a verbatim transcript` header.
49
+
50
+ 2. **WorkIQ — Copilot recap (SUPPLEMENTARY, always attempt)**:
51
+ ```
52
+ workiq ask -q "Get the Copilot meeting recap (decisions, action items, key points) for the Teams meeting \"<subject>\" on <YYYY-MM-DD>. Return the FULL recap card verbatim. Do not summarize."
53
+ ```
54
+ - Save as `facilitator-notes.md`. This is supplementary; does NOT replace the transcript.
55
+
56
+ 3. **Chat structured dump (ALWAYS in parallel, allowed)**:
57
+ - `m365_list_chat_messages(chatId)` → `chat-messages.json` raw + `chat-messages.md` rendered.
58
+ - Chat-id is sourced from `boundaries.teams.chat_ids[]` in integrations.yml, OR from the WorkIQ meeting-discovery query if not pre-known:
59
+ ```
60
+ workiq ask -q "List my Teams meetings between <start> and <end> where the subject contains \"<token>\". Return subject, date, organizer, joinUrl, and Teams chat id."
61
+ ```
62
+ - **Allowed exception to workiq-only**: `m365_list_chat_messages` is a structured-data dump for the chat-id'd thread. It is NOT a transcript and NOT a fallback for one. It runs in parallel.
63
+
64
+ 4. **Recording URL discovery (WorkIQ)**:
65
+ ```
66
+ workiq ask -q "For the meeting \"<subject>\" on <YYYY-MM-DD>, return the SharePoint Stream recording URL if it exists, and the calendar event body."
67
+ ```
68
+ - Save URL(s) to `recording-url.txt`. If a `.mp4` URL is present and `m365_download_file` against the SharePoint item succeeds (size < 200MB), download to `recording.mp4` so a future transcription pass remains possible after the cloud copy expires.
69
+
70
+ 5. **Ask user to paste** (when 1 returns empty AND 2 returns empty):
71
+ - Prompt user to paste the transcript. Save to `transcript.txt` with header `User-pasted; WorkIQ returned no transcript on <ISO timestamp>`. This is a first-class evidence path, not a degradation.
72
+
73
+ **Forbidden paths** (do NOT attempt; do NOT log as cascade steps in coverage.md):
74
+ - `m365_get_transcript(joinUrl)` — FORBIDDEN. Use WorkIQ step 1.
75
+ - `m365_get_facilitator_notes(joinUrl)` — FORBIDDEN. Use WorkIQ step 2.
76
+ - `m365_list_meetings`, `m365_list_events` — FORBIDDEN for discovery. Use WorkIQ chat-id discovery in step 3.
77
+ - Graph REST `/me/onlineMeetings/.../transcripts/.../content` — FORBIDDEN. Use WorkIQ step 1.
78
+
79
+ If steps 1–2 both return empty AND user-paste in step 5 is declined: the meeting is **`transcript-unrecoverable`**. The verbatim folder still exists with chat (if any) + `coverage.md` documenting every WorkIQ attempt with request-ids. The stream/ block carries `Source basis: ❌ no-transcript-recovered-chat-only` and the run-log records `transcript-unrecoverable` as the source-level error signature.
80
+
81
+ ## Required contents of `verbatim/<YYYY-MM-DD-HHMM>_<slug>/`
82
+
83
+ Slug = lowercase ASCII, spaces→hyphens, max 60 chars, derived from meeting subject. Time = local 24h.
84
+
85
+ | File | When required | Source |
86
+ |---|---|---|
87
+ | `captured-at.txt` | Always | ISO timestamps + every path attempted (success + failures) + kushi version + REST endpoints used |
88
+ | `coverage.md` | Always | What was captured, what was attempted, what failed, source-basis classification |
89
+ | `transcript.vtt` | When `m365_get_transcript` or Graph REST returned VTT bytes | Raw VTT — highest fidelity; required first attempt |
90
+ | `transcript.txt` | When VTT unavailable but plain text retrieved (WorkIQ full-text or user paste) | Speaker-by-speaker plain text; second preference |
91
+ | `transcript-source.md` | When only a Copilot summary is available | WorkIQ markdown verbatim with `WARNING: NOT a verbatim transcript` header; lowest acceptable transcript-class artifact |
92
+ | `facilitator-notes.md` | When `m365_get_facilitator_notes` returned content | Copilot AI decisions + action items; supplementary, NOT a transcript |
93
+ | `chat-messages.json` | Always when meeting has a chat thread | Full `m365_list_chat_messages` raw API dump |
94
+ | `chat-messages.md` | Always when chat-messages.json exists | Human-readable rendering: sender + ISO timestamp + full body per message |
95
+ | `recording-url.txt` | When any recording URL is found in chat or event body | One URL per line; SharePoint/Stream URLs |
96
+ | `recording.mp4` | When `recording-url.txt` resolves and size < 200MB | `m365_download_file` of the recording binary; last-chance source for re-transcription after cloud expiry |
97
+ | `recap-card.md` | When a Copilot recap card was posted to chat | Verbatim card body |
98
+ | `attachments/` | When chat included shared files | `m365_download_file` of every attachment referenced by chat messages |
99
+
100
+ **Transcript-class file required.** A verbatim folder MUST contain at least ONE of `transcript.vtt` / `transcript.txt` / `transcript-source.md` to be classified `transcript-complete`. A folder with only `chat-messages.*` (no transcript-class file) is `transcript-missing` — the meeting is flagged in coverage.md and run-log as `transcript-unrecoverable` and the corresponding stream/ block carries `Source basis: ❌ no-transcript-recovered-chat-only`. Self-check D13 enforces both presence and classification.
101
+
102
+ ## Capture-on-pull execution order
103
+
104
+ Every step writes to `verbatim/` BEFORE producing the curated snapshot. The order is locked and is now WorkIQ-only for transcript capture (no `m365_get_transcript`, no Graph REST):
105
+
106
+ 1. Create `verbatim/<YYYY-MM-DD-HHMM>_<slug>/` and write `captured-at.txt` (started).
107
+ 2. **Transcript cascade — WorkIQ-only** (steps 1–5 of the cascade in "Transcript capture cascade — WorkIQ-only" above, in order — do not stop early):
108
+ - a. WorkIQ full-transcript pull → `transcript.txt` if speaker-turns returned, else `transcript-source.md` (with `WARNING: NOT a verbatim transcript` header) after the doubled-strict retry.
109
+ - b. WorkIQ Copilot recap → `facilitator-notes.md` (supplementary; not a transcript).
110
+ - c. WorkIQ recording-URL discovery → `recording-url.txt`; if `.mp4` URL present and size < 200MB, `m365_download_file` against the SharePoint item id → `recording.mp4` (binary download carve-out — WorkIQ does not return binaries).
111
+ - d. User-paste fallback (first-class) when (a) returns empty after doubled-strict retry → `transcript.txt` with `User-pasted; WorkIQ returned no transcript on <ISO>` header.
112
+ 3. **Chat capture (always, in parallel with step 2 — allowed structured-data dump)**: `m365_list_chat_messages(chatId)` → `chat-messages.json` + `chat-messages.md`. If it fails, run the WorkIQ Teams chat-thread prompt as fallback.
113
+ 4. Walk chat for attachments → `m365_download_file` each → `attachments/<original-name>` (binary download carve-out).
114
+ 5. Walk chat for Copilot recap card → `recap-card.md`.
115
+ 6. Classify the verbatim folder per "transcript-class file required" rule above:
116
+ - `transcript-complete` if any of transcript.vtt / transcript.txt / transcript-source.md is non-empty.
117
+ - `transcript-missing` otherwise.
118
+ 7. Write `coverage.md` with classification + per-path WorkIQ attempt log (every WorkIQ path attempted, with request-id).
119
+ 8. Update `captured-at.txt` with `completed_at` + `final_status`.
120
+ 9. **Only now** produce the curated snapshot/stream block, citing verbatim files relatively: `[source: Evidence/<alias>/meetings/verbatim/<dir>/transcript.txt · <date>]` (preferred) or `transcript-source.md` (with warning) or `chat-messages.md` if transcript-missing.
121
+
122
+ If steps 2(a-d) all return empty AND chat in step 3 is also empty: meeting is `unrecoverable`. Mark the per-meeting block `❌ source-expired-or-unrecoverable`, write `coverage.md` listing every WorkIQ attempt with request-ids, and ask the user to paste from memory/notes (paste lands in `transcript.txt` with the user-paste header).
123
+
124
+ **Note**: `transcript.vtt` is no longer produced because the only paths that yielded raw VTT (`m365_get_transcript` and Graph REST `/transcripts/.../content`) are FORBIDDEN per `workiq-only.instructions.md`. Existing pre-v3.12.0 `transcript.vtt` files in verbatim/ folders remain valid evidence and continue to satisfy the transcript-complete classification — they just won't be created by new runs.
125
+
126
+ ## Why a folder per meeting (not a flat file)
127
+
128
+ - Multiple artifact kinds per meeting (chat + transcript + recap + attachments) — a folder is the natural shape.
129
+ - Attachments are binary; they don't belong inline in a markdown file.
130
+ - Future capture additions (e.g. screen-share thumbnails, whiteboard exports) shouldn't break the layout.
131
+ - Easy to dedupe by directory existence on subsequent runs.
132
+
133
+ ## Naming
134
+
135
+ - Folder: `<YYYY-MM-DD>-<HHMM>_<slug>` — e.g. `2026-05-13-1530_fde-intake-john-deere`
136
+ - Slug rules: lowercase, ASCII, spaces and punctuation → single hyphen, collapse repeats, trim trailing hyphens, max 60 chars.
137
+ - Time: meeting start time in the user's local timezone (per `m365_get_mailbox_settings`), 24h, no separator.
138
+ - If two meetings share start time + slug: append `-2`, `-3`, etc.
139
+
140
+ ## Citation convention
141
+
142
+ Inside the curated snapshot/stream files, cite verbatim files with the kushi standard format:
143
+
144
+ ```
145
+ [source: Evidence/<alias>/meetings/verbatim/2026-05-13-1530_fde-intake-john-deere/chat-messages.md · 2026-05-13]
146
+ ```
147
+
148
+ The curated snapshot is the assertion; the verbatim file is the evidence. The two must always travel together.
149
+
150
+ ## What this is NOT
151
+
152
+ - This is NOT a backup of OneNote, email, or SharePoint. Those sources persist; we cite them in place.
153
+ - This is NOT a place for AI-generated summaries. `transcript-source.md` from WorkIQ is allowed because WorkIQ returns it as the only available form of the transcript; everything in verbatim/ must be source-derived, not Kushi-derived.
154
+ - This is NOT optional based on meeting size. A 5-minute side meeting with 3 chat messages still gets a verbatim folder.
155
+
156
+ ## Self-check enforcement (D13)
157
+
158
+ `plugin/skills/self-check/run.ps1` deep-mode rule D13 walks every per-meeting block in `Evidence/*/meetings/stream/*.md` and emits:
159
+
160
+ - **D13.a** — verbatim/<dir> missing entirely for a referenced meeting (HARD defect).
161
+ - **D13.b** — verbatim/<dir> exists but contains only `captured-at.txt` / `coverage.md` (cascade failed silently; HARD defect).
162
+ - **D13.c** — verbatim/<dir> contains chat-messages.* but no transcript-class file (`transcript.vtt`, `transcript.txt`, or `transcript-source.md`). Surfaces as `transcript-missing` warning. Not a hard defect if `coverage.md` documents the exhaustive cascade was attempted; IS a hard defect if any transcript path was skipped.
163
+
164
+ ## Anti-patterns (defects)
165
+
166
+ 1. **Verbatim folder absent** — per-meeting block in stream/ with no sibling verbatim/<...>/ dir. Hard defect.
167
+ 2. **Verbatim folder empty / captured-at-only** — capture cascade failed silently. Hard defect; surface in coverage.md and run-log.
168
+ 3. **Chat-only treated as transcript** — verbatim/ contains only chat-messages.* but coverage.md claims `transcript-complete`. Hard defect (misclassification of source basis).
169
+ 4. **Transcript cascade stopped early** — first path (m365_get_transcript) failed and skill jumped straight to chat without trying Graph REST fallback + WorkIQ strict pull + recording URL discovery. Hard defect — the cascade is exhaustive, not first-success.
170
+ 5. **Inline transcript in stream/** — pasting raw VTT into the stream file instead of writing to verbatim/transcript.vtt. Defect; bloats the curated file and obscures the audit trail.
171
+ 6. **Curated-only refresh** — refresh-project re-runs pull-meetings without re-checking that the verbatim folder is still intact (in case of accidental cleanup). The orchestrator should warn if verbatim/ for an existing meeting disappeared between runs.
172
+ 7. **AI-narrative substitution** — when no transcript and no chat exist, writing an inferred narrative as `transcript-source.md`. Forbidden; per `verbatim-by-default.instructions.md` anti-pattern #8.
173
+
174
+ ## Apply
175
+
176
+ `pull-meetings/SKILL.md` v2.2.0+ MUST cite this instruction in its front contracts blockquote. Templates `meetings-stream.template.md` and `meetings-series-index.template.md` MUST reference the verbatim/ folder location. Template `meeting-verbatim.template.md` defines the standard contents of a verbatim folder.
@@ -0,0 +1,129 @@
1
+ ---
2
+ applyTo: "**"
3
+ description: "Run-reports rule — every bootstrap and refresh MUST write a per-user, per-run report under <project>/Evidence/<alias>/refresh-reports/ so each contributor can see what was done when, what worked, what didn't, and what changed."
4
+ ---
5
+
6
+ # Run-reports (HARD RULE)
7
+
8
+ Every `bootstrap-project` and `refresh-project` invocation MUST produce a per-user, per-run report file in addition to updating `<project>/bootstrap-status.md` and `Evidence/run-log.yml`.
9
+
10
+ ## Why
11
+
12
+ `bootstrap-status.md` is the **current durable state** (per bootstrap-status-format.instructions.md — short, scannable, no run narrative).
13
+
14
+ `run-log.yml` is **structured history** (machine-readable, watermarks + per-source status).
15
+
16
+ Neither tells a user "what just happened in my last run" in narrative form. The per-user run report fills that gap. It's the contributor's audit trail of **what the agent did on their behalf** — what was pulled, what worked, what was skipped, what gaps remain, and what to do next.
17
+
18
+ Critical for multi-user projects: each contributor's runs are independent, and each user needs to see their own report without scrolling through everyone else's history.
19
+
20
+ ## Where it lives
21
+
22
+ ```
23
+ <engagement-root>/<project>/Evidence/<alias>/refresh-reports/
24
+ YYYY-MM-DD-HHmm_<mode>.md
25
+ ```
26
+
27
+ - `<alias>` — the contributor running the skill (from `~/.copilot/project-evidence.yml#alias`).
28
+ - `<mode>` — one of `bootstrap`, `refresh`, `force-refresh`, `consolidate`.
29
+ - Timestamp uses the **start time** of the run, in local time, format `YYYY-MM-DD-HHmm` (no seconds, no timezone — local-time prefix is enough for chronological sort).
30
+
31
+ Newest at the bottom of `Get-ChildItem` listings (sortable by name = sortable by time).
32
+
33
+ ## Required structure
34
+
35
+ ```markdown
36
+ # Refresh Report — <project> — <YYYY-MM-DD HH:mm local>
37
+
38
+ - **Mode**: bootstrap | refresh | force-refresh | consolidate
39
+ - **Contributor**: <alias>
40
+ - **Window**: <effective window per source>
41
+ - **Profile**: standard | full | core
42
+ - **Started**: <ISO timestamp>
43
+ - **Ended**: <ISO timestamp>
44
+ - **Duration**: <Hh Mm Ss>
45
+
46
+ ## What was done
47
+
48
+ | Source | Action | Items pulled | Outcome | Notes |
49
+ |---|---|---|---|---|
50
+ | ado | snapshot+stream | 2 WIs / 6 comments / 24 revisions | resolved | ... |
51
+ | crm | snapshot | 1 record / 24 annotations | resolved | ... |
52
+ | email | stream | 47 messages / 12 threads | populated | ... |
53
+ | teams | stream | ... | ... | ... |
54
+ | meetings | snapshot+stream | ... | ... | ... |
55
+ | onenote | snapshot | ... | ... | ... |
56
+ | sharepoint | snapshot+stream | ... | ... | ... |
57
+
58
+ ## Resolutions this run
59
+
60
+ For each ID/folder/section newly resolved during this run, one bullet:
61
+ - `ado.engagement_id` resolved to `96944` (HCA - Engagement) — pinned to integrations.yml + m365-mutable.json.
62
+ - `crm.record_id` resolved to `e561b31e-...` — pinned + env override applied (iscrm.crm.dynamics.com).
63
+
64
+ ## Cleanups this run
65
+
66
+ Per `cleanup-on-resolution.instructions.md`. List entries pruned when a resolution succeeded:
67
+ - Removed stale `ado.last_discovery_result: 'no-match'` from integrations.yml (resolved this run).
68
+ - Removed `## Current Bootstrap Outcome` note `ADO context sync pending` from bootstrap-status.md.
69
+
70
+ ## Learnings appended
71
+
72
+ Per `capture-learnings.instructions.md`. List new entries written to `<KUSHI_ROOT>/plugin/learnings/<file>.md`:
73
+ - `learnings/crm.md` — "Custom entities don't expose Annotations as a navigation property" (HCA / pull-crm).
74
+ - `learnings/ado.md` — "$top=500 exceeds permissible range on workItems updates endpoint" (HCA / pull-ado).
75
+
76
+ ## Skips & gaps
77
+
78
+ What was deliberately skipped or couldn't be retrieved, with reason:
79
+ - `sharepoint` — no folder pinned in `integrations.yml#boundaries.sharepoint.folder_paths`; root-fallback would be too noisy. ACTION: ask user to pin folder, or accept root-scope on next run.
80
+ - `meetings` transcripts — 2 of 5 meetings had no transcript; chat-reconstruction used.
81
+
82
+ ## Files written
83
+
84
+ | Path | Bytes | Type |
85
+ |---|---|---|
86
+ | `Evidence/<alias>/ado/snapshot/engagement-96944.md` | 18,420 | snapshot |
87
+ | `Evidence/<alias>/ado/snapshot/items/96571.md` | 4,210 | snapshot |
88
+ | `Evidence/<alias>/crm/snapshot/.../e561b31e-...md` | 24,180 | snapshot |
89
+ | `Evidence/<alias>/email/stream/2026-05-04_email-stream.md` | 38,910 | stream |
90
+ | ... | | |
91
+
92
+ ## Next steps for this contributor
93
+
94
+ Plain-language list. What the contributor should do next, e.g.:
95
+ - Review `Evidence/<alias>/email/stream/2026-05-04_email-stream.md` — 3 emails from James Gibbings have substantive customer needs that should land in `State/00_overview.md`.
96
+ - Pin a SharePoint folder for HCA in `integrations.yml#boundaries.sharepoint.folder_paths` so next refresh covers it.
97
+ - Re-run with `force-refresh HCA` if today's run looks partial.
98
+
99
+ ---
100
+
101
+ _Generated by kushi <version>. See `<KUSHI_ROOT>/plugin/instructions/run-reports.instructions.md` for the format spec._
102
+ ```
103
+
104
+ ## When sections are empty
105
+
106
+ Same rule as evidence-thoroughness: write `_None this run._` rather than omit a section. Empty section signals "checked, none" vs missing section signals "skipped".
107
+
108
+ ## Relationship to other artifacts
109
+
110
+ | Artifact | What it captures | Lifecycle |
111
+ |---|---|---|
112
+ | `<project>/bootstrap-status.md` | Current durable project state | Overwritten each bootstrap/refresh; short |
113
+ | `<project>/Evidence/run-log.yml` | Structured per-source history (watermarks, item counts, runs[]) | Append-only history block + replaceable per-source summary |
114
+ | `<project>/Evidence/<alias>/refresh-reports/<ts>_<mode>.md` | Narrative per-user per-run report | Append-only (one new file per run); never overwritten |
115
+ | `<KUSHI_ROOT>/plugin/learnings/<source>.md` | Cross-project doctrine (API quirks, fixes) | Append-only, kushi-repo-wide |
116
+
117
+ The four are complementary; do not collapse them.
118
+
119
+ ## When the report is generated
120
+
121
+ - `bootstrap-project` — at the END of the run, after all per-source pulls have completed (success or with coverage gaps). Write the file before announcing "bootstrap complete" to the user.
122
+ - `refresh-project` — same, at the END. Even if no source had new data ("no new evidence — state unchanged"), write a 1-section report saying so. The user needs to see the run happened.
123
+ - `force-refresh` (per `force-refresh.md` how-to) — same, with `Mode: force-refresh` so the report distinguishes deliberate re-pulls from incremental refreshes.
124
+
125
+ ## Self-check enforcement
126
+
127
+ `plugin/skills/self-check/run.ps1` deep-mode rule **D10** (added v3.7.6):
128
+ - Warns if `<project>/Evidence/<alias>/` exists but `refresh-reports/` is missing or empty after a run.
129
+ - Warns if `run-log.yml` shows a `runs[]` entry from today but no matching `refresh-reports/<today's-prefix>_*.md` exists.