kushi-agents 4.4.4 → 4.8.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 (100) hide show
  1. package/README.md +3 -0
  2. package/package.json +4 -4
  3. package/plugin/agents/kushi.agent.md +29 -15
  4. package/plugin/config/studios.json +37 -0
  5. package/plugin/config/studios.schema.json +45 -0
  6. package/plugin/instructions/auth-and-retry.instructions.md +268 -1
  7. package/plugin/instructions/customer-hint-discovery.instructions.md +129 -0
  8. package/plugin/instructions/email-bootstrap-discovery.instructions.md +105 -0
  9. package/plugin/instructions/engagement-root-resolution.instructions.md +5 -1
  10. package/plugin/instructions/evidence-thoroughness.instructions.md +103 -1
  11. package/plugin/instructions/fuzzy-disambiguation.instructions.md +97 -0
  12. package/plugin/instructions/identity-resolution.instructions.md +76 -0
  13. package/plugin/instructions/issue-recovery.instructions.md +58 -0
  14. package/plugin/instructions/kushi-config-root.instructions.md +66 -0
  15. package/plugin/instructions/loop-bootstrap-discovery.instructions.md +105 -0
  16. package/plugin/instructions/m365-id-registry.instructions.md +1 -1
  17. package/plugin/instructions/meetings-bootstrap-discovery.instructions.md +99 -0
  18. package/plugin/instructions/onedrive-pin-policy.instructions.md +132 -0
  19. package/plugin/instructions/per-source-verification-gate.instructions.md +193 -0
  20. package/plugin/instructions/sharepoint-bootstrap-discovery.instructions.md +97 -0
  21. package/plugin/instructions/sharepoint-to-onedrive-sync.instructions.md +116 -0
  22. package/plugin/instructions/status-color-rule.instructions.md +62 -0
  23. package/plugin/instructions/status-taxonomy.instructions.md +1 -1
  24. package/plugin/instructions/studio-registry.instructions.md +48 -0
  25. package/plugin/instructions/teams-bootstrap-discovery.instructions.md +101 -0
  26. package/plugin/instructions/update-ledger.instructions.md +1 -1
  27. package/plugin/instructions/verbatim-by-default.instructions.md +1 -1
  28. package/plugin/instructions/vertex-emit.instructions.md +120 -0
  29. package/plugin/instructions/workiq-input-sanitization.instructions.md +43 -0
  30. package/plugin/instructions/workiq-onenote-query-shape.instructions.md +79 -0
  31. package/plugin/instructions/workiq-only.instructions.md +13 -7
  32. package/plugin/learnings/loop.md +11 -0
  33. package/plugin/learnings/onenote.md +27 -1
  34. package/plugin/lib/Get-KushiConfig.ps1 +22 -9
  35. package/plugin/lib/detect-vertex-repo.mjs +96 -0
  36. package/plugin/lib/render-vertex.mjs +249 -0
  37. package/plugin/lib/sanitize-workiq-input.mjs +72 -0
  38. package/plugin/lib/studio-registry.mjs +39 -0
  39. package/plugin/lib/vertex-validate.mjs +121 -0
  40. package/plugin/plugin.json +13 -6
  41. package/plugin/prompts/bootstrap.prompt.md +9 -7
  42. package/plugin/prompts/emit-vertex.prompt.md +33 -0
  43. package/plugin/prompts/setup.prompt.md +1 -1
  44. package/plugin/prompts/vertex-link.prompt.md +27 -0
  45. package/plugin/skills/aggregate-project/SKILL.md +24 -2
  46. package/plugin/skills/apply-ado-update/SKILL.md +9 -4
  47. package/plugin/skills/ask-project/SKILL.md +4 -0
  48. package/plugin/skills/bootstrap-project/SKILL.md +106 -39
  49. package/plugin/skills/consolidate-evidence/SKILL.md +5 -1
  50. package/plugin/skills/emit-vertex/README.md +37 -0
  51. package/plugin/skills/emit-vertex/SKILL.md +173 -0
  52. package/plugin/skills/intro/SKILL.md +2 -0
  53. package/plugin/skills/propose-ado-update/SKILL.md +8 -3
  54. package/plugin/skills/pull-ado/SKILL.md +11 -1
  55. package/plugin/skills/pull-crm/SKILL.md +12 -2
  56. package/plugin/skills/pull-email/SKILL.md +15 -1
  57. package/plugin/skills/pull-loop/README.md +64 -0
  58. package/plugin/skills/pull-loop/SKILL.md +180 -0
  59. package/plugin/skills/pull-loop/runner.mjs +261 -0
  60. package/plugin/skills/pull-loop/write-snapshot.mjs +181 -0
  61. package/plugin/skills/pull-meetings/SKILL.md +15 -1
  62. package/plugin/skills/pull-misc/README.md +4 -4
  63. package/plugin/skills/pull-misc/SKILL.md +18 -12
  64. package/plugin/skills/pull-onenote/SKILL.md +71 -19
  65. package/plugin/skills/pull-sharepoint/SKILL.md +15 -2
  66. package/plugin/skills/pull-teams/SKILL.md +15 -2
  67. package/plugin/skills/refresh-project/SKILL.md +38 -7
  68. package/plugin/skills/self-check/SKILL.md +14 -1
  69. package/plugin/skills/self-check/run.ps1 +442 -20
  70. package/plugin/skills/setup/SKILL.md +289 -86
  71. package/plugin/skills/vertex-link/SKILL.md +143 -0
  72. package/plugin/templates/init/m365-auth.template.json +10 -4
  73. package/plugin/templates/init/project-evidence.template.yml +4 -1
  74. package/plugin/templates/init/project-integrations.template.yml +5 -0
  75. package/plugin/templates/snapshot/ado-item.template.md +1 -1
  76. package/plugin/templates/snapshot/crm-record.template.md +1 -1
  77. package/plugin/templates/snapshot/meetings-series-index.template.md +1 -1
  78. package/plugin/templates/snapshot/onenote-page.template.md +1 -1
  79. package/plugin/templates/snapshot/sharepoint-file.template.md +1 -1
  80. package/plugin/templates/snapshot/sharepoint-tree.template.md +1 -1
  81. package/plugin/templates/snapshot/teams-roster.template.md +1 -1
  82. package/plugin/templates/weekly/ado-stream.template.md +1 -1
  83. package/plugin/templates/weekly/crm-stream.template.md +1 -1
  84. package/plugin/templates/weekly/email-stream.template.md +1 -1
  85. package/plugin/templates/weekly/meetings-stream.template.md +1 -1
  86. package/plugin/templates/weekly/onenote-stream.template.md +1 -1
  87. package/plugin/templates/weekly/sharepoint-stream.template.md +1 -1
  88. package/plugin/templates/weekly/teams-stream.template.md +1 -1
  89. package/src/check-workiq.mjs +48 -15
  90. package/src/config-loader.mjs +71 -13
  91. package/src/config-root-resolve.test.mjs +137 -0
  92. package/src/detect-vertex-repo.test.mjs +128 -0
  93. package/src/emit-vertex.e2e.test.mjs +308 -0
  94. package/src/forbidden-workiq-phrasings.test.mjs +167 -0
  95. package/src/sanitize-workiq-input.test.mjs +45 -0
  96. package/src/vertex-validate.test.mjs +142 -0
  97. package/plugin/instructions/az-auth-conditional.instructions.md +0 -39
  98. package/plugin/instructions/azure-auth-patterns.instructions.md +0 -233
  99. package/plugin/instructions/thoroughness-detector.instructions.md +0 -105
  100. package/plugin/instructions/workiq-first.instructions.md +0 -31
