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,271 @@
1
+ #!/usr/bin/env node
2
+ // pull-onenote/write-snapshot.mjs — kushi v3.11.5
3
+ //
4
+ // Translates runner.mjs JSON output into the canonical snapshot layout per
5
+ // snapshot-vs-stream.instructions.md and pull-onenote/SKILL.md §"Snapshot file shape":
6
+ //
7
+ // <engagement-root>/<project>/Evidence/<alias>/onenote/
8
+ // snapshot/pages/<safe-title>.md — one file per page (HARD rule)
9
+ // refresh-reports/<YYYY-MM-DD>-<HHMM>-onenote.md
10
+ //
11
+ // Also upserts the per-page retry registry into m365-mutable.json#knownSections.<project>.one_pages[].
12
+ //
13
+ // Stream events (page-edit events) are NOT written here — those come from WorkIQ on the next
14
+ // stream pass. This wrapper only handles snapshot + registry + run report.
15
+ //
16
+ // Usage (preferred — runs the runner internally, avoids shell pipe UTF-8 corruption):
17
+ // node plugin/skills/pull-onenote/write-snapshot.mjs \
18
+ // --section-url "<url>" \
19
+ // --project "<project>" \
20
+ // --engagement-root "<engagement-root>" \
21
+ // --alias "<alias>" \
22
+ // [--mutable <path>] [--timeout 120000]
23
+ //
24
+ // Usage (legacy — read pre-captured runner JSON):
25
+ // node plugin/skills/pull-onenote/write-snapshot.mjs \
26
+ // --json <path-to-runner-output.json> \
27
+ // --project "<project>" \
28
+ // --engagement-root "<engagement-root>" \
29
+ // --alias "<alias>" \
30
+ // [--mutable <path-to-m365-mutable.json>]
31
+ //
32
+ // Idempotency: snapshot/pages/*.md is replace-in-place (HARD per snapshot-vs-stream doctrine).
33
+ // Registry merge: preserves prior attempts count, advances last_status if newer is captured.
34
+
35
+ import fs from 'node:fs';
36
+ import path from 'node:path';
37
+ import process from 'node:process';
38
+ import { spawnSync } from 'node:child_process';
39
+
40
+ const args = Object.fromEntries(
41
+ process.argv.slice(2).reduce((acc, cur, idx, arr) => {
42
+ if (cur.startsWith('--')) {
43
+ const key = cur.replace(/^--/, '');
44
+ const next = arr[idx + 1];
45
+ acc.push([key, next && !next.startsWith('--') ? next : true]);
46
+ }
47
+ return acc;
48
+ }, [])
49
+ );
50
+
51
+ const JSON_PATH = args.json;
52
+ const SECTION_URL = args['section-url'];
53
+ const TIMEOUT_MS = args.timeout || '120000';
54
+ const PROJECT = args.project;
55
+ const ROOT = args['engagement-root'];
56
+ const ALIAS = args.alias || 'ushak';
57
+ const MUTABLE_PATH = args.mutable || path.join(ROOT || '.', '.project-evidence', 'm365', 'm365-mutable.json');
58
+
59
+ if ((!JSON_PATH && !SECTION_URL) || !PROJECT || !ROOT) {
60
+ console.error('Usage: (--json <runner-output> | --section-url <url>) --project <name> --engagement-root <root> [--alias ushak] [--mutable <path>] [--timeout 120000]');
61
+ process.exit(2);
62
+ }
63
+
64
+ // --- helpers ---
65
+ function safeTitle(title) {
66
+ return title
67
+ .replace(/[\\/:*?"<>|]/g, '-')
68
+ .replace(/\s+/g, '-')
69
+ .replace(/-+/g, '-')
70
+ .replace(/^-|-$/g, '')
71
+ .slice(0, 120);
72
+ }
73
+
74
+ function nowUtc() {
75
+ return new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
76
+ }
77
+
78
+ function isoWeekMonday(d = new Date()) {
79
+ const x = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
80
+ const day = x.getUTCDay() || 7;
81
+ x.setUTCDate(x.getUTCDate() - day + 1);
82
+ return x.toISOString().slice(0, 10);
83
+ }
84
+
85
+ function mkdirp(p) { fs.mkdirSync(p, { recursive: true }); }
86
+
87
+ function readJson(p) {
88
+ const raw = fs.readFileSync(p, 'utf8');
89
+ // strip non-JSON header lines (e.g. "Docs: ...") emitted by some wrappers
90
+ const i = raw.indexOf('{');
91
+ return JSON.parse(raw.slice(i));
92
+ }
93
+
94
+ function writeJson(p, obj) {
95
+ fs.writeFileSync(p, JSON.stringify(obj, null, 2), 'utf8');
96
+ }
97
+
98
+ function renderPageMd(p, ctx) {
99
+ const fm = {
100
+ page_title: p.title,
101
+ section: ctx.sectionName,
102
+ notebook: ctx.notebookName,
103
+ section_id: ctx.sectionFileId,
104
+ wdpartid: p.wdpartid || null,
105
+ webPageId: p.webPageId || null,
106
+ last_modified: p.lastModified || null,
107
+ last_status: p.last_status,
108
+ captured_via: p.captured_via || 'browser',
109
+ attempts: p.attempts || 1,
110
+ last_attempt: p.capturedAt || nowUtc(),
111
+ captured_at: p.capturedAt || nowUtc()
112
+ };
113
+ const fmYaml = Object.entries(fm)
114
+ .map(([k, v]) => `${k.padEnd(13)}: ${v === null ? 'null' : JSON.stringify(v)}`)
115
+ .join('\n');
116
+
117
+ let body;
118
+ if (p.last_status === 'captured' || p.last_status === 'user-pasted') {
119
+ body = [
120
+ '## AI Narrative Summary',
121
+ '',
122
+ '_(Generated by build-state on next aggregation; verbatim body below is authoritative.)_',
123
+ '',
124
+ '## Body (verbatim)',
125
+ '',
126
+ '```',
127
+ p.body || '',
128
+ '```',
129
+ '',
130
+ `[source: ${PROJECT}/onenote/${ctx.sectionName}/${p.title} · ${(p.lastModified || p.capturedAt || '').slice(0, 10) || 'unknown'}]`
131
+ ].join('\n');
132
+ } else if (p.last_status === 'enumeration-only') {
133
+ body = `## ⏳ Enumeration-only\n\nPage discovered but body not yet captured. Will be attempted on next refresh.`;
134
+ } else {
135
+ body = [
136
+ `## ⛔ Unavailable — ${p.last_status}`,
137
+ '',
138
+ `Last attempt: ${p.capturedAt || nowUtc()}`,
139
+ '',
140
+ '### next_step',
141
+ '',
142
+ p.last_status === 'auth-required'
143
+ ? '1. Re-bootstrap the Playwright profile: `node plugin/skills/pull-onenote/runner.mjs --bootstrap`\n2. Re-run refresh.'
144
+ : p.last_status === 'notebook-unavailable'
145
+ ? '1. Open the notebook in OneNote desktop, force sync.\n2. Verify the section opens at onenote.cloud.microsoft.\n3. Re-run refresh.'
146
+ : '1. Re-run refresh on next cycle.'
147
+ ].join('\n');
148
+ }
149
+
150
+ return `---\n${fmYaml}\n---\n\n# ${p.title}\n\n${body}\n`;
151
+ }
152
+
153
+ // --- main ---
154
+ let runOut;
155
+ if (SECTION_URL) {
156
+ // Invoke runner.mjs directly via child_process to keep UTF-8 intact
157
+ // (PowerShell's Out-File default mangles non-ASCII chars like NBSP).
158
+ const runnerPath = path.join(path.dirname(new URL(import.meta.url).pathname.replace(/^\//, '')), 'runner.mjs');
159
+ const r = spawnSync(process.execPath, [
160
+ runnerPath,
161
+ '--project', PROJECT,
162
+ '--section-url', SECTION_URL,
163
+ '--skip-preflight',
164
+ '--headless',
165
+ '--timeout', String(TIMEOUT_MS)
166
+ ], { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
167
+ if (r.status !== 0) {
168
+ console.error('[write-snapshot] runner failed:', r.stderr);
169
+ process.exit(r.status || 1);
170
+ }
171
+ const i = r.stdout.indexOf('{');
172
+ runOut = JSON.parse(r.stdout.slice(i));
173
+ } else {
174
+ runOut = readJson(JSON_PATH);
175
+ }
176
+ const pages = runOut.pages || [];
177
+ const sectionUrl = runOut.sectionUrl || SECTION_URL || '';
178
+
179
+ // Resolve section/notebook context from m365-mutable.json#knownSections.<project>
180
+ const mutable = fs.existsSync(MUTABLE_PATH) ? JSON.parse(fs.readFileSync(MUTABLE_PATH, 'utf8')) : { m365Mutable: { knownSections: {} } };
181
+ const ks = mutable.m365Mutable?.knownSections?.[PROJECT] || {};
182
+ const ctx = {
183
+ sectionName: ks.one_sectionName || 'unknown.one',
184
+ notebookName: ks.one_notebookName || 'unknown',
185
+ sectionFileId: ks.one_sectionFileId || null
186
+ };
187
+
188
+ // Paths
189
+ const evDir = path.join(ROOT, PROJECT, 'Evidence', ALIAS, 'onenote');
190
+ const pagesDir = path.join(evDir, 'snapshot', 'pages');
191
+ const reportsDir = path.join(evDir, 'refresh-reports');
192
+ mkdirp(pagesDir);
193
+ mkdirp(reportsDir);
194
+
195
+ // Write page snapshots
196
+ const written = [];
197
+ for (const p of pages) {
198
+ const fname = `${safeTitle(p.title)}.md`;
199
+ const fpath = path.join(pagesDir, fname);
200
+ const md = renderPageMd(p, ctx);
201
+ fs.writeFileSync(fpath, md, 'utf8');
202
+ written.push({ title: p.title, file: path.relative(ROOT, fpath), status: p.last_status, bytes: md.length });
203
+ }
204
+
205
+ // Upsert registry m365Mutable.knownSections.<project>.one_pages[]
206
+ const priorPages = Array.isArray(ks.one_pages) ? ks.one_pages : [];
207
+ const byKey = new Map(priorPages.map(x => [x.webPageId || x.wdpartid || x.title, x]));
208
+ for (const p of pages) {
209
+ const key = p.webPageId || p.wdpartid || p.title;
210
+ const prior = byKey.get(key) || {};
211
+ byKey.set(key, {
212
+ title: p.title,
213
+ wdpartid: p.wdpartid || prior.wdpartid || null,
214
+ webPageId: p.webPageId || prior.webPageId || null,
215
+ lastModified: p.lastModified || prior.lastModified || null,
216
+ last_status: p.last_status,
217
+ captured_via: p.captured_via || 'browser',
218
+ attempts: (prior.attempts || 0) + 1,
219
+ last_attempt_at: p.capturedAt || nowUtc(),
220
+ snapshot_path: `${PROJECT}/Evidence/${ALIAS}/onenote/snapshot/pages/${safeTitle(p.title)}.md`,
221
+ captured_at: p.last_status === 'captured' ? (p.capturedAt || nowUtc()) : (prior.captured_at || null)
222
+ });
223
+ }
224
+ mutable.m365Mutable.knownSections[PROJECT] = {
225
+ ...ks,
226
+ one_pages: Array.from(byKey.values()),
227
+ one_lastPullAt: nowUtc(),
228
+ one_lastPullRunStatus: runOut.runStatus,
229
+ one_lastPullKushiVersion: '3.11.5'
230
+ };
231
+ writeJson(MUTABLE_PATH, mutable);
232
+
233
+ // Run report
234
+ const nowStamp = nowUtc().replace(/[-:]/g, '').replace('T', '-').slice(0, 13);
235
+ const reportPath = path.join(reportsDir, `${nowStamp}-onenote.md`);
236
+ const counts = pages.reduce((acc, p) => { acc[p.last_status] = (acc[p.last_status] || 0) + 1; return acc; }, {});
237
+ const report = [
238
+ `# pull-onenote run report — ${PROJECT}`,
239
+ '',
240
+ `- **timestamp**: ${nowUtc()}`,
241
+ `- **alias**: ${ALIAS}`,
242
+ `- **section**: ${ctx.sectionName} (notebook: ${ctx.notebookName})`,
243
+ `- **section URL**: ${sectionUrl}`,
244
+ `- **runStatus**: ${runOut.runStatus}`,
245
+ `- **kushi runner**: v3.11.4+`,
246
+ '',
247
+ '## Per-status counts',
248
+ '',
249
+ ...Object.entries(counts).map(([s, n]) => `- ${s}: ${n}`),
250
+ '',
251
+ '## Pages written',
252
+ '',
253
+ ...written.map(w => `- \`${w.file}\` — ${w.status} (${w.bytes} bytes)`),
254
+ '',
255
+ '## Registry',
256
+ '',
257
+ `Upserted ${pages.length} entries in \`m365Mutable.knownSections."${PROJECT}".one_pages[]\`.`,
258
+ ''
259
+ ].join('\n');
260
+ fs.writeFileSync(reportPath, report, 'utf8');
261
+
262
+ console.log(JSON.stringify({
263
+ project: PROJECT,
264
+ alias: ALIAS,
265
+ pages_written: written.length,
266
+ pages_dir: path.relative(ROOT, pagesDir),
267
+ registry_path: MUTABLE_PATH,
268
+ registry_entries: pages.length,
269
+ report_path: path.relative(ROOT, reportPath),
270
+ counts
271
+ }, null, 2));
@@ -1,17 +1,27 @@
1
1
  ---
2
2
  name: "pull-sharepoint"
3
- version: "2.0.0"
4
- description: "Pull SharePoint evidence (snapshot: folder tree + key file bodies; stream: file change events). WorkIQ-first; also walks local synced folder for metadata without auth."
3
+ version: "2.2.1"
4
+ description: "Pull SharePoint evidence (snapshot: folder tree + key file bodies; stream: file change events). Local synced folder walk for metadata (always allowed — no auth, no API). WorkIQ-ONLY for content/text extraction per workiq-only.instructions.md. `m365_search_files` / `m365_download_file` / Graph REST FORBIDDEN."
5
5
  ---
6
6
 
7
7
  # Skill: pull-sharepoint
8
8
 
9
+
10
+ > **v3.7.6 + v3.11.0 contracts** — This skill operates under five HARD-rule doctrines:
11
+ > - `verbatim-by-default.instructions.md` — full bodies/notetext/fields by default; no preview-grade pulls accepted.
12
+ > - **`workiq-only.instructions.md` (v3.11.0)** — text-content extraction from SharePoint files goes through WorkIQ ONLY. `m365_search_files`, `m365_download_file`, `m365_list_files`, and Graph REST `https://graph.microsoft.com/v1.0/me/drive/*` are FORBIDDEN as content-extraction fallbacks. Local-synced folder walk for metadata (filename, mtime, size) is ALWAYS allowed because it's a filesystem read, not an M365 API call. The canonical WorkIQ prompt for SharePoint file content is codified in that instruction — do not re-discover.
13
+ > - `capture-learnings.instructions.md` — every fix/discovery is logged to `plugin/learnings/<source>.md` immediately.
14
+ > - `cleanup-on-resolution.instructions.md` — when a value resolves, all stale `no-match` / `not yet` notes referencing the prior unresolved state must be rewritten in the same turn.
15
+ > - `run-reports.instructions.md` — every refresh writes a per-user report under `Evidence/<alias>/refresh-reports/YYYY-MM-DD-HHMM_refresh.md`.
16
+
17
+ > **Canonical evidence layout** (HARD, kushi v3.12.1+): all artifacts produced by this skill MUST be written under `<project>/Evidence/<alias>/<source>/{snapshot,stream,...}/` — sibling folders under `<project>/` (e.g. `<project>/<source>-context/`, `<project>/<source>/`, `<project>/_Weekly Summaries/`) are FORBIDDEN. See `evidence-layout-canonical.instructions.md`.
18
+
9
19
  Pulls **sharepoint** evidence in two shapes per `snapshot-vs-stream.instructions.md`:
10
20
 
11
21
  - **snapshot/** — tree.md (full folder tree) + files/<path>.md (key files with full extracted content)
12
22
  - **stream/** — file change events (created/modified/renamed/deleted) with author + size + brief content summary
13
23
 
14
- WorkIQ-first per `workiq-first.instructions.md`. Thoroughness per `evidence-thoroughness.instructions.md`; runtime detector + auto-retry + paste-prompt per `thoroughness-detector.instructions.md`. Citations per `citation-ledger.instructions.md`. Side-by-side mutable hints written immediately on discovery per `side-by-side-config.instructions.md`.
24
+ Thoroughness per `evidence-thoroughness.instructions.md`; runtime detector + auto-retry + paste-prompt per `thoroughness-detector.instructions.md`. Citations per `citation-ledger.instructions.md`. Side-by-side mutable hints written immediately on discovery per `side-by-side-config.instructions.md`.
15
25
 
16
26
  ## Inputs
17
27
 
@@ -27,9 +37,26 @@ WorkIQ-first per `workiq-first.instructions.md`. Thoroughness per `evidence-thor
27
37
  - `sharePoint.siteUrl` — SP site URL
28
38
  - `sharePoint.driveId` — Graph drive ID for direct access
29
39
 
30
- ## Snapshot pass
40
+ ## Boundaries (REQUIRED — see `scope-boundaries.instructions.md`)
41
+
42
+ This skill REFUSES to query unless `<engagement-root>/<project>/integrations.yml#boundaries.sharepoint` is satisfied:
43
+
44
+ - `boundaries.sharepoint.local_folders` — REQUIRED, non-empty (full local OneDrive-synced paths).
45
+ - `boundaries.sharepoint.site_urls` — optional online-only scope.
46
+ - `boundaries.sharepoint.drive_ids` — optional Graph-direct access.
47
+ - `boundaries.date_window_days` — defaults to 30 if absent.
48
+
49
+ The walk is **strictly inclusive** — only paths/sites/drives in the boundary are walked. There is no auto-discovery of additional folders. `path_includes`/`path_excludes` from `.settings.yml` further narrow inside the boundary; they cannot widen it.
31
50
 
32
- Write `snapshot/tree.md` (full folder hierarchy, no truncation) and `snapshot/files/<relative-path>.md` for each file selected for body extraction. The selected set is the **union** of:
51
+ Refusal message when boundary is missing:
52
+
53
+ ```
54
+ sharepoint-boundary-missing — add boundaries.sharepoint.local_folders to
55
+ <engagement-root>/<project>/integrations.yml. See doctrine in
56
+ plugin/instructions/scope-boundaries.instructions.md.
57
+ ```
58
+
59
+ ## Snapshot pass and `snapshot/files/<relative-path>.md` for each file selected for body extraction. The selected set is the **union** of:
33
60
 
34
61
  1. **Curated key files** — anything matching `architecture`, `decision`, `adr`, `deliverable`, `proposal`, `statement of work`, `sow`, `business case`, `roadmap`, `plan` in the filename or top-level folder name. These are always extracted.
35
62
  2. **Top-N most-recently-modified files** under the project's SharePoint folder, where N = `sharepoint.snapshot.recent_n` from the user's `.settings.yml` (default **25**, configurable to 0 to disable). This captures the live working tail without exploding on large team sites.
@@ -53,14 +80,15 @@ Use template: `templates/weekly/sharepoint-summary.template.md`
53
80
 
54
81
  If a week file already exists, MERGE (dedupe by event ID, append new events, keep existing).
55
82
 
56
- ## Tools (in order)
83
+ ## Tools (WorkIQ ONLY — per `workiq-only.instructions.md`)
84
+
85
+ 1. **WorkIQ (`workiq ask`)** — the ONLY allowed path for SharePoint file text/content extraction (snapshot bodies and stream change summaries). Use the canonical prompt codified in `plugin/instructions/workiq-only.instructions.md` (SharePoint file content section) — do NOT re-derive.
86
+ 2. **Local synced folder walk (filesystem only — ALWAYS allowed)** — for metadata only (filename, size, mtime, author from filesystem attributes, change-type via filesystem events). This is a `Get-ChildItem` against `boundaries.sharepoint.local_folders`; it is NOT an M365 API call and therefore not governed by workiq-only. Use it as the discovery layer; pipe interesting file paths into WorkIQ for content extraction.
87
+ 3. **Ask user (paste verbatim)** — first-class fallback when WorkIQ doubled-strict retry fails or returns insufficient content for a specific file (e.g. very large doc, encrypted PDF). NOT a degradation; coverage.md labels it `Source: User paste on <ISO>`.
57
88
 
58
- 1. **WorkIQ** ````$WorkIQQuery`````
59
- 2. **Host fallback** — `m365_search_files` / `m365_list_files`; `helpers/sharepoint-pull.ps1` (uses `az account get-access-token --resource https://graph.microsoft.com`); also walk local synced folder under `sharePoint.localFolder` for metadata (no auth needed)
60
- 3. **Graph REST** — last resort, soft-fail per `az-auth-conditional.instructions.md`.
61
- 4. **Ask user** — paste verbatim source content if all above fail.
89
+ **FORBIDDEN** for this skill (per workiq-only.instructions.md): `m365_search_files`, `m365_list_files`, `m365_get_recent_files`, `m365_download_file` (for content extraction; downloading recording.mp4 in pull-meetings is a separate carve-out), Graph REST `https://graph.microsoft.com/v1.0/me/drive/*`, Graph REST `shares/{id}/driveItem`, SharePoint REST `_api/web/GetFileByServerRelativePath` (for content). These have a near-100% failure rate or produce metadata-only stubs. Calling them is a DEFECT; coverage.md must NOT show them in the attempt trail.
62
90
 
63
- Document which path succeeded under `## Source Basis` in each output file.
91
+ Document the WorkIQ request-id + the exact prompt + which folder/site was queried under `## Source Basis` in each output file.
64
92
 
65
93
  ## Mutable hints to upsert (during the run, not at the end)
66
94
 
@@ -78,8 +106,12 @@ After successful pass:
78
106
  - `sources.sharepoint.item_count = <N>`
79
107
  - For each week touched, add to `weekly_files` index.
80
108
 
109
+ ## Depth bar (per `evidence-thoroughness.instructions.md`)
110
+
111
+ Every snapshot file body starts with an **AI Narrative Summary (REQUIRED FIRST, 3+ paragraphs)** covering what the file is, what it argues / proposes / records, key positions, audience, why it matters — then the body summary at meeting-transcript depth (decisions, risks, actions, owners, dates, key context). Stream events also lead with a short AI Narrative Summary of the change.
112
+
81
113
  ## Stop conditions
82
114
 
83
115
  - Hint missing AND fuzzy resolution returns 0 candidates → ask user once, persist answer to mutable, continue.
84
116
  - Multiple plausible candidates → ask user to pick, persist answer.
85
- - WorkIQ fails AND host fallback fails AND Graph fails → write evidence file with `❌ all paths failed` marker, log to run-log errors, continue with rest of run.
117
+ - WorkIQ fails (after doubled-strict retry) AND user declines to paste → write evidence file with `❌ workiq-empty-after-retry` marker, log to run-log errors with `next_step: re-run after WorkIQ recovery or paste verbatim`, continue with rest of run. Do NOT fall through to Graph / `m365_*` — FORBIDDEN per workiq-only.
@@ -1,17 +1,27 @@
1
1
  ---
2
2
  name: "pull-teams"
3
- version: "2.0.0"
4
- description: "Pull Teams chat + channel evidence (snapshot: chat roster; stream: messages with full message-by-message reproduction). WorkIQ-first."
3
+ version: "2.2.1"
4
+ description: "Pull Teams chat + channel evidence (snapshot: chat roster; stream: messages with full message-by-message reproduction). WorkIQ-ONLY for human-readable thread rendering per workiq-only.instructions.md. `m365_list_chat_messages` is the ONLY allowed host tool — used as parallel structured-data dump (chat-messages.json), NEVER a substitute for the WorkIQ human-readable pull. Graph REST / `m365_list_chats` / `m365_search_chats` FORBIDDEN."
5
5
  ---
6
6
 
7
7
  # Skill: pull-teams
8
8
 
9
+
10
+ > **v3.7.6 + v3.11.0 contracts** — This skill operates under five HARD-rule doctrines:
11
+ > - `verbatim-by-default.instructions.md` — full bodies/notetext/fields by default; no preview-grade pulls accepted.
12
+ > - **`workiq-only.instructions.md` (v3.11.0)** — chat-thread human rendering goes through WorkIQ ONLY. Graph REST URLs (`/me/chats/*/messages`, etc.) are FORBIDDEN. `m365_list_chat_messages` is ALLOWED as a parallel structured-data dump (writes `chat-messages.json`) — it is NOT a fallback for the WorkIQ human-readable thread pull. The canonical WorkIQ prompt for chat threads is codified in that instruction — do not re-discover.
13
+ > - `capture-learnings.instructions.md` — every fix/discovery is logged to `plugin/learnings/<source>.md` immediately.
14
+ > - `cleanup-on-resolution.instructions.md` — when a value resolves, all stale `no-match` / `not yet` notes referencing the prior unresolved state must be rewritten in the same turn.
15
+ > - `run-reports.instructions.md` — every refresh writes a per-user report under `Evidence/<alias>/refresh-reports/YYYY-MM-DD-HHMM_refresh.md`.
16
+
17
+ > **Canonical evidence layout** (HARD, kushi v3.12.1+): all artifacts produced by this skill MUST be written under `<project>/Evidence/<alias>/<source>/{snapshot,stream,...}/` — sibling folders under `<project>/` (e.g. `<project>/<source>-context/`, `<project>/<source>/`, `<project>/_Weekly Summaries/`) are FORBIDDEN. See `evidence-layout-canonical.instructions.md`.
18
+
9
19
  Pulls **teams** evidence in two shapes per `snapshot-vs-stream.instructions.md`:
10
20
 
11
21
  - **snapshot/** — chat roster + member list per chat the user is part of for this project
12
22
  - **stream/** — messages with full message-by-message reproduction — sender, timestamp, verbatim text, reactions, attachments, replies — grouped by thread
13
23
 
14
- WorkIQ-first per `workiq-first.instructions.md`. Thoroughness per `evidence-thoroughness.instructions.md`; runtime detector + auto-retry + paste-prompt per `thoroughness-detector.instructions.md`. Citations per `citation-ledger.instructions.md`. Side-by-side mutable hints written immediately on discovery per `side-by-side-config.instructions.md`.
24
+ Thoroughness per `evidence-thoroughness.instructions.md`; runtime detector + auto-retry + paste-prompt per `thoroughness-detector.instructions.md`. Citations per `citation-ledger.instructions.md`. Side-by-side mutable hints written immediately on discovery per `side-by-side-config.instructions.md`.
15
25
 
16
26
  ## Inputs
17
27
 
@@ -36,7 +46,7 @@ Use template: `templates/snapshot/teams-<kind>.template.md`
36
46
 
37
47
  ## Stream pass
38
48
 
39
- Per `evidence-thoroughness.instructions.md`: every thread touched in window gets full message-by-message reproduction. Group by thread, not by day. If a thread has 30 messages, reproduce all 30.
49
+ Per `evidence-thoroughness.instructions.md`: every thread touched in window starts with an **AI Narrative Summary (REQUIRED FIRST, 3+ paragraphs)** covering what the thread is about, the conversation arc, positions taken, sentiment, what landed and what stayed open — then full message-by-message reproduction. Group by thread, not by day. If a thread has 30 messages, reproduce all 30.
40
50
 
41
51
  Write to: `<engagement-root>/<project>/Evidence/<alias>/teams/stream/<YYYY-MM-DD>_teams-stream.md` (date = Monday of the ISO week the events fall in).
42
52
 
@@ -44,14 +54,33 @@ Use template: `templates/weekly/teams-summary.template.md`
44
54
 
45
55
  If a week file already exists, MERGE (dedupe by event ID, append new events, keep existing).
46
56
 
47
- ## Tools (in order)
57
+ ## Boundaries (REQUIRED — see `scope-boundaries.instructions.md`)
58
+
59
+ This skill REFUSES to query unless `<engagement-root>/<project>/integrations.yml#boundaries.teams` is satisfied:
60
+
61
+ - `boundaries.teams.chat_ids` — REQUIRED, non-empty (pinned chat IDs; no fuzzy discovery beyond this list).
62
+ - `boundaries.teams.channel_ids` — REQUIRED for any channel evidence (empty = no channel pulls).
63
+ - `boundaries.date_window_days` — defaults to 30 if absent.
64
+
65
+ There is **no fuzzy chat discovery** in v3.7.0+. The pre-existing `chatHints` / `participantHints` are now treated as **discovery aids during bootstrap only** — they help the user populate `boundaries.teams.chat_ids` once, and after that they are ignored at pull time.
66
+
67
+ Refusal message when boundary is missing:
68
+
69
+ ```
70
+ teams-boundary-missing — add boundaries.teams.chat_ids to
71
+ <engagement-root>/<project>/integrations.yml. See doctrine in
72
+ plugin/instructions/scope-boundaries.instructions.md.
73
+ ```
74
+
75
+ ## Tools (WorkIQ ONLY — per `workiq-only.instructions.md`)
76
+
77
+ 1. **WorkIQ (`workiq ask`)** — the ONLY allowed path for the human-readable thread reproduction (stream pass) and chat-roster enumeration (snapshot pass). Always cite the chat_ids in the prompt. Use the canonical prompts codified in `plugin/instructions/workiq-only.instructions.md` — do NOT re-derive them.
78
+ 2. **`m365_list_chat_messages` (PARALLEL structured-data dump ONLY — allowed exception)** — runs in parallel with the WorkIQ pull to write the raw JSON dump `chat-messages.json` for each chat-id in `boundaries.teams.chat_ids[]`. It is **NOT** a fallback for WorkIQ; the WorkIQ human-readable thread is still REQUIRED. If `m365_list_chat_messages` fails, the WorkIQ artifact still stands and the JSON dump is simply skipped (recorded in coverage.md).
79
+ 3. **Ask user (paste verbatim)** — first-class fallback when WorkIQ doubled-strict retry fails or returns insufficient content. NOT a degradation; coverage.md labels it `Source: User paste on <ISO>`.
48
80
 
49
- 1. **WorkIQ** — ````$WorkIQQuery`````
50
- 2. **Host fallback** — `m365_list_chats` + `m365_list_chat_messages` scoped by `chatIds` from mutable
51
- 3. **Graph REST** — last resort, soft-fail per `az-auth-conditional.instructions.md`.
52
- 4. **Ask user** — paste verbatim source content if all above fail.
81
+ **FORBIDDEN** for this skill (per workiq-only.instructions.md): `m365_list_chats` (chat-roster discovery use WorkIQ), `m365_search_chats`, Graph REST `https://graph.microsoft.com/v1.0/me/chats*`, Graph REST `https://graph.microsoft.com/v1.0/teams/*/channels/*/messages`. These have a near-100% failure rate in this workspace. Calling them is a DEFECT; coverage.md must NOT show them in the attempt trail.
53
82
 
54
- Document which path succeeded under `## Source Basis` in each output file.
83
+ Document the WorkIQ request-id + the exact prompt + which chat_ids were queried under `## Source Basis` in each output file (per workiq-only coverage.md requirements).
55
84
 
56
85
  ## Mutable hints to upsert (during the run, not at the end)
57
86
 
@@ -72,4 +101,4 @@ After successful pass:
72
101
 
73
102
  - Hint missing AND fuzzy resolution returns 0 candidates → ask user once, persist answer to mutable, continue.
74
103
  - Multiple plausible candidates → ask user to pick, persist answer.
75
- - WorkIQ fails AND host fallback fails AND Graph fails → write evidence file with `❌ all paths failed` marker, log to run-log errors, continue with rest of run.
104
+ - WorkIQ fails (after doubled-strict retry) AND user declines to paste → write evidence file with `❌ workiq-empty-after-retry` marker, log to run-log errors with `next_step: re-run after WorkIQ recovery or paste verbatim`, continue with rest of run. Do NOT fall through to Graph / `m365_*` — FORBIDDEN per workiq-only.
@@ -1,11 +1,23 @@
1
1
  ---
2
2
  name: "refresh-project"
3
- version: "2.1.0"
4
- description: "Incremental refresh for an already-bootstrapped project. Reads run-log watermarks, pulls only what is new since last run per source, splits stream output by ISO week, replaces snapshots in place. Builds State/ only on `full` profile (skipped on `standard`). Defaults to since-watermark (fallback 7d if first refresh)."
3
+ version: "2.3.1"
4
+ description: "Incremental refresh for an already-bootstrapped project. Reads run-log watermarks, pulls only what is new since last run per source, splits stream output by ISO week, replaces snapshots in place. Verbatim-by-default per `verbatim-by-default.instructions.md` every enabled source MUST be dispatched, no silent skips. Writes per-user refresh report per `run-reports.instructions.md`. Cleans stale no-match notes on resolution per `cleanup-on-resolution.instructions.md`. Builds State/ only on `full` profile."
5
5
  ---
6
6
 
7
7
  # Skill: refresh-project
8
8
 
9
+ > **Verbatim-by-default**: This orchestrator MUST dispatch every enabled `pull-<source>` skill whose boundary in `integrations.yml#boundaries.<source>` is satisfied. Skipping a source silently is a defect. See `verbatim-by-default.instructions.md`.
10
+ >
11
+ > **Discover once, consume deterministically**: Refresh MUST read canonical M365 IDs from `<engagement-root>/.project-evidence/m365/m365-mutable.json#knownSections.<projectKey>` and pass them verbatim into each `pull-*`'s index-extractor query. **Refresh never re-discovers.** If the project entry is missing or a per-source key is empty, re-dispatch through `bootstrap-project` for that source's discovery only, then resume. See `m365-id-registry.instructions.md`.
12
+ >
13
+ > **Per-user refresh report REQUIRED**: At end of run, write `<project>/Evidence/<alias>/refresh-reports/<YYYY-MM-DD-HHmm>_refresh.md` per `run-reports.instructions.md`. Even on a no-op run. Cite the registry: `Resolved IDs sourced from m365-mutable.json#knownSections.<projectKey>`.
14
+ >
15
+ > **Cleanup on resolution**: When this run resolves a previously-unknown ID/folder/section, prune the stale `no-match` / probe-trail notes from integrations.yml, m365-mutable.json, run-log, and bootstrap-status.md per `cleanup-on-resolution.instructions.md`. UPSERT the newly-resolved ID into `m365-mutable.json#knownSections.<projectKey>` mid-run.
16
+ >
17
+ > **Capture learnings**: Any quirk / fix / workaround discovered mid-run is appended to `<KUSHI_ROOT>/plugin/learnings/<source>.md` in the same turn per `capture-learnings.instructions.md`.
18
+ >
19
+ > **Canonical evidence layout** (HARD, kushi v3.12.1+): every per-source artifact lands under `<project>/Evidence/<alias>/<source>/{snapshot,stream,...}/` and **nowhere else** under `<project>/`. Before per-source dispatch, refresh MUST scan `<project>/` for non-canonical source-output folders (e.g. `<project>/email-context/`, `<project>/notes/`, `<project>/_Weekly Summaries/`) and migrate each to `Evidence/<alias>/<source>/_legacy_<original-name>_pre-bootstrap/` (move verbatim; log under `## Layout migrations` in the refresh report; add `runs:` entry with `type: layout-migration`). Do NOT delete. See `evidence-layout-canonical.instructions.md`.
20
+
9
21
  Run this when the user says any of: "refresh `<X>`", "do it all for `<X>`" (when project already bootstrapped), "weekly extract for `<X>`", "update `<X>`", "pull `<X>` since `<date>`".
10
22
 
11
23
  ## Profile-aware behavior
@@ -42,12 +54,31 @@ Profile is read from `kushi-install.json#profile` next to the agent file. Defaul
42
54
 
43
55
  For each enabled source (or just the requested one), call its `pull-<source>` skill with the effective window.
44
56
 
57
+ **OneNote pre-dispatch gate (kushi v3.10.0+, HARD):** Before invoking `pull-onenote`, run the browser-URL completeness check:
58
+ ```pwsh
59
+ node plugin/skills/pull-onenote/scripts/recapture-section-url.mjs --project <name> --engagement-root <engagement-root> --check
60
+ ```
61
+ If exit 1 (`incomplete`), dispatch the script interactively (no `--check`) so the user pastes the section URL once. Persist + proceed. This is a one-time self-heal of pre-doctrine registry entries — see `pull-onenote/SKILL.md` Pre-flight C and `m365-id-registry.instructions.md` § "Browser-fields self-heal exception". This is NOT re-discovery (which is forbidden); it is a backfill of fields that were never captured, cached forever after the first paste.
62
+
63
+ Per-source pull skills (canonical order — refresh-project dispatches in this order, but each is independent):
64
+
65
+ 1. `pull-email`
66
+ 2. `pull-teams`
67
+ 3. `pull-meetings`
68
+ 4. `pull-onenote`
69
+ 5. `pull-sharepoint`
70
+ 6. `pull-crm` (if enabled)
71
+ 7. `pull-ado` (if enabled)
72
+ 8. `pull-misc` (if `<project>/external-links.txt` exists)
73
+
45
74
  Each `pull-*` skill is responsible for:
46
75
  - Snapshot pass (always re-fetch known entities).
47
76
  - Stream pass (incremental from watermark, bucketed by ISO week, append-only merge).
48
77
  - Updating `sources.<src>.watermark`, `sources.<src>.last_pulled`, `sources.<src>.item_count` in run-log.
49
78
  - Updating `weekly_files` index in run-log.
50
79
 
80
+ **pull-misc dispatch:** invoke as `node plugin/skills/pull-misc/runner.mjs --project <name> --links-file <engagement-root>/<project>/external-links.txt --engagement-root <engagement-root> --headless`. Parse the JSON output, write per-link snapshot files to `Evidence/<alias>/misc/snapshot/<safe-type>__<safe-title>.md`, and merge the `links[]` records into `m365-mutable.json#knownSections.<projectKey>.misc_links[]` (preserving prior `attempts`, bumping `last_attempt_at`).
81
+
51
82
  ### Step 3 — Consolidate (if multi-user)
52
83
 
53
84
  If `Evidence/contributors.yml` lists more than one alias and any of them have new evidence in the window, dispatch `consolidate-evidence` for the same window.