kushi-agents 4.4.3 → 4.7.4
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.
- package/README.md +4 -0
- package/package.json +4 -4
- package/plugin/agents/kushi.agent.md +30 -14
- package/plugin/config/studios.json +37 -0
- package/plugin/config/studios.schema.json +45 -0
- package/plugin/instructions/auth-and-retry.instructions.md +268 -1
- package/plugin/instructions/engagement-root-resolution.instructions.md +5 -1
- package/plugin/instructions/evidence-thoroughness.instructions.md +103 -1
- package/plugin/instructions/fuzzy-disambiguation.instructions.md +97 -0
- package/plugin/instructions/identity-resolution.instructions.md +76 -0
- package/plugin/instructions/issue-recovery.instructions.md +58 -0
- package/plugin/instructions/kushi-config-root.instructions.md +66 -0
- package/plugin/instructions/loop-bootstrap-discovery.instructions.md +105 -0
- package/plugin/instructions/m365-id-registry.instructions.md +1 -1
- package/plugin/instructions/onedrive-pin-policy.instructions.md +132 -0
- package/plugin/instructions/per-source-verification-gate.instructions.md +193 -0
- package/plugin/instructions/sharepoint-to-onedrive-sync.instructions.md +116 -0
- package/plugin/instructions/status-color-rule.instructions.md +62 -0
- package/plugin/instructions/studio-registry.instructions.md +48 -0
- package/plugin/instructions/update-ledger.instructions.md +1 -1
- package/plugin/instructions/verbatim-by-default.instructions.md +1 -1
- package/plugin/instructions/vertex-emit.instructions.md +120 -0
- package/plugin/instructions/workiq-input-sanitization.instructions.md +43 -0
- package/plugin/instructions/workiq-onenote-query-shape.instructions.md +79 -0
- package/plugin/instructions/workiq-only.instructions.md +15 -9
- package/plugin/learnings/loop.md +11 -0
- package/plugin/learnings/onenote.md +27 -1
- package/plugin/lib/Get-KushiConfig.ps1 +22 -9
- package/plugin/lib/detect-vertex-repo.mjs +96 -0
- package/plugin/lib/render-vertex.mjs +249 -0
- package/plugin/lib/sanitize-workiq-input.mjs +72 -0
- package/plugin/lib/studio-registry.mjs +39 -0
- package/plugin/lib/vertex-validate.mjs +121 -0
- package/plugin/plugin.json +16 -6
- package/plugin/prompts/bootstrap.prompt.md +9 -7
- package/plugin/prompts/emit-vertex.prompt.md +33 -0
- package/plugin/prompts/setup.prompt.md +29 -0
- package/plugin/prompts/vertex-link.prompt.md +27 -0
- package/plugin/skills/aggregate-project/SKILL.md +24 -2
- package/plugin/skills/apply-ado-update/SKILL.md +9 -4
- package/plugin/skills/ask-project/SKILL.md +4 -0
- package/plugin/skills/bootstrap-project/SKILL.md +67 -41
- package/plugin/skills/consolidate-evidence/SKILL.md +5 -1
- package/plugin/skills/emit-vertex/README.md +37 -0
- package/plugin/skills/emit-vertex/SKILL.md +173 -0
- package/plugin/skills/intro/SKILL.md +2 -0
- package/plugin/skills/propose-ado-update/SKILL.md +8 -3
- package/plugin/skills/pull-ado/SKILL.md +11 -1
- package/plugin/skills/pull-crm/SKILL.md +12 -2
- package/plugin/skills/pull-email/SKILL.md +11 -1
- package/plugin/skills/pull-loop/README.md +64 -0
- package/plugin/skills/pull-loop/SKILL.md +180 -0
- package/plugin/skills/pull-loop/runner.mjs +261 -0
- package/plugin/skills/pull-loop/write-snapshot.mjs +181 -0
- package/plugin/skills/pull-meetings/SKILL.md +11 -1
- package/plugin/skills/pull-misc/README.md +4 -4
- package/plugin/skills/pull-misc/SKILL.md +18 -12
- package/plugin/skills/pull-onenote/SKILL.md +71 -19
- package/plugin/skills/pull-sharepoint/SKILL.md +11 -2
- package/plugin/skills/pull-teams/SKILL.md +11 -2
- package/plugin/skills/refresh-project/SKILL.md +38 -7
- package/plugin/skills/self-check/SKILL.md +14 -1
- package/plugin/skills/self-check/run.ps1 +442 -20
- package/plugin/skills/setup/SKILL.md +377 -0
- package/plugin/skills/vertex-link/SKILL.md +143 -0
- package/plugin/templates/init/m365-auth.template.json +10 -4
- package/plugin/templates/init/project-evidence.template.yml +10 -3
- package/plugin/templates/init/project-integrations.template.yml +5 -0
- package/plugin/templates/snapshot/ado-item.template.md +1 -1
- package/plugin/templates/snapshot/crm-record.template.md +1 -1
- package/plugin/templates/snapshot/meetings-series-index.template.md +1 -1
- package/plugin/templates/snapshot/onenote-page.template.md +1 -1
- package/plugin/templates/snapshot/sharepoint-file.template.md +1 -1
- package/plugin/templates/snapshot/sharepoint-tree.template.md +1 -1
- package/plugin/templates/snapshot/teams-roster.template.md +1 -1
- package/plugin/templates/weekly/ado-stream.template.md +1 -1
- package/plugin/templates/weekly/crm-stream.template.md +1 -1
- package/plugin/templates/weekly/email-stream.template.md +1 -1
- package/plugin/templates/weekly/meetings-stream.template.md +1 -1
- package/plugin/templates/weekly/onenote-stream.template.md +1 -1
- package/plugin/templates/weekly/sharepoint-stream.template.md +1 -1
- package/plugin/templates/weekly/teams-stream.template.md +1 -1
- package/src/check-workiq.mjs +109 -15
- package/src/config-loader.mjs +71 -13
- package/src/config-root-resolve.test.mjs +137 -0
- package/src/detect-vertex-repo.test.mjs +128 -0
- package/src/emit-vertex.e2e.test.mjs +308 -0
- package/src/forbidden-workiq-phrasings.test.mjs +111 -0
- package/src/main.mjs +11 -2
- package/src/sanitize-workiq-input.test.mjs +45 -0
- package/src/vertex-validate.test.mjs +142 -0
- package/plugin/instructions/az-auth-conditional.instructions.md +0 -39
- package/plugin/instructions/azure-auth-patterns.instructions.md +0 -233
- package/plugin/instructions/thoroughness-detector.instructions.md +0 -105
- package/plugin/instructions/workiq-first.instructions.md +0 -31
|
@@ -129,4 +129,106 @@ Before writing any per-entity block (meeting / thread / email / page / file / re
|
|
|
129
129
|
- [ ] **Next Steps** section is present (distinct from Action Items)
|
|
130
130
|
- [ ] All other structured sub-sections (Decisions / Action Items / Risks / Open Questions / Customer Asks) come AFTER, not instead of
|
|
131
131
|
- [ ] Verbatim transcript / message reproduction / page body is still present (the AI summary does not replace it)
|
|
132
|
-
- [ ] Empty sections explicitly say `_None surfaced._`, never omitted
|
|
132
|
+
- [ ] Empty sections explicitly say `_None surfaced._`, never omitted
|
|
133
|
+
|
|
134
|
+
<!-- merged from evidence-thoroughness.instructions.md (v4.4.9) -->
|
|
135
|
+
## Detection algorithm
|
|
136
|
+
|
|
137
|
+
Every `pull-*` skill MUST run this check after writing each evidence file. A file that fails the detector is a **defect** — it MUST be re-extracted with a richer query or replaced with pasted source content. The skill MUST NOT silently accept a thin file.
|
|
138
|
+
|
|
139
|
+
This detector enforces `evidence-thoroughness.instructions.md` at runtime. The thoroughness doctrine defines what "full" means; this file defines how to **detect** and **fix** violations.
|
|
140
|
+
|
|
141
|
+
## Per-source minimum thresholds
|
|
142
|
+
|
|
143
|
+
Apply the threshold for the source the file belongs to. A file fails if **any** check triggers.
|
|
144
|
+
|
|
145
|
+
### Email stream (`email/stream/*.md`)
|
|
146
|
+
|
|
147
|
+
- Per thread block: body text ≥ **400 characters** (not counting headers / metadata / "see original" stubs).
|
|
148
|
+
- Per thread block contains at least one of: `Body:`, `Reply chain`, or a quoted block.
|
|
149
|
+
- File-level: ≥ 1 thread block per substantive subject mentioned in the windowed search results.
|
|
150
|
+
|
|
151
|
+
### Teams stream (`teams/stream/*.md`)
|
|
152
|
+
|
|
153
|
+
- Per thread block: reproduces ≥ **5 messages** (or, if thread is shorter, ALL messages — collapse only routine acks 👍 / "thanks").
|
|
154
|
+
- Each reproduced message has `<sender> · <timestamp>` line + verbatim or close-paraphrase text.
|
|
155
|
+
- File-level: rejects "summary-only" files with no message-by-message reproduction.
|
|
156
|
+
|
|
157
|
+
### OneNote snapshot (`onenote/snapshot/pages/*.md`)
|
|
158
|
+
|
|
159
|
+
- Page body text ≥ **600 characters** OR contains a `## Page Body` section followed by content (not just a link).
|
|
160
|
+
- Page metadata block (last-modified, author) present.
|
|
161
|
+
|
|
162
|
+
### OneNote stream (`onenote/stream/*.md`)
|
|
163
|
+
|
|
164
|
+
- Each page-edit event includes diff summary (what changed) — not just "page updated".
|
|
165
|
+
|
|
166
|
+
### SharePoint snapshot tree (`sharepoint/snapshot/tree.md`)
|
|
167
|
+
|
|
168
|
+
- Tree is not truncated (no "…" or "N more" placeholders).
|
|
169
|
+
- Each entry has a status tag: `[key]` / `[recent]` / `[pin]` / `[skip]` / `[tree-only]`.
|
|
170
|
+
|
|
171
|
+
### SharePoint snapshot file (`sharepoint/snapshot/files/*.md`)
|
|
172
|
+
|
|
173
|
+
- Body summary ≥ **400 characters** OR explicit `[tree-only]` justification.
|
|
174
|
+
- Has changed-by + change-type + last-modified.
|
|
175
|
+
|
|
176
|
+
### Meetings stream (`meetings/stream/*.md`)
|
|
177
|
+
|
|
178
|
+
- Per meeting block contains ALL of the following named sections:
|
|
179
|
+
- `## Detailed Discussion Summary`
|
|
180
|
+
- `## Transcript Walk-Through` (or `## Chronological Walk-Through`)
|
|
181
|
+
- `## Key Decisions`
|
|
182
|
+
- `## Open Questions` (or `## Open Questions / Non-Decisions`)
|
|
183
|
+
- `## Next Steps` ← **dedicated section, distinct from Action Items**
|
|
184
|
+
- `## Action Items`
|
|
185
|
+
- `## Next Steps` MUST be present even if empty (`_None this meeting._`) — missing the section is a defect.
|
|
186
|
+
- Per meeting block: total length ≥ **800 characters**.
|
|
187
|
+
- Action items: every bullet has owner + due (date OR "TBD" with reason) + `[source: …]` citation.
|
|
188
|
+
|
|
189
|
+
### CRM/ADO snapshot (`crm/snapshot/*.md`, `ado/snapshot/*.md`)
|
|
190
|
+
|
|
191
|
+
- Snapshot lists ≥ **8 fields** with values (not just an ID + a link).
|
|
192
|
+
|
|
193
|
+
### CRM/ADO stream (`crm/stream/*.md`, `ado/stream/*.md`)
|
|
194
|
+
|
|
195
|
+
- Each change event has old-value → new-value + actor + timestamp.
|
|
196
|
+
|
|
197
|
+
## Red-flag patterns (auto-fail)
|
|
198
|
+
|
|
199
|
+
Any file containing these strings outside a `## Coverage Notes` block fails:
|
|
200
|
+
|
|
201
|
+
- `(see original in …)` / `see original email` / `see Teams thread` / `see page` — link-only stubs.
|
|
202
|
+
- `Full body unavailable` / `body not captured` without an accompanying `## Coverage Notes` explaining why.
|
|
203
|
+
- `Summary: …` as the entire body (no detail beneath it).
|
|
204
|
+
- `[redacted]` without justification.
|
|
205
|
+
- `TODO` / `FIXME` left in production evidence.
|
|
206
|
+
|
|
207
|
+
## Retry policy
|
|
208
|
+
|
|
209
|
+
When a file fails the detector:
|
|
210
|
+
|
|
211
|
+
1. **Auto-retry once** with a deeper WorkIQ query. Append `"give me full bodies and full reply chain — not just metadata"` (or source-appropriate equivalent) to the query. Re-write the file.
|
|
212
|
+
2. If the second attempt still fails the detector, emit the **paste-prompt** (see `plugin/templates/paste-prompt.md`) to the user with the specific file path, the failing checks, and ready-to-paste section headers.
|
|
213
|
+
3. Mark the file with `⚠ thoroughness: pending-paste` at the top under `## Source Basis` and log `thoroughness-pending` in `run-log.yml` for that source. Continue the rest of the run — do NOT block.
|
|
214
|
+
|
|
215
|
+
## Validation block in every evidence file
|
|
216
|
+
|
|
217
|
+
Every evidence file MUST end with:
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
## Validation
|
|
221
|
+
|
|
222
|
+
- [ ] bodies-present
|
|
223
|
+
- [ ] attendees-or-participants-captured <!-- meetings/teams only -->
|
|
224
|
+
- [ ] next-steps-section-present <!-- meetings only -->
|
|
225
|
+
- [ ] action-items-have-owner-and-due <!-- meetings only -->
|
|
226
|
+
- [ ] no-link-only-stubs
|
|
227
|
+
- [ ] coverage-notes-explain-any-gaps
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The skill ticks the boxes it can verify automatically; the rest are left for `self-check` and human review.
|
|
231
|
+
|
|
232
|
+
## Out of scope
|
|
233
|
+
|
|
234
|
+
This detector does NOT enforce citation density (that's `citation-ledger.instructions.md`) or snapshot-vs-stream placement (that's `snapshot-vs-stream.instructions.md`). It enforces depth-of-capture only.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**"
|
|
3
|
+
description: "Universal fuzzy-match contract — whenever a kushi skill needs to map a user-given name (project, OneNote section, SharePoint site, Teams chat, ADO area, CRM record, mailbox folder, meeting topic) to a concrete identifier, it MUST use the same ranked-candidate flow with ask_user disambiguation on ambiguity. Eliminates inconsistent matching across sources."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Fuzzy Disambiguation (HARD RULE, v4.4.7+)
|
|
7
|
+
|
|
8
|
+
Every kushi skill that maps a **string the user typed** (or pulled from config) to a **concrete ID/path/record** in an external system goes through this exact flow. No per-source improvisation.
|
|
9
|
+
|
|
10
|
+
## Why this exists
|
|
11
|
+
|
|
12
|
+
Different sources used different matching heuristics: `engagement-root-resolution` did `exact > prefix > contains` and prompted on ambiguity; `pull-onenote` Step A did its own section-name fuzz; `pull-teams` matched chat topics manually; `pull-crm` accepted whichever Dataverse row came back first. Result: same project name resolved to different things across sources, and users were silently sent to the wrong artifact.
|
|
13
|
+
|
|
14
|
+
## The flow (applies to ALL sources)
|
|
15
|
+
|
|
16
|
+
1. **Candidate list.** Pull every plausible target from the source (notebooks under the user's account, chats in the last 60d, project folders under engagement-root, etc.). Cap at 50 candidates.
|
|
17
|
+
|
|
18
|
+
2. **Score and rank** with this fixed lexicographic order:
|
|
19
|
+
- **exact** (case-insensitive equality) — score 1000
|
|
20
|
+
- **prefix** (target startsWith query) — score 800 − (target.length − query.length)
|
|
21
|
+
- **contains** (target.includes query) — score 500 − distance-from-start
|
|
22
|
+
- **token-overlap** (query and target share ≥ 1 whitespace-split token, case-insensitive) — score 300 + matchedTokens × 10
|
|
23
|
+
- **levenshtein** (within 2 edits) — score 100 + (3 − distance) × 10
|
|
24
|
+
- else — score 0 (drop)
|
|
25
|
+
|
|
26
|
+
3. **Decision rule** (uses top 5 scored candidates):
|
|
27
|
+
| Situation | Action |
|
|
28
|
+
|---|---|
|
|
29
|
+
| Top candidate score ≥ 800 AND #2 score ≤ top × 0.7 | **Auto-pick top**. Echo: `✓ Resolved '<query>' → '<picked>' (<source-id>) [score=<n>, confidence=high]`. Continue. |
|
|
30
|
+
| Top candidate score ≥ 300 AND #2 score ≤ top × 0.7 AND in `--non-interactive` mode | **Auto-pick top with warning**. Echo: `⚠ Auto-picked '<query>' → '<picked>' (<source-id>) [score=<n>, confidence=medium]. Re-run interactively to confirm.` |
|
|
31
|
+
| Top ≥ 1 candidate AND interactive | **`ask_user` with options**. See "Disambiguation prompt shape" below. |
|
|
32
|
+
| Zero candidates above score 100 | **`ask_user` with type-in-an-id option**. After 1 failed retry → write `unresolved-<source>` to run-log + tracking; continue with the source-level coverage gap. |
|
|
33
|
+
|
|
34
|
+
4. **Persist the resolution** to the appropriate registry so the next run is direct:
|
|
35
|
+
- Project name → `m365-mutable.json#m365Mutable.knownSections.<project>` (per `m365-id-registry.instructions.md`)
|
|
36
|
+
- OneNote section/notebook → `m365-mutable.json#m365Mutable.knownSections.<project>.onenote.<section>`
|
|
37
|
+
- SharePoint site → `<project>/integrations.yml#sharepoint.siteId` + `siteName`
|
|
38
|
+
- Teams chat → `<alias>/.settings.yml#teams.chatIds[]`
|
|
39
|
+
- ADO area → `<project>/integrations.yml#ado.areaPath`
|
|
40
|
+
- CRM record → `<project>/integrations.yml#crm.recordId`
|
|
41
|
+
- Mailbox folder name → `<workspace>/.kushi/config/user/m365-auth.json#emailContext.folders[]` (already canonical names)
|
|
42
|
+
|
|
43
|
+
Persistence happens INSIDE the pull skill that resolved it, in the same run.
|
|
44
|
+
|
|
45
|
+
## Disambiguation prompt shape (ALWAYS the same)
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
🤔 Multiple matches for '<query>' in <source>:
|
|
49
|
+
|
|
50
|
+
1) <candidate-1-display-name> · <secondary-context> [score=<n>]
|
|
51
|
+
2) <candidate-2-display-name> · <secondary-context> [score=<n>]
|
|
52
|
+
3) <candidate-3-display-name> · <secondary-context> [score=<n>]
|
|
53
|
+
4) Type a different name / ID
|
|
54
|
+
5) Skip this source for now (write unresolved-<source> to run-log)
|
|
55
|
+
|
|
56
|
+
Which one matches '<query>'?
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Render via `ask_user` (preferred) when the host supports it. Fallback: chat-message prompt with the same shape.
|
|
60
|
+
|
|
61
|
+
- "Secondary context" is whatever disambiguates **the same display name appearing twice**: notebook owner, site URL host, ADO project, CRM account name, meeting organizer + date, etc.
|
|
62
|
+
- The chosen option is **persisted immediately** (see step 4). No re-prompt for the same query in the same project.
|
|
63
|
+
|
|
64
|
+
## Retry-once contract
|
|
65
|
+
|
|
66
|
+
If `ask_user` returns option 4 (type-in) and the typed value still doesn't resolve in step 1, retry ONCE with the new query, then fall through to option 5 (skip + write `unresolved-<source>`). Never loop forever.
|
|
67
|
+
|
|
68
|
+
## Where this rule applies (HARD inventory)
|
|
69
|
+
|
|
70
|
+
| Surface | Query | Resolves to |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `engagement-root-resolution` Step "project name" | user's project arg | `<engagement-root>/<project>/` folder |
|
|
73
|
+
| `pull-onenote` Step A | section name from `m365-mutable.json` or new ask | OneNote `sectionId` + `notebookId` (write to id-registry) |
|
|
74
|
+
| `pull-sharepoint` Step "resolve site" | site name / URL hint | `siteId` + `webUrl` (write to project `integrations.yml`) |
|
|
75
|
+
| `pull-teams` Step "scope chats" | chat topic / member name | `chatId[]` (write to alias `.settings.yml`) |
|
|
76
|
+
| `pull-meetings` Step "scope meetings" | meeting subject pattern | meeting candidates by joinUrl (no persistence — meeting-scoped) |
|
|
77
|
+
| `pull-crm` Step "resolve record" | account / project name | Dataverse `engagementRecordId` (write to project `integrations.yml`) |
|
|
78
|
+
| `pull-ado` Step "resolve area" | area name | ADO `areaPath` (write to project `integrations.yml`) |
|
|
79
|
+
| `pull-email` Step "resolve mailbox folders" | folder name | canonical folder display-name (write to `m365-auth.json`) |
|
|
80
|
+
| `ask-project` Step "resolve project" | user's project arg in question | same as engagement-root-resolution |
|
|
81
|
+
|
|
82
|
+
## What NOT to do
|
|
83
|
+
|
|
84
|
+
- Do NOT silently pick the first candidate when score-gap is `< 0.7`. Always disambiguate.
|
|
85
|
+
- Do NOT prompt with the legacy "Enter the exact name:" free-text — always present a ranked list first.
|
|
86
|
+
- Do NOT re-prompt for the same query in the same run after the user picked an answer. Cache it for this run AND persist for next-run direct lookup.
|
|
87
|
+
- Do NOT use per-source thresholds. The above scoring is universal.
|
|
88
|
+
|
|
89
|
+
## Self-check enforcement
|
|
90
|
+
|
|
91
|
+
**D17 — Fuzzy disambiguation cite.** Every `pull-*` SKILL.md whose source has fuzzy name → ID resolution (`pull-onenote`, `pull-sharepoint`, `pull-teams`, `pull-crm`, `pull-ado`, `pull-email`) MUST reference `fuzzy-disambiguation.instructions.md` in its body. Plus `engagement-root-resolution.instructions.md` and `ask-project` SKILL.md.
|
|
92
|
+
|
|
93
|
+
## References
|
|
94
|
+
|
|
95
|
+
- `engagement-root-resolution.instructions.md` — the originating "ask user on ambiguity" pattern (project-name resolution). v4.4.7+ this rule is the universal generalization.
|
|
96
|
+
- `m365-id-registry.instructions.md` — destination registries for persisted resolutions.
|
|
97
|
+
- `multi-user-shared-files.instructions.md` — `m365-mutable.json` writes go through the SharePoint conflict-absorb pattern.
|
|
@@ -71,3 +71,79 @@ Per `deferred-retry-on-workiq-fail.instructions.md`, identity resolution NEVER b
|
|
|
71
71
|
* `deferred-retry-on-workiq-fail.instructions.md` — failure-mode contract.
|
|
72
72
|
* `engagement-root-resolution.instructions.md` — `projects_root` resolution (separate from identity).
|
|
73
73
|
* `templates/init/project-evidence.template.yml` — defaults to `<auto>` for these three fields.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## v4.4.5 extension — `setup` skill is the single source of truth for ALL onboarding questions
|
|
78
|
+
|
|
79
|
+
The `setup` skill (added v4.4.4) extends this doctrine beyond identity to cover the full onboarding question set. `bootstrap-project` MUST NOT re-ask any question that `setup` already answered.
|
|
80
|
+
|
|
81
|
+
### Fields managed by `setup`
|
|
82
|
+
|
|
83
|
+
| File | Field | Required? | Auto-resolved from |
|
|
84
|
+
|---|---|---|---|
|
|
85
|
+
| `project-evidence.yml` | `identity.alias` / `display_name` / `email` | YES | WorkIQ "Who am I?" |
|
|
86
|
+
| `project-evidence.yml` | `identity_status`, `identity_verified_at` | YES | written by setup |
|
|
87
|
+
| `project-evidence.yml` | `projects_root` | YES | cwd-ancestor detection; otherwise prompt |
|
|
88
|
+
| `project-evidence.yml` | `default_onenote_notebook` | **OPTIONAL** | user prompt; value `<skip>` = OneNote disabled |
|
|
89
|
+
| `project-evidence.yml` | `active_projects[]` | YES (list, may be empty) | the project arg if dispatched from `bootstrap <project>`; otherwise leave existing |
|
|
90
|
+
| `project-evidence.yml` | `workiq.cli_path` | OPTIONAL | only written when discovered via PATH/fallback, not already pinned |
|
|
91
|
+
| `m365-auth.json` | `_meta.owner` | YES | `<identity.email>` |
|
|
92
|
+
| `m365-auth.json` | `oneNote.defaultLinkOwner` | YES (when OneNote enabled) | `<identity.email>` — the user's own UPN, since OneNote queries default to the user's personal notebook |
|
|
93
|
+
| `m365-auth.json` | `oneNote.defaultNotebookId` | **NOT WRITTEN (kushi v4.7.3+)** | Per `workiq-onenote-query-shape.instructions.md`, WorkIQ has no notebook-ID lookup surface — any such probe punts to Graph Explorer. Setup persists `defaultNotebookName` only; section IDs are discovered per-section at bootstrap/refresh time using the natural-language WorkIQ pattern. |
|
|
94
|
+
| `m365-auth.json` | `oneNote.enabled` | YES | `false` when user chose `<skip>` for the notebook name |
|
|
95
|
+
| `m365-auth.json` | `emailContext.folders[]` | OPTIONAL | user prompt: comma-sep list / `all` / blank. `all` or blank → `[]` (full mailbox) |
|
|
96
|
+
| `m365-auth.json` | `sharePointContext.localProjectsRoot` | YES | mirrors `<projects_root>` |
|
|
97
|
+
|
|
98
|
+
### Auto-EULA + sign-in detection
|
|
99
|
+
|
|
100
|
+
`pingWorkIQ()` in `src/check-workiq.mjs` auto-runs `workiq accept-eula` (idempotent) when the first `workiq ask -q "ping"` returns empty OR mentions EULA/license, then retries the ping. Only if BOTH attempts return empty does the installer surface a "Sign in by running `workiq ask -q \"Who am I?\"` in a new terminal" hint.
|
|
101
|
+
|
|
102
|
+
The `setup` skill does the deeper recovery (3-retry loop with `ask_user` choices).
|
|
103
|
+
|
|
104
|
+
### OneNote — optional + auto-resolve
|
|
105
|
+
|
|
106
|
+
- User chose `<skip>` → set `oneNote.enabled: false`, leave all other OneNote fields blank, OneNote source becomes a no-op (reported as `not-applicable` in run logs).
|
|
107
|
+
- User gave a notebook name → query WorkIQ for `{notebookId, notebookSourceDoc, notebookSpoBaseUrl}`. Persist all three. If WorkIQ returns empty, leave `defaultNotebookId: ""` with a `_defaultNotebookId_note` and let `pull-onenote` Step A do per-section discovery.
|
|
108
|
+
- `defaultLinkOwner` is ALWAYS the user's own UPN unless the user explicitly overrides (shared/team-owned notebook case).
|
|
109
|
+
|
|
110
|
+
### Mailbox folders — multi-value with "all" sentinel
|
|
111
|
+
|
|
112
|
+
Question text (only asked once, only by `setup`, only when `emailContext.folders` is empty or `["__FILL_ME_IN__"]`):
|
|
113
|
+
|
|
114
|
+
> Which mailbox folders should kushi search for email evidence?
|
|
115
|
+
> • Enter a comma-separated list (e.g. `Inbox, Archive, Projects/HCA`)
|
|
116
|
+
> • Type `all` to search the FULL mailbox (slower — every refresh scans everything)
|
|
117
|
+
> • Press Enter to leave blank (equivalent to `all`)
|
|
118
|
+
|
|
119
|
+
Both `all` and blank persist as `[]`. ALWAYS print after persisting:
|
|
120
|
+
|
|
121
|
+
> Note: empty list = scan the full mailbox on every refresh (slower). Add specific folders later via `.kushi/config/user/m365-auth.json#emailContext.folders` to speed runs up.
|
|
122
|
+
|
|
123
|
+
### Active projects — don't ask, use what was asked
|
|
124
|
+
|
|
125
|
+
- **Dispatched from `bootstrap <project>`** → append `<project>` to `active_projects` (don't replace).
|
|
126
|
+
- **Standalone `setup`** → leave `active_projects` untouched; just print the file pointer.
|
|
127
|
+
- **`setup --reconfigure`** → ask the user explicitly, default = current list.
|
|
128
|
+
|
|
129
|
+
### "Where to edit this later" footer (REQUIRED on every setup run)
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
Edit these files later if anything changes:
|
|
133
|
+
• <workspace>/.kushi/config/user/project-evidence.yml
|
|
134
|
+
identity, projects_root, default_onenote_notebook, active_projects, workiq.cli_path
|
|
135
|
+
• <workspace>/.kushi/config/user/m365-auth.json
|
|
136
|
+
OneNote defaults, email folder list, SharePoint root
|
|
137
|
+
• <workspace>/.kushi/config/shared/integrations.yml (team-wide; commit changes)
|
|
138
|
+
CRM environmentUrl + tenantId, ADO organization + project
|
|
139
|
+
|
|
140
|
+
To re-walk every question (overwrites all answers):
|
|
141
|
+
@Kushi setup --reconfigure
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Render the file paths as clickable `file:///` URIs when the host (VS Code, Clawpilot) supports them.
|
|
145
|
+
|
|
146
|
+
### `bootstrap-project` Step 0 contract
|
|
147
|
+
|
|
148
|
+
`bootstrap-project` Step 0 dispatches to `setup` only when `identity_status` is missing or `skipped-*`. It NEVER directly asks for identity, OneNote name/ID, mailbox folders, or `projects_root`. If `identity_status: verified` is set, Step 0 is a no-op and bootstrap proceeds directly to Step 1.
|
|
149
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**"
|
|
3
|
+
description: "Universal Issue Recovery Rule. When a workflow exposes a reusable problem in the repo-owned surface (skill text, instruction, prompt, config, supporting code), fix the smallest correct artifact first — don't just report and continue. Prefer durable fixes over one-off workarounds. After the fix, re-run the narrowest failed check before continuing. Never use memory as a substitute for correcting the workflow surface."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Issue Recovery Rule (v4.5.1)
|
|
7
|
+
|
|
8
|
+
A universal rule for every kushi skill that hits an external service (M365 via WorkIQ, Dataverse via az CLI, ADO via az boards, OneNote via Playwright, etc.) AND for every workflow that consumes evidence (aggregate-project, ask-project, fde-report).
|
|
9
|
+
|
|
10
|
+
## The rule
|
|
11
|
+
|
|
12
|
+
When a run exposes a reusable problem in the repo-owned ADO/CRM/M365/OneNote/SharePoint/Meetings/Email surface:
|
|
13
|
+
|
|
14
|
+
1. **Fix the smallest correct repo-owned artifact first** when feasible.
|
|
15
|
+
- Skill text (`plugin/skills/<name>/SKILL.md`)
|
|
16
|
+
- Instruction file (`plugin/instructions/*.instructions.md`)
|
|
17
|
+
- Prompt (`plugin/prompts/*.prompt.md`)
|
|
18
|
+
- Config template (`plugin/templates/init/*.template.*`)
|
|
19
|
+
- Supporting code (`plugin/skills/<name>/*.mjs`, `plugin/lib/*.ps1`, `src/*.mjs`)
|
|
20
|
+
- Reference pack (`plugin/reference-packs/<pack>/*.md`)
|
|
21
|
+
2. **Prefer a durable fix over a one-off workaround** when the issue will affect the next run too. Per-run workarounds (chat-only retries, ad-hoc filter changes that aren't persisted) are escalation paths, not solutions.
|
|
22
|
+
3. **After the fix, rerun the narrowest failed check** before continuing the broader workflow steps. E.g. if pull-onenote failed Pre-flight C URL completeness, fix the canonical URL doctrine + re-run Pre-flight C only; don't re-run the whole bootstrap.
|
|
23
|
+
4. **Record the minimum useful guardrail in the run report + learnings** AFTER the fix is confirmed. Per `capture-learnings.instructions.md` for the source-specific learnings file; per `run-reports.instructions.md` for the per-run narrative.
|
|
24
|
+
5. **Do NOT use memory as a substitute** for correcting the workflow surface. If an issue happened once, the fix lives in the repo. Memory captures user preferences and decisions, not workflow defects.
|
|
25
|
+
|
|
26
|
+
## Examples (correct application)
|
|
27
|
+
|
|
28
|
+
| Symptom | Wrong response | Right response |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| pull-onenote returns auth-required on every project after a URL-formula change | Add retry loop to runner.mjs | Fix `pull-onenote/SKILL.md` Pre-flight C to ban synthesized URLs (per learnings/onenote.md) — durable across every project |
|
|
31
|
+
| pull-ado finds zero comments because comments/history is empty; attachment has the discussion | Skip this run / mark as no-coverage | Emit `evidence_source_kind: attachment-fallback` in coverage notes; `aggregate-project` reads it; user knows the discussion lives in an attachment, not comments. Update `pull-ado/SKILL.md` if the fallback path itself is missing |
|
|
32
|
+
| pull-crm finds the engagement record but the bootstrap discovery skipped it because of a 401 | Add a per-run retry in the orchestrator | Fix the auth-and-retry pattern in `auth-and-retry.instructions.md` if the retry shape is broken; or fix the `crm-bootstrap-discovery.instructions.md` if the 401 means the user has a different tenant than the config says |
|
|
33
|
+
| Workspace template missing a field that user keeps having to add by hand | Document the workaround in CHANGELOG | Update the template at `plugin/templates/init/*.template.*` so the next user gets the right shape from `setup` Step 2 |
|
|
34
|
+
|
|
35
|
+
## Examples (wrong application — don't do these)
|
|
36
|
+
|
|
37
|
+
- ❌ **"Just store this in memory so I don't forget next time."** Memory is for user preferences and engagement-specific decisions, not workflow defects. If the same issue happens again, the fix must already be in the repo.
|
|
38
|
+
- ❌ **"Hot-patch the live install but skip the repo edit."** Live install fixes are temporary; repo is the source of truth. Self-check D4 will surface the drift.
|
|
39
|
+
- ❌ **"Add a one-off filter for this project to avoid the bad data."** Per-project filters belong in the project's `integrations.yml#boundaries.*`. Doctrine fixes belong in the repo.
|
|
40
|
+
|
|
41
|
+
## Scope (which skills MUST cite this rule)
|
|
42
|
+
|
|
43
|
+
The Issue Recovery Rule applies to every skill that runs against an external service AND to every consumer of their evidence:
|
|
44
|
+
|
|
45
|
+
| Skill | Cite required | Why |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `pull-email`, `pull-teams`, `pull-meetings`, `pull-onenote`, `pull-sharepoint`, `pull-crm`, `pull-ado`, `pull-misc` | ✅ | External service failures expose doctrine gaps |
|
|
48
|
+
| `aggregate-project`, `consolidate-evidence` | ✅ | Consumers — they discover when capture quality is poor |
|
|
49
|
+
| `bootstrap-project`, `refresh-project` | ✅ | Orchestrators — they observe per-source gate outcomes |
|
|
50
|
+
| `setup`, `self-check`, `intro` | ➖ | Internal-state skills; no external service surface |
|
|
51
|
+
| `ask-project`, `project-status`, `fde-*` | ➖ | Read-only over local evidence; defects surface via gate, not directly |
|
|
52
|
+
|
|
53
|
+
## References
|
|
54
|
+
|
|
55
|
+
- `per-source-verification-gate.instructions.md` — the per-source gate exposes the symptoms this rule responds to.
|
|
56
|
+
- `capture-learnings.instructions.md` — where the post-fix learning gets appended.
|
|
57
|
+
- `run-reports.instructions.md` — where the run-specific narrative records the fix.
|
|
58
|
+
- `fallback-status-reporting.instructions.md` — when an attachment-fallback or comments-empty path was used, how to surface it in status.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**/*"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Kushi config root — never hardcode `.kushi/config/` paths
|
|
6
|
+
|
|
7
|
+
**Truth (v4.7.2+):** the `.kushi/config/` tree lives in different places
|
|
8
|
+
depending on how Kushi was installed:
|
|
9
|
+
|
|
10
|
+
| Install command | Config root |
|
|
11
|
+
|----------------------------------------------|----------------------------------------------|
|
|
12
|
+
| `npx kushi-agents@latest` (default = vscode) | `<workspace>/.kushi/config/` |
|
|
13
|
+
| `npx kushi-agents@latest --clawpilot` | `~/.copilot/m-skills/kushi/config/` |
|
|
14
|
+
|
|
15
|
+
Some users install BOTH (per-repo vscode + global clawpilot). Both layouts are
|
|
16
|
+
first-class and supported indefinitely.
|
|
17
|
+
|
|
18
|
+
## Rule
|
|
19
|
+
|
|
20
|
+
Every skill, prompt, and instructions file that needs to **read or write** a
|
|
21
|
+
Kushi config file MUST resolve the path via the official helpers — never by
|
|
22
|
+
joining `<workspace>` + `.kushi/config/...` directly.
|
|
23
|
+
|
|
24
|
+
| Caller | Helper |
|
|
25
|
+
|-----------------------|----------------------------------------------------------------|
|
|
26
|
+
| PowerShell (`.ps1`) | `& "$PSScriptRoot/Get-KushiConfig.ps1" -Name '<name>'` |
|
|
27
|
+
| Node (`.mjs`) | `import { loadKushiConfig } from '../../../src/config-loader.mjs'` |
|
|
28
|
+
| Agent / prompt text | Refer to `<kushi-config-root>/user/<name>.<ext>` (placeholder) |
|
|
29
|
+
|
|
30
|
+
The helpers walk a fixed candidate order:
|
|
31
|
+
|
|
32
|
+
1. `<workspace>/.kushi/config/` — if it exists with `user/` or `shared/`.
|
|
33
|
+
2. `~/.copilot/m-skills/kushi/config/` — clawpilot fallback.
|
|
34
|
+
|
|
35
|
+
The first match wins. The helpers throw a clear actionable error
|
|
36
|
+
("Run `npx kushi-agents@latest [--clawpilot]`") when neither exists.
|
|
37
|
+
|
|
38
|
+
## What this fixes (v4.7.2)
|
|
39
|
+
|
|
40
|
+
Before v4.7.2, every skill (setup, bootstrap-project, pull-*) hardcoded
|
|
41
|
+
`<workspace>/.kushi/config/...`. On a Clawpilot-only host (where the config
|
|
42
|
+
lives at `~/.copilot/m-skills/kushi/config/`), Step 0 of `bootstrap` saw the
|
|
43
|
+
workspace path missing, concluded "no install", and asked the user to
|
|
44
|
+
install/overwrite `.kushi/`. That prompt was a bug — the config was present at
|
|
45
|
+
the clawpilot location and bootstrap should have proceeded normally.
|
|
46
|
+
|
|
47
|
+
## Prompts and user-facing text
|
|
48
|
+
|
|
49
|
+
When telling the user where their config file lives (for hand-editing), call
|
|
50
|
+
the resolver first:
|
|
51
|
+
|
|
52
|
+
```ps1
|
|
53
|
+
$pePath = & "$PSScriptRoot/Get-KushiConfig.ps1" -Name 'project-evidence' -Path
|
|
54
|
+
"Edit: file:///$($pePath -replace '\\','/')"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Do NOT emit `file:///<workspace>/.kushi/config/user/...` as a literal — it
|
|
58
|
+
points at a non-existent file on Clawpilot-only hosts.
|
|
59
|
+
|
|
60
|
+
## Migration of legacy doctrine text
|
|
61
|
+
|
|
62
|
+
In prose, the canonical placeholder is `<kushi-config-root>/user/<name>.yml`
|
|
63
|
+
(or `.../shared/<name>.yml`). Old text using `<workspace>/.kushi/config/...`
|
|
64
|
+
is equivalent ONLY for vscode installs and should be migrated when touched.
|
|
65
|
+
The helpers continue to support both layouts, so the bug is in the doctrine
|
|
66
|
+
text, not the runtime.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**/pull-loop/**,**/setup/**,**/bootstrap-project/**,**/refresh-project/**"
|
|
3
|
+
description: "How Loop workspaces and pages are registered, resolved, and persisted. WorkIQ-first for discovery + metadata; Playwright for body capture. Mirrors the OneNote bootstrap pattern."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Loop bootstrap discovery (v4.6.0+)
|
|
7
|
+
|
|
8
|
+
Loop is a first-class evidence source in kushi v4.6.0+. This doctrine governs how a project's Loop workspaces and pages are discovered, resolved to durable IDs, and persisted to the per-user registry.
|
|
9
|
+
|
|
10
|
+
## Surface model
|
|
11
|
+
|
|
12
|
+
Microsoft Loop has three relevant surface levels:
|
|
13
|
+
|
|
14
|
+
| Level | URL pattern | What it is |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| **Workspace** | `https://*.loop.microsoft.com/loop/p/<workspaceId>/_workspace` | Top-level container; holds pages |
|
|
17
|
+
| **Page** | `https://*.loop.microsoft.com/loop/p/<workspaceId>/<pageId>` | A document inside a workspace; can hold components, tables, lists |
|
|
18
|
+
| **Standalone component** | `https://*.fluidpreview.office.net/...`, `https://loop-api.cloud.microsoft/...` | A Loop component embedded in another surface (Teams chat, OneNote page, Outlook email, etc.) |
|
|
19
|
+
|
|
20
|
+
Kushi captures workspace + page bodies. Standalone components embedded in Teams/OneNote/Email are captured by their host source's `pull-*` skill, NOT by `pull-loop` (which would double-count).
|
|
21
|
+
|
|
22
|
+
## Registry shape
|
|
23
|
+
|
|
24
|
+
Persisted to `<workspace>/.kushi/config/user/m365-mutable.json` under `knownSections.<projectKey>.loop`:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"knownSections": {
|
|
29
|
+
"<projectKey>": {
|
|
30
|
+
"loop": {
|
|
31
|
+
"workspaces": [
|
|
32
|
+
{
|
|
33
|
+
"workspaceId": "<base64-workspace-id>",
|
|
34
|
+
"workspaceUrl": "https://<tenant>.loop.microsoft.com/loop/p/<workspaceId>/_workspace",
|
|
35
|
+
"workspaceTitle": "<display title>",
|
|
36
|
+
"owner": "<upn>",
|
|
37
|
+
"discovered_at": "<ISO-8601>",
|
|
38
|
+
"loop_pages": [
|
|
39
|
+
{
|
|
40
|
+
"pageId": "<page-id>",
|
|
41
|
+
"pageUrl": "https://<tenant>.loop.microsoft.com/loop/p/<workspaceId>/<pageId>",
|
|
42
|
+
"pageTitle": "<display title>",
|
|
43
|
+
"last_status": "ok | auth-required | page-body-unavailable | unresolved",
|
|
44
|
+
"last_captured_at": "<ISO-8601>",
|
|
45
|
+
"captured_via": "playwright | workiq | host"
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
This shape mirrors `knownSections.<projectKey>.onenote.{sections, one_pages}` — same discover-once / consume-deterministically contract per `m365-id-registry.instructions.md`.
|
|
57
|
+
|
|
58
|
+
## Discovery flow (during `setup` Step 4d-loop)
|
|
59
|
+
|
|
60
|
+
When the user enables Loop for a project, run this flow:
|
|
61
|
+
|
|
62
|
+
1. **Ask for the workspace URL(s).** `ask_user` with the user's recent Loop activity (if WorkIQ can surface it via `workiq ask -q "List my recent Loop workspaces. Return strictly JSON: [{workspaceId, workspaceUrl, workspaceTitle}]. No prose."`). Else ask for a URL paste.
|
|
63
|
+
2. **Resolve via WorkIQ** — `workiq ask -q "What is the Loop workspace ID for workspace at URL '<URL>'? Return strictly JSON: {workspaceId, workspaceTitle, owner}. No prose."`. WorkIQ returns metadata via the M365 search/Graph index.
|
|
64
|
+
3. **Enumerate pages via WorkIQ** — `workiq ask -q "List pages in Loop workspace '<workspaceTitle>' (id=<workspaceId>) modified in the last 90 days. Return strictly JSON array: [{pageId, pageUrl, pageTitle, lastModified}]. No prose."`. Persist each as `loop_pages[]` with `last_status: "unresolved"`.
|
|
65
|
+
4. **Persist** — write to `knownSections.<projectKey>.loop.workspaces[]` per `m365-id-registry.instructions.md` (discover-once); never re-discover unless `--reconfigure`.
|
|
66
|
+
5. **Skip-friendly** — Loop is OPTIONAL. If WorkIQ returns no Loop coverage in this tenant (Conditional Access / new feature / no workspaces), persist `loop_status: "skipped-no-coverage"` and continue. `pull-loop` will refuse to run when Loop isn't registered for a project — never silently widens to "all loop workspaces."
|
|
67
|
+
|
|
68
|
+
## Pre-flight gates (every `pull-loop` invocation)
|
|
69
|
+
|
|
70
|
+
| Pre-flight | Pass criteria |
|
|
71
|
+
|---|---|
|
|
72
|
+
| **A. Workspaces registered** | `knownSections.<projectKey>.loop.workspaces[]` non-empty for the resolved project. |
|
|
73
|
+
| **B. Pages registered** | At least one workspace has `loop_pages[]` non-empty OR `loop_pages_status: "to-be-enumerated"`. |
|
|
74
|
+
| **C. Playwright profile bootstrapped** | `~/.kushi/playwright-profile/m365/` exists OR `~/.kushi/playwright-profile/onenote/` exists (reused; see Profile sharing below). |
|
|
75
|
+
| **D. URLs are canonical** | Every `pageUrl` matches `https://*.loop.microsoft.com/loop/p/<workspaceId>/<pageId>` (or a documented redirect target). Synthesized URLs (constructed from workspaceId + pageId templates) are FORBIDDEN unless they were observed in a WorkIQ response — same rule as OneNote per the learnings/onenote.md doctrine. |
|
|
76
|
+
|
|
77
|
+
On Pre-flight failure: refuse to run, write a `FOLLOW-UPS.md` entry per `per-source-verification-gate.instructions.md`, and instruct the user to run `@Kushi setup --reconfigure` (or `setup --resync-loop`).
|
|
78
|
+
|
|
79
|
+
## Profile sharing with OneNote
|
|
80
|
+
|
|
81
|
+
The Playwright profile at `~/.kushi/playwright-profile/onenote/` is M365-wide — it holds the user's signed-in browser context for OneNote-for-Web, SharePoint-Online, Loop, and Office.com. `pull-loop` reuses this profile (no separate bootstrap). If the profile is missing or auth-expired, the OneNote bootstrap recipe re-mints it: `node plugin/skills/pull-onenote/runner.mjs --bootstrap` (the bootstrap visits SharePoint surfaces, which also seeds Loop auth).
|
|
82
|
+
|
|
83
|
+
In v4.6.0+ the profile path is canonically `~/.kushi/playwright-profile/m365/` with `~/.kushi/playwright-profile/onenote/` retained as a fallback for older installs. Both readers check both paths; new bootstraps write to `m365/`.
|
|
84
|
+
|
|
85
|
+
## Boundary in `<project>/integrations.yml`
|
|
86
|
+
|
|
87
|
+
```yaml
|
|
88
|
+
boundaries:
|
|
89
|
+
loop:
|
|
90
|
+
workspace_ids: [] # required: durable IDs from knownSections.<project>.loop.workspaces[]
|
|
91
|
+
page_ids: [] # optional: pin specific pages; else enumerate all in workspace
|
|
92
|
+
include_components: false # v4.6.0: standalone components captured by their host source (teams/onenote/email)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`workspace_ids` MUST be non-empty for Loop to be enabled per `scope-boundaries.instructions.md`. Empty = source disabled.
|
|
96
|
+
|
|
97
|
+
## References
|
|
98
|
+
|
|
99
|
+
- `m365-id-registry.instructions.md` — discover-once contract.
|
|
100
|
+
- `evidence-thoroughness.instructions.md` — verbatim body capture.
|
|
101
|
+
- `per-source-verification-gate.instructions.md` — gate §2 loop row + §2a canonical kind.
|
|
102
|
+
- `workiq-only.instructions.md` — discovery via WorkIQ first.
|
|
103
|
+
- `issue-recovery.instructions.md` — apply when Loop discovery or capture fails.
|
|
104
|
+
- `cleanup-on-resolution.instructions.md` — when a page resolves from `unresolved` → `ok`, prune the stale marker.
|
|
105
|
+
- `docs/concepts/loop-source.md` — user-facing companion doc.
|
|
@@ -123,7 +123,7 @@ The script's `--check` mode is the gate; the interactive mode is the heal. See `
|
|
|
123
123
|
|
|
124
124
|
1. ❌ Refresh probes WorkIQ for `"List sections in OneNote notebook"` instead of reading `one_sectionFileId` from the registry.
|
|
125
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
|
|
126
|
+
3. ❌ Pull-* skill uses the wrong WorkIQ phrasing for the source. For OneNote specifically (per `workiq-onenote-query-shape.instructions.md`, kushi v4.7.3+): WorkIQ is the **PRIMARY** path; Playwright is opt-in recovery-only fallback. The only working WorkIQ shape is **natural-language naming by display name** (one section in one notebook). Forbidden: `"List my OneNote notebooks"`, `"What is the OneNote notebook ID for..."`, `"List sections in OneNote notebook..."`, anything with `wdsectionfileid = <id>` filter syntax, anything asking for ID-shaped JSON output. These all empirically fail (WorkIQ punts to Graph or routes to summary mode).
|
|
127
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
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
129
|
4. ❌ Bootstrap writes a partial / speculative ID with a `_note` field instead of marking the source disabled.
|