@@ -0,0 +1,129 @@
1
+ ---
2
+ applyTo: "**/skills/bootstrap-project/**, **/skills/pull-email/**, **/skills/pull-teams/**, **/skills/pull-meetings/**, **/skills/pull-sharepoint/**, **/skills/refresh-project/**"
3
+ description: "Customer-hint discovery sweep — bootstrap MUST attempt WorkIQ-driven discovery for every source whose boundary is empty before declaring blocked-config. Mirrors crm-bootstrap-discovery + loop-bootstrap-discovery pattern. Kushi v4.8.0+."
4
+ ---
5
+
6
+ # Customer-hint discovery sweep (HARD RULE — kushi v4.8.0+)
7
+
8
+ ## The defect this rule exists to prevent
9
+
10
+ Bootstrap runs that scaffold an empty `<project>/integrations.yml#boundaries.*` and immediately declare every source `blocked-config` — without ever asking WorkIQ "who/what mentions this customer?". The result: a fresh project bootstrap finishes with 0 evidence pulled, the user is told to hand-populate mailboxes / chat IDs / channel IDs / meeting join URLs / SharePoint sites, and the entire value proposition of "bootstrap a project from a customer hint" collapses to a config form. Discovered 2026-05-26 on the HCA bootstrap — every source row read `blocked-config` despite extensive HCA email / Teams / meeting history in the tenant.
11
+
12
+ ## The rule
13
+
14
+ Before `bootstrap-project` (or `refresh-project` on a first pull) is allowed to write `last_status: blocked-config` for **email, teams, meetings, or sharepoint**, the per-source customer-hint discovery sweep defined in the matching doctrine MUST be attempted with the customer hint + lookback window:
15
+
16
+ | Source | Doctrine file (mandatory) |
17
+ |---|---|
18
+ | email | `email-bootstrap-discovery.instructions.md` |
19
+ | teams | `teams-bootstrap-discovery.instructions.md` |
20
+ | meetings | `meetings-bootstrap-discovery.instructions.md` |
21
+ | sharepoint | `sharepoint-bootstrap-discovery.instructions.md` |
22
+ | onenote | `bootstrap-project/SKILL.md#step-4a` (already shipped v4.7.x — display-name driven) |
23
+ | loop | `loop-bootstrap-discovery.instructions.md` (already shipped v4.6.0) |
24
+ | crm | `crm-bootstrap-discovery.instructions.md` (already shipped v3.11.0) |
25
+ | ado | `ado-bootstrap-discovery.instructions.md` |
26
+
27
+ `blocked-config` is ONLY legitimate when:
28
+
29
+ 1. **The sweep ran** and returned 0 candidates → status is `unresolved` (sweep succeeded but no hits), not `blocked-config`. Use `discovery-empty` annotation in the per-source notes.
30
+ 2. **The sweep COULD not run** because a prerequisite is genuinely missing (e.g. CRM/ADO need `<workspace>/.kushi/config/shared/integrations.yml` populated, SharePoint local-folder enumeration needs a local OneDrive sync path the customer hint cannot infer). In that case, `blocked-config` is correct, and the per-source `next_step` MUST cite the specific missing config field.
31
+
32
+ Any other path that writes `blocked-config` without attempting the sweep is a **defect**.
33
+
34
+ ## Required inputs
35
+
36
+ - `<customer-hint>` — verbatim string the user provided at bootstrap invocation (e.g. `HCA`). Captured by `bootstrap-project` Step 0/1 and persisted to `<project>/bootstrap-status.md` under `Customer Hint:`. Used VERBATIM in v4.8.0 — no fuzzy expansion (deferred to v4.9.0).
37
+ - `<lookback-days>` — defaults to **90** for discovery (longer than the 30-day pull window so historical chats / series still surface). Configurable via `<workspace>/.kushi/config/user/m365-mutable.json#bootstrap.discoveryLookbackDays`.
38
+ - `<project>` — engagement name (already resolved).
39
+ - `<alias>` — current contributor.
40
+
41
+ ## Required outputs
42
+
43
+ For each source whose sweep runs:
44
+
45
+ 1. **Append discovered IDs / URLs / paths to `<engagement-root>/<project>/integrations.yml#boundaries.<source>.<key>`** as plain strings (existing pull-* skills consume strings — do NOT change the array element type). Idempotent: deduplicate by exact-string equality.
46
+
47
+ 2. **Write a sidecar discovery record to `<engagement-root>/<project>/Evidence/_discovery/<YYYY-MM-DD>_<source>_discovery.yml`** with the per-row metadata (`discovered_by`, `discovered_at`, `needs_review`, `query`, `request_id`, `confidence`). Schema:
48
+
49
+ ```yaml
50
+ source: email | teams | meetings | sharepoint
51
+ project: <project>
52
+ customer_hint: '<hint>'
53
+ lookback_days: 90
54
+ discovered_at: '<ISO-8601>'
55
+ discovered_by: <alias>
56
+ query: '<exact WorkIQ prompt>'
57
+ workiq_request_id: '<request-id from response>'
58
+ total_candidates_found: <N>
59
+ candidates_persisted: <M> # ≤ 10 after cap
60
+ candidates_deferred: <N - M> # written to OPEN-QUESTIONS-DRAFT.md
61
+ results:
62
+ - value: '<string written to boundary>'
63
+ label: '<human-readable label>'
64
+ confidence: high | medium | low
65
+ needs_review: true
66
+ ```
67
+
68
+ 3. **If `total_candidates_found > 10`**, persist the top 10 by recency to the boundary and append the remaining N–10 to `<project>/OPEN-QUESTIONS-DRAFT.md` under `## Discovery sweep — candidates over cap` with one row per candidate.
69
+
70
+ 4. **Add a `## Discovery Sweep Results` section to `<project>/bootstrap-status.md`** after `## Context Artifact Status`. Table shape:
71
+
72
+ ```
73
+ ## Discovery Sweep Results
74
+
75
+ | Source | Hint | Query attempted | Candidates found | Persisted | Deferred | Discovered by |
76
+ |---|---|---|---|---|---|---|
77
+ | email | HCA | "In my Inbox..." | 7 | 7 | 0 | ushak |
78
+ | teams | HCA | "In my Teams chats..." | 14 | 10 | 4 | ushak |
79
+ | meetings | HCA | "In my calendar..." | 3 | 3 | 0 | ushak |
80
+ | sharepoint | HCA | "In my SharePoint sites..." | 0 | 0 | 0 | ushak |
81
+ ```
82
+
83
+ ## Behavior matrix (per source)
84
+
85
+ | Sweep result | `boundaries.<source>.<key>` written? | `last_status` | `retry_signal` | Open Questions written? |
86
+ |---|---|---|---|---|
87
+ | 0 candidates | no | `unresolved` (annotated `discovery-empty`) | `user-action` | yes — "Discovery sweep for `<source>` returned 0 hits for `<hint>`; widen hint or seed `boundaries.<source>.<key>` manually" |
88
+ | 1–10 candidates | yes (all) | `completed-with-coverage-gaps` (because rows are `needs_review`) | `watch` | only if any row low-confidence |
89
+ | > 10 candidates | yes (top 10 by recency) | `completed-with-coverage-gaps` | `watch` | yes — "Discovery sweep returned >10 candidates; review the deferred list" |
90
+ | Sweep query failed (WorkIQ error / classified per `fallback-status-reporting.instructions.md`) | no | `deferred` (write marker per `deferred-retry-on-workiq-fail.instructions.md`) | `retry` | no — next refresh will drain |
91
+ | Prerequisite genuinely missing (e.g. CRM shared config empty) | no | `blocked-config` | `user-action` | yes — cite specific missing field |
92
+
93
+ ## Multi-contributor safety
94
+
95
+ When the boundary already has rows from another alias (or the sidecar `Evidence/_discovery/` already has entries):
96
+
97
+ 1. **Append-only** — the new alias's sweep adds rows that are not already present (dedupe by exact-string equality on the boundary value). Never remove or rewrite another alias's row.
98
+ 2. **Sidecar file is per-source-per-date** — multiple contributors on the same day produce separate sidecar files (`2026-05-26_email_discovery-ushak.yml`, `2026-05-26_email_discovery-stand.yml`). Bootstrap-status's Discovery Sweep Results table shows one row per source × alias.
99
+ 3. **`Discovered by` column in bootstrap-status's per-source Context Artifact Status row** cites the most recent discovering alias. Preserve other aliases' rows in `## Contributors who have bootstrapped this project` per `multi-user-shared-files.instructions.md`.
100
+
101
+ ## Rerun behavior
102
+
103
+ When the user re-runs bootstrap on a project that already has populated boundaries:
104
+
105
+ | Boundary state | Sweep behavior |
106
+ |---|---|
107
+ | Empty | Run sweep (full discovery). |
108
+ | Has rows, none `needs_review: true` (all confirmed) | **Skip sweep.** Boundary is gospel — do not re-discover. |
109
+ | Has rows, some `needs_review: true` (sidecar shows confidence < high) | Run sweep. Merge new candidates by ID. Promote previously-discovered candidates from `needs_review: true` → `false` only if user has manually confirmed (i.e. removed the `needs_review` flag from the sidecar). |
110
+ | User passes `--force-rediscover` | Run sweep regardless of state. Merge new candidates; never delete user-confirmed rows. |
111
+
112
+ ## Forbidden behaviors
113
+
114
+ 1. **Declaring `blocked-config` without running the sweep first** for email/teams/meetings/sharepoint — see "The defect" above. CRM/ADO have their own mandates in `crm-bootstrap-discovery` / `ado-bootstrap-discovery`.
115
+ 2. **Auto-narrowing the customer hint** ("HCA" → "HCA Healthcare Inc"). Use the hint verbatim. Smart-expansion is v4.9.0.
116
+ 3. **Discovering across all sources in one mega-query.** Each source has its own narrow WorkIQ prompt (see per-source doctrines). Mega-queries punt to Graph and return empty.
117
+ 4. **Inferring local OneDrive sync paths from the hint** for SharePoint `local_folders[]`. Discovery populates `site_urls[]` only.
118
+ 5. **Calling Graph / `m365_*` directly for discovery.** Per `workiq-only.instructions.md` the four sources covered here use WorkIQ exclusively. The only allowed `m365_*` exceptions remain `m365_list_chat_messages` (parallel structured dump per pull-teams) and the per-source carve-outs already named in their pull-* SKILLs.
119
+
120
+ ## References
121
+
122
+ - `crm-bootstrap-discovery.instructions.md` — the original "must-attempt-before-declaring-disabled" doctrine; this file mirrors its pattern for the WorkIQ-driven sources.
123
+ - `loop-bootstrap-discovery.instructions.md` — Loop-specific discovery + registry shape.
124
+ - `scope-boundaries.instructions.md` — the broader partial-determinism contract; this doctrine is the discovery-time enabler that makes boundaries achievable.
125
+ - `workiq-only.instructions.md` — what discovery is NOT allowed to call (Graph, m365_* for content).
126
+ - `status-taxonomy.instructions.md` — the closed-set status vocabulary; `unresolved` + `discovery-empty` annotation vs. `blocked-config`.
127
+ - `fallback-status-reporting.instructions.md` — how to classify WorkIQ punts during the sweep.
128
+ - `multi-user-shared-files.instructions.md` — append-only rules for the boundary file.
129
+ - `bootstrap-status-format.instructions.md` — where `## Discovery Sweep Results` slots in the report.
@@ -0,0 +1,105 @@
1
+ ---
2
+ applyTo: "**/skills/bootstrap-project/**, **/skills/pull-email/**, **/skills/refresh-project/**"
3
+ description: "Outlook mail folder discovery — single approved WorkIQ phrasing that scans recent Inbox/subfolder contents for the customer hint, ranks folders by hit-density, writes top candidates into mailboxes[]. Source-specific subset of customer-hint-discovery."
4
+ ---
5
+
6
+ # Email bootstrap discovery (kushi v4.8.0+)
7
+
8
+ Governed by `customer-hint-discovery.instructions.md` — read that file first for the orchestration contract, rerun rules, multi-contributor merge behavior, and behavior matrix.
9
+
10
+ ## What this sweep populates
11
+
12
+ | Boundary key | Element shape | Example |
13
+ |---|---|---|
14
+ | `boundaries.email.mailboxes[]` | string — mail folder path relative to mailbox root | `"Inbox"`, `"Inbox/HCA"`, `"FDE/HCA Intake"` |
15
+
16
+ Optional narrowing fields (`sender_domains[]`, `subject_keywords[]`) are NOT populated by the sweep — they are user-supplied narrowing.
17
+
18
+ ## Approved WorkIQ query (the ONLY shape that returns this data)
19
+
20
+ Issued ONCE per bootstrap, per project:
21
+
22
+ ```
23
+ workiq ask -q "In my Outlook mail folders, find the top mail folders that contain emails mentioning '<HINT>' received in the last <N> days. Return a flat table with: folder path (from mailbox root), message count, most recent received date. Sort by message count descending. Do not summarize. Do not truncate. Flat table only."
24
+ ```
25
+
26
+ Substitution rules:
27
+
28
+ - `<HINT>` = the verbatim customer hint from `bootstrap-status.md#Customer Hint` (e.g. `HCA`).
29
+ - `<N>` = `m365-mutable.json#bootstrap.discoveryLookbackDays` (default 90).
30
+
31
+ The phrasing is **natural-language by folder content** — empirically the only shape that returns folder paths. WorkIQ punts on any other shape (see Forbidden phrasings).
32
+
33
+ ## Forbidden phrasings (will fail empirically — do NOT emit)
34
+
35
+ | Forbidden phrasing | Why it fails |
36
+ |---|---|
37
+ | `"List all my Outlook mail folders. Return folder name and folderId for each."` | Enumerate-verb on the folder space punts to `m365_list_mail_folders` / Graph and returns Graph-Explorer guidance instead of data. |
38
+ | `"Search Microsoft 365 for mail folders matching '<hint>'."` | Structured-search verb routes to summary mode, not folder data. |
39
+ | `"What is the folder ID for the '<name>' folder in my mailbox?"` | ID-lookup question punts to Graph. |
40
+ | `"Get the mail folder hierarchy from my Outlook account."` | Hierarchy verb returns prose, not folder paths. |
41
+ | `"$filter=displayName eq '<name>'"` (OData syntax embedded in the query) | Filter syntax fails — WorkIQ does not pass through OData. |
42
+
43
+ ## Parsing the response
44
+
45
+ WorkIQ returns a markdown table. Parse rows where `folder path` is non-empty and `message count >= 1`:
46
+
47
+ 1. Trim each folder path (no leading/trailing slashes).
48
+ 2. Deduplicate against existing `boundaries.email.mailboxes[]`.
49
+ 3. Cap at top 10 by `message count`.
50
+ 4. The remainder (if any) goes to `<project>/OPEN-QUESTIONS-DRAFT.md` per the orchestration doctrine.
51
+ 5. Confidence ranking:
52
+ - `high` — folder path explicitly contains the hint (case-insensitive substring), e.g. `Inbox/HCA Intake` for hint `HCA`.
53
+ - `medium` — message count ≥ 10 AND folder is a known well-known root (`Inbox`, `Sent Items`, `Archive`).
54
+ - `low` — everything else.
55
+
56
+ ## Sidecar file shape
57
+
58
+ Written to `<engagement-root>/<project>/Evidence/_discovery/<YYYY-MM-DD>_email_discovery-<alias>.yml`:
59
+
60
+ ```yaml
61
+ source: email
62
+ project: '<project>'
63
+ customer_hint: '<HINT>'
64
+ lookback_days: 90
65
+ discovered_at: '<ISO-8601>'
66
+ discovered_by: '<alias>'
67
+ query: 'In my Outlook mail folders, find the top mail folders that contain emails mentioning ''<HINT>'' received in the last 90 days. ...'
68
+ workiq_request_id: '<request-id>'
69
+ total_candidates_found: 7
70
+ candidates_persisted: 7
71
+ candidates_deferred: 0
72
+ results:
73
+ - value: 'Inbox/HCA Intake'
74
+ label: 'Inbox/HCA Intake (245 messages, last 2026-05-25)'
75
+ confidence: high
76
+ needs_review: true
77
+ - value: 'Inbox'
78
+ label: 'Inbox (87 messages, last 2026-05-26)'
79
+ confidence: medium
80
+ needs_review: true
81
+ ```
82
+
83
+ `needs_review: true` is set on EVERY row by default. The user clears it manually after confirming the folder.
84
+
85
+ ## Bootstrap-status row
86
+
87
+ The Discovery Sweep Results table row (per `customer-hint-discovery.instructions.md`):
88
+
89
+ ```
90
+ | email | <HINT> | "In my Outlook mail folders..." | 7 | 7 | 0 | <alias> |
91
+ ```
92
+
93
+ ## When this sweep does NOT run
94
+
95
+ - The user already populated `boundaries.email.mailboxes[]` with at least one entry that does NOT carry `needs_review: true` in the sidecar — boundary is gospel; skip sweep.
96
+ - The active profile disables email (`m365-mutable.json#sources.email.enabled = false`) — write `last_status: not-applicable`.
97
+ - WorkIQ itself is unreachable (signed out, EULA pending, CLI missing) — write `last_status: blocked-auth`, retry_signal `user-action`. Do NOT write `blocked-config`.
98
+
99
+ ## References
100
+
101
+ - `customer-hint-discovery.instructions.md` — orchestration contract.
102
+ - `pull-email/SKILL.md` — what consumes `boundaries.email.mailboxes[]` (folder fast-path + root fallback).
103
+ - `workiq-only.instructions.md` — why m365_list_mail_folders / Graph are forbidden.
104
+ - `deferred-retry-on-workiq-fail.instructions.md` — marker shape when sweep returns a WorkIQ error.
105
+ - `status-taxonomy.instructions.md` — `unresolved` vs. `blocked-config` distinction.
@@ -82,4 +82,8 @@ Case-insensitive; ranking `exact > prefix > contains`. Multiple plausible candid
82
82
  09_open-questions.md
83
83
  ```
84
84
 
85
- The skill creates missing folders/files on first use; never overwrites existing files without explicit user approval.
85
+ The skill creates missing folders/files on first use; never overwrites existing files without explicit user approval.
86
+
87
+ ## References (v4.4.7)
88
+
89
+ - Project-name resolution implements the universal fuzzy contract in fuzzy-disambiguation.instructions.md.
@@ -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.