kushi-agents 4.3.0 → 4.4.1

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 (54) hide show
  1. package/package.json +2 -4
  2. package/plugin/agents/kushi.agent.md +3 -3
  3. package/plugin/instructions/ado-engagement-tree.instructions.md +1 -1
  4. package/plugin/instructions/az-auth-conditional.instructions.md +2 -2
  5. package/plugin/instructions/azure-auth-patterns.instructions.md +8 -8
  6. package/plugin/instructions/bootstrap-status-format.instructions.md +37 -2
  7. package/plugin/instructions/cleanup-on-resolution.instructions.md +1 -1
  8. package/plugin/instructions/crm-bootstrap-discovery.instructions.md +1 -1
  9. package/plugin/instructions/deferred-retry-on-workiq-fail.instructions.md +155 -0
  10. package/plugin/instructions/engagement-root-resolution.instructions.md +16 -13
  11. package/plugin/instructions/evidence-layout-canonical.instructions.md +2 -2
  12. package/plugin/instructions/identity-resolution.instructions.md +19 -12
  13. package/plugin/instructions/m365-id-registry.instructions.md +1 -1
  14. package/plugin/instructions/multi-user-shared-files.instructions.md +87 -0
  15. package/plugin/instructions/run-reports.instructions.md +1 -1
  16. package/plugin/instructions/scope-boundaries.instructions.md +4 -4
  17. package/plugin/instructions/side-by-side-config.instructions.md +23 -17
  18. package/plugin/instructions/tracking.instructions.md +1 -1
  19. package/plugin/instructions/workiq-only.instructions.md +6 -4
  20. package/plugin/lib/Get-KushiConfig.ps1 +214 -0
  21. package/plugin/prompts/bootstrap.prompt.md +64 -35
  22. package/plugin/reference-packs/README.md +1 -1
  23. package/plugin/skills/apply-ado-update/SKILL.md +2 -2
  24. package/plugin/skills/ask-project/SKILL.md +1 -1
  25. package/plugin/skills/bootstrap-project/SKILL.md +18 -16
  26. package/plugin/skills/intro/SKILL.md +2 -2
  27. package/plugin/skills/propose-ado-update/SKILL.md +4 -4
  28. package/plugin/skills/pull-ado/SKILL.md +6 -6
  29. package/plugin/skills/pull-crm/SKILL.md +5 -5
  30. package/plugin/skills/pull-email/SKILL.md +2 -2
  31. package/plugin/skills/pull-meetings/SKILL.md +1 -1
  32. package/plugin/skills/pull-onenote/scripts/recapture-section-url.mjs +2 -1
  33. package/plugin/skills/pull-onenote/write-snapshot.mjs +2 -1
  34. package/plugin/skills/pull-sharepoint/SKILL.md +1 -1
  35. package/plugin/skills/pull-teams/SKILL.md +1 -1
  36. package/plugin/skills/refresh-project/SKILL.md +21 -1
  37. package/plugin/skills/self-check/run.ps1 +24 -1
  38. package/plugin/templates/ado-update/integrations-ado-writes.example.yml +1 -1
  39. package/plugin/templates/init/azuredevops.template.json +159 -0
  40. package/plugin/templates/init/dynamics365.template.json +412 -0
  41. package/plugin/templates/init/m365-auth.template.json +5 -5
  42. package/{.github/config/m365-mutable.json.example → plugin/templates/init/m365-mutable.example.json} +1 -1
  43. package/plugin/templates/init/project-integrations.template.yml +2 -2
  44. package/plugin/templates/init/rsi-program-catalog.template.json +107 -0
  45. package/plugin/templates/snapshot/onenote-page.template.md +3 -3
  46. package/src/config-loader.mjs +156 -0
  47. package/src/constants.mjs +54 -18
  48. package/src/copy-assets.mjs +0 -76
  49. package/src/main.mjs +30 -26
  50. package/src/seed-config.mjs +88 -23
  51. package/src/seed-config.test.mjs +150 -0
  52. package/plugin/templates/init/ado-config.template.yml +0 -21
  53. package/plugin/templates/init/crm-config.template.yml +0 -16
  54. /package/{.github/config/m365-auth.json.example → plugin/templates/init/m365-auth.example.json} +0 -0
@@ -21,7 +21,7 @@ host-fallback Graph call, and every CRM/ADO probe MUST honor.
21
21
  Before issuing **any** WorkIQ query, `m365_*` host call, Graph REST call, or CRM/ADO
22
22
  REST call, the skill MUST resolve the boundary it is operating inside from
23
23
  `<engagement-root>/<project>/integrations.yml boundaries:` (per-source) or the
24
- global `<engagement-root>/.project-evidence/{crm,ado}/config.yml` (auth +
24
+ global `<workspace>/.kushi/config/shared/integrations.yml` (auth +
25
25
  tenant + org). The resolved boundary MUST be recorded in the evidence file's
26
26
  `## Source Basis` block, e.g.:
27
27
 
@@ -57,8 +57,8 @@ returns evidence over the same surface — no run-to-run drift.
57
57
 
58
58
  | Skill | Hard prerequisite | Failure message (verbatim) |
59
59
  |---|---|---|
60
- | `pull-crm` | `<engagement-root>/.project-evidence/crm/config.yml` exists with non-placeholder `environmentUrl` | `crm-config-missing — drop a filled config.yml at <engagement-root>/.project-evidence/crm/config.yml. See plugin/templates/init/crm-config.template.yml.` |
61
- | `pull-ado` | `<engagement-root>/.project-evidence/ado/config.yml` exists with non-placeholder `organization` AND `defaultProject` | `ado-config-missing — drop a filled config.yml at <engagement-root>/.project-evidence/ado/config.yml. See plugin/templates/init/ado-config.template.yml.` |
60
+ | `pull-crm` | `<workspace>/.kushi/config/shared/integrations.yml` exists with non-placeholder `crm.environmentUrl` | `crm-config-missing — fill the crm: block in <workspace>/.kushi/config/shared/integrations.yml. See plugin/templates/init/integrations.template.yml.` |
61
+ | `pull-ado` | `<workspace>/.kushi/config/shared/integrations.yml` exists with non-placeholder `ado.organization` AND `ado.defaultProject` | `ado-config-missing — fill the ado: block in <workspace>/.kushi/config/shared/integrations.yml. See plugin/templates/init/integrations.template.yml.` |
62
62
  | `propose-ado-update` | both above + `integrations.yml ado.engagement_id > 0` | per `propose-ado-update/SKILL.md` Prerequisites table |
63
63
 
64
64
  Pre-v3.7.0 behavior allowed `pull-crm` to "narrate from email" when the live
@@ -172,7 +172,7 @@ On first run for a new project, bootstrap MUST:
172
172
  3. For sources where bootstrap cannot auto-populate, write the source as
173
173
  **disabled** (`enabled: false`) with a one-liner in `OPEN-QUESTIONS-DRAFT.md`
174
174
  asking the user to fill the boundary and re-enable.
175
- 4. For CRM and ADO, ALSO check that the global `<engagement-root>/.project-evidence/{crm,ado}/config.yml`
175
+ 4. For CRM and ADO, ALSO check that the global `<workspace>/.kushi/config/shared/integrations.yml`
176
176
  exists and has non-placeholder values; if not, scaffold from
177
177
  `plugin/templates/init/{crm,ado}-config.template.yml` and park in
178
178
  `OPEN-QUESTIONS-DRAFT.md` with the path the user needs to fill.
@@ -5,20 +5,23 @@ description: "Side-by-side config rule — whenever the skill creates or changes
5
5
 
6
6
  # Side-by-side config rule (HARD RULE)
7
7
 
8
- Whenever the skill creates, changes, or extends any template in `<KUSHI_ROOT>/plugin/templates/init/`, it MUST also write the corresponding live file under `<engagement-root>/.project-evidence/`.
8
+ Whenever the skill creates, changes, or extends any template in `<KUSHI_ROOT>/plugin/templates/init/`, it MUST also write the corresponding live file in the workspace under `<workspace>/.kushi/config/user/` (for per-contributor identity + paths) or `<workspace>/.kushi/config/shared/` (for team-owned config the team commits).
9
9
 
10
10
  Templates stay generic; the live file gets the actual filled-in fact. Never leave the user with only the template — that's a half-done config.
11
11
 
12
- ## The two layers
12
+ ## The three layers
13
13
 
14
14
  | Layer | Where | Lifecycle |
15
15
  |---|---|---|
16
16
  | Template (generic) | `<KUSHI_ROOT>/plugin/templates/init/*.template.{json,yml}` | Ships with kushi, never edited per-user |
17
- | Live filled | `<engagement-root>/.project-evidence/*.{json,yml}` | Per-user, OneDrive-synced, edited by skill + user |
17
+ | Live shared | `<workspace>/.kushi/config/shared/*.{json,yml}` | Team-owned, committed; doctrine files overwrite on install, others seed-once |
18
+ | Live user | `<workspace>/.kushi/config/user/*.{json,yml}` | Per-contributor, gitignored; seed-once and preserved across upgrades |
19
+
20
+ Skills read configs via `<install-dest>/lib/Get-KushiConfig.ps1` which resolves `user/` first, then `shared/`.
18
21
 
19
22
  ## Discover → upsert immediately
20
23
 
21
- Any time the skill discovers a high-confidence fact about a project (folder id, chat id, OneNote section id, CRM record id, ADO WI id, stakeholder hint, alias) — upsert it into the live mutable file (`<engagement-root>/.project-evidence/m365/m365-mutable.json` under `m365Mutable.knownSections.<project>.<source>`) **in the same turn it was discovered**, with `discoveredOn: <today>` and `confidence: high|medium|low`.
24
+ Any time the skill discovers a high-confidence fact about a project (folder id, chat id, OneNote section id, CRM record id, ADO WI id, stakeholder hint, alias) — upsert it into the live mutable file (`<workspace>/.kushi/config/user/m365-mutable.json` under `m365Mutable.knownSections.<project>.<source>`) **in the same turn it was discovered**, with `discoveredOn: <today>` and `confidence: high|medium|low`.
22
25
 
23
26
  Don't wait until the end of the run.
24
27
 
@@ -38,19 +41,22 @@ End every bootstrap / fix-up turn by listing the live config files (path + size
38
41
  ## Live config locations
39
42
 
40
43
  ```
41
- <engagement-root>/.project-evidence/
42
- m365/
43
- m365-auth.json ← stable (OneDrive-synced, hand-edited mostly)
44
- m365-mutable.json discovered hints, auto-updated by skill
45
- crm/
46
- config.yml Dataverse env URL, tenant, schema
47
- ado/
48
- config.yml organization, projects, queries
49
- project-evidence.yml (optional) per-engagement override of personal config
44
+ <workspace>/.kushi/config/
45
+ shared/ ← committed; doctrine + team defaults
46
+ azuredevops.json ← ADO org/project/field-map doctrine (overwritten on install)
47
+ dynamics365.json CRM env/schema doctrine (overwritten on install)
48
+ rsi-program-catalog.json ← RSI segment reference (overwritten on install)
49
+ integrations.yml optional team CRM/ADO defaults (seed-once)
50
+ *.example ← reference scaffolds (overwritten on install)
51
+ user/ gitignored; per-contributor
52
+ project-evidence.yml your alias, engagement-root, active projects (seed-once)
53
+ m365-auth.json ← your tenant, default notebook, mailbox folders (seed-once)
54
+ m365-mutable.json ← discovered hints, auto-updated by skill (seed-once)
50
55
  ```
51
56
 
52
- ## Why outside the kushi repo
57
+ ## Why side-by-side
53
58
 
54
- - **Multi-machine portability** — OneDrive-synced beside engagement folders, follows the user wherever they install kushi.
55
- - **Discoverability** — user can see/edit it next to their engagement work and inside their workspace at `<workspace>/.kushi/config/`, not buried in an OS-hidden home directory.
56
- - **Repo-safe** — kushi is meant to be GitHub-published; live filled configs MUST never be committed.
59
+ - **Self-contained** — everything Kushi needs to run lives under `.kushi/`. No `.github/config/` split, no `~/.copilot/` scatter.
60
+ - **Multi-user safe** — the `user/` tier is per-contributor and gitignored; `shared/` is committed once and stays in sync via git.
61
+ - **Discoverable** — visible in VS Code's file tree, not buried in an OS-hidden home directory.
62
+ - **Repo-safe** — `<workspace>/.kushi/.gitignore` (auto-written by installer) excludes `config/user/` so personal IDs never leak.
@@ -77,7 +77,7 @@ kushi_version: 4.1.0
77
77
  - Engagement root: `C:/.../Engagement Assets`
78
78
  - Project folder: `HCA Healthcare`
79
79
  - Alias: `ushak`
80
- - Config: `<workspace>/.kushi/config/project-evidence.yml`
80
+ - Config: `<workspace>/.kushi/config/user/project-evidence.yml`
81
81
 
82
82
  ## Steps taken
83
83
 
@@ -42,13 +42,13 @@ For every M365 source in scope:
42
42
 
43
43
  1. **WorkIQ FIRST and ONLY.** Issue the canonical query from the table below.
44
44
  2. **If WorkIQ returns insufficient content** (empty, truncated, or only a summary when the query asked for verbatim): retry ONCE with the doubled-strict prompt from the same row.
45
- 3. **If WorkIQ still fails or is unavailable**: ask the user to paste the data verbatim. This is a first-class evidence path, not a degradation.
46
- 4. **DO NOT attempt** `m365_get_transcript`, `m365_get_facilitator_notes`, `m365_list_meetings`, `m365_list_events`, `m365_search_files` (for content), `m365_download_file` (for transcript/notes), or any Graph REST URL for the in-scope sources above. These are FORBIDDEN. Calling them is a defect; coverage.md must NOT show them in the attempt trail.
45
+ 3. **If WorkIQ still fails or is unavailable**: follow `deferred-retry-on-workiq-fail.instructions.md` — write a deferred-retry marker under `<project>/Evidence/<alias>/_deferred-retries/`, surface a one-liner in coverage.md and the run report, and **continue the run** for the remaining sources. The next `refresh-project` drains the queue. User-paste remains available as a manual recovery the user MAY do later by populating the artifact directly and deleting the marker — it is NOT the agent's recovery flow.
46
+ 4. **DO NOT attempt** `m365_get_transcript`, `m365_get_facilitator_notes`, `m365_list_meetings`, `m365_list_events`, `m365_search_files` (for content), `m365_download_file` (for transcript/notes), or any Graph REST URL for the in-scope sources above. These are FORBIDDEN — **including as a "last-resort" fallback after WorkIQ fails** (kushi v4.4.1+). Calling them is a defect; coverage.md must NOT show them in the attempt trail. The deferred-retry marker IS the audit trail for WorkIQ failures.
47
47
  5. **Allowed alongside WorkIQ** (structured-data dumps only): `m365_list_chat_messages` for chat-id'd threads → `chat-messages.json`. These run in parallel with the WorkIQ pull, not as a substitute.
48
48
 
49
49
  ## Canonical WorkIQ commands (CODIFIED — do not re-discover)
50
50
 
51
- The CLI is at `<USER_HOME>\.kushi\bin\workiq.cmd` (resolved from `<workspace>/.kushi/config/project-evidence.yml workiq.cli_path`; fall back to PATH; fall back to `~/.kushi/bin/workiq.cmd`).
51
+ The CLI is at `<USER_HOME>\.kushi\bin\workiq.cmd` (resolved from `<workspace>/.kushi/config/user/project-evidence.yml workiq.cli_path`; fall back to PATH; fall back to `~/.kushi/bin/workiq.cmd`).
52
52
 
53
53
  Invocation shape:
54
54
 
@@ -153,7 +153,7 @@ List my Teams meetings between <YYYY-MM-DD> and <YYYY-MM-DD> where the subject c
153
153
 
154
154
  Before the first WorkIQ query in a run:
155
155
 
156
- 1. Resolve CLI path: `<workspace>/.kushi/config/project-evidence.yml workiq.cli_path` → `Get-Command workiq` → `~/.kushi/bin/workiq.cmd`. If none resolves → log `workiq-not-on-path`, write evidence file pointing at install docs, STOP this source.
156
+ 1. Resolve CLI path: `<workspace>/.kushi/config/user/project-evidence.yml workiq.cli_path` → `Get-Command workiq` → `~/.kushi/bin/workiq.cmd`. If none resolves → log `workiq-not-on-path`, write evidence file pointing at install docs, STOP this source.
157
157
  2. Probe with `workiq ask -q "ping"`. If EULA prompt → `workiq accept-eula` once, retry.
158
158
  3. Capture `--version` once into run-log for audit.
159
159
 
@@ -174,6 +174,7 @@ If `Result: SUMMARY-ONLY` after doubled-strict retry: the artifact is acceptable
174
174
 
175
175
  ## Anti-patterns (defects)
176
176
 
177
+ 0. **Falling back to `m365_get_*` / Graph after a WorkIQ failure (kushi v4.4.1+).** FORBIDDEN. Even framed as "a last-resort partial," "just to try," or "we already have a WorkIQ marker so this is harmless." The doctrine is: WorkIQ fail → deferred-retry marker → continue → next refresh retries. Never a Graph escape hatch. See `deferred-retry-on-workiq-fail.instructions.md`.
177
178
  1. **Calling `m365_get_transcript`, `m365_get_facilitator_notes`, `m365_list_meetings`, `m365_list_events` from a kushi pull-* skill.** FORBIDDEN. These tools have a near-100% failure rate in this workspace. Use WorkIQ.
178
179
  2. **Using Graph REST URLs directly** (e.g. `https://graph.microsoft.com/v1.0/me/onlineMeetings/...`) from a kushi pull-* skill. FORBIDDEN for the in-scope M365 sources. Use WorkIQ.
179
180
  3. **Re-discovering the right WorkIQ prompt every run.** FORBIDDEN. Use the codified prompts from the table above. If a new source variant is needed, add a row to the table, do not improvise.
@@ -192,3 +193,4 @@ If `Result: SUMMARY-ONLY` after doubled-strict retry: the artifact is acceptable
192
193
  - `plugin/instructions/ado-bootstrap-discovery.instructions.md` — ADO is OUT of WorkIQ scope; it uses ADO REST.
193
194
  - `plugin/instructions/scope-boundaries.instructions.md` — every pull-* still respects integrations.yml#boundaries.
194
195
  - `plugin/instructions/evidence-thoroughness.instructions.md` — verbatim is the bar; this rule operationalizes how to hit it.
196
+ - `plugin/instructions/deferred-retry-on-workiq-fail.instructions.md` (kushi v4.4.1+) — defines exactly what happens when WorkIQ fails. NO Graph fallback. Marker + continue + inform.
@@ -0,0 +1,214 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Resolve and load a Kushi config file from the two-tier .kushi/config/ layout.
4
+
5
+ .DESCRIPTION
6
+ Kushi configs live in <workspace>/.kushi/config/ split across two subdirs:
7
+ - user/ per-contributor identity & local paths (gitignored)
8
+ - shared/ team-owned, safe to commit (doctrine + team defaults)
9
+
10
+ Lookup order (highest-priority first):
11
+ 1. <workspace>/.kushi/config/user/<name>.<ext>
12
+ 2. <workspace>/.kushi/config/shared/<name>.<ext>
13
+
14
+ By default the file is parsed (JSON or YAML inferred from extension) and
15
+ returned as a PowerShell object. Use -Raw for raw text, or -Path to only
16
+ resolve the path.
17
+
18
+ Throws if the file is missing or appears to still hold template
19
+ placeholder values (sentinel `__FILL_ME_IN__` or `<auto>`). Suppress with
20
+ -AllowPlaceholders (needed for the identity-resolution flow which fills
21
+ `<auto>` on first run).
22
+
23
+ .PARAMETER Name
24
+ Logical config name, e.g. `project-evidence`, `m365-auth`, `azuredevops`.
25
+
26
+ .PARAMETER Extension
27
+ Override extension. Defaults: yml for project-evidence/integrations; json otherwise.
28
+
29
+ .PARAMETER Workspace
30
+ Workspace root. Defaults to the current location.
31
+
32
+ .EXAMPLE
33
+ $auth = Get-KushiConfig -Name 'm365-auth'
34
+ $notebookId = $auth.m365Auth.oneNote.defaultNotebookId
35
+
36
+ .EXAMPLE
37
+ $cfgPath = Get-KushiConfig -Name 'project-evidence' -Path
38
+ #>
39
+ [CmdletBinding()]
40
+ param(
41
+ [Parameter(Mandatory = $true, Position = 0)]
42
+ [string] $Name,
43
+
44
+ [ValidateSet('json', 'yml', 'yaml')]
45
+ [string] $Extension,
46
+
47
+ [string] $Workspace = (Get-Location).Path,
48
+
49
+ [switch] $Raw,
50
+ [switch] $Path,
51
+ [switch] $AllowPlaceholders
52
+ )
53
+
54
+ $ErrorActionPreference = 'Stop'
55
+
56
+ # Schema-aware required-field map (kushi v4.4.1+).
57
+ # For each logical config name, list the dot-paths that MUST be populated
58
+ # (non-empty, non-sentinel) before the config is considered usable. The check
59
+ # runs IN ADDITION to the substring sentinel check.
60
+ $script:RequiredFields = @{
61
+ 'm365-auth' = @(
62
+ 'm365Auth.defaultTenantId',
63
+ 'm365Auth.oneNote.defaultNotebookName',
64
+ 'm365Auth.oneNote.defaultNotebookId',
65
+ 'm365Auth.oneNote.defaultLinkOwner',
66
+ 'm365Auth.emailContext.folders',
67
+ 'm365Auth.sharePointContext.localProjectsRoot'
68
+ )
69
+ 'project-evidence' = @(
70
+ 'identity.alias',
71
+ 'identity.email',
72
+ 'engagement_root'
73
+ )
74
+ # integrations.yml is shared and per-project — required-field check is
75
+ # the consumer skill's job (it knows which sources are enabled).
76
+ 'integrations' = @()
77
+ }
78
+
79
+ # Sentinel substrings that indicate "still a placeholder."
80
+ $script:Sentinels = @(
81
+ '__FILL_ME_IN__',
82
+ '<auto>',
83
+ '<TENANT_ID>',
84
+ '<NOTEBOOK_ID>',
85
+ '<NOTEBOOK_NAME>',
86
+ '<LINK_OWNER>',
87
+ '<SP_LOCAL_ROOT>',
88
+ '<your-alias>',
89
+ '<Your Full Name>',
90
+ 'your.email@example.com'
91
+ )
92
+
93
+ function Test-Sentinel {
94
+ param([string] $Value)
95
+ if ([string]::IsNullOrWhiteSpace($Value)) { return $true }
96
+ foreach ($s in $script:Sentinels) {
97
+ if ($Value -like "*$s*") { return $true }
98
+ }
99
+ return $false
100
+ }
101
+
102
+ function Get-DotPath {
103
+ param([object] $Obj, [string] $DotPath)
104
+ $cur = $Obj
105
+ foreach ($seg in $DotPath -split '\.') {
106
+ if ($null -eq $cur) { return $null }
107
+ if ($cur -is [System.Collections.IDictionary]) {
108
+ $cur = $cur[$seg]
109
+ } else {
110
+ $cur = $cur.$seg
111
+ }
112
+ }
113
+ return $cur
114
+ }
115
+
116
+ function Test-RequiredFields {
117
+ param([string] $Name, [object] $Parsed)
118
+ $required = $script:RequiredFields[$Name]
119
+ if (-not $required) { return @() }
120
+ $missing = @()
121
+ foreach ($field in $required) {
122
+ $val = Get-DotPath -Obj $Parsed -DotPath $field
123
+ if ($null -eq $val) { $missing += $field; continue }
124
+ # Arrays: must have at least one non-sentinel element.
125
+ if ($val -is [System.Collections.IEnumerable] -and -not ($val -is [string])) {
126
+ $arr = @($val)
127
+ if ($arr.Count -eq 0) { $missing += $field; continue }
128
+ $allBlank = $true
129
+ foreach ($item in $arr) {
130
+ if ($item -is [string]) {
131
+ if (-not (Test-Sentinel $item)) { $allBlank = $false; break }
132
+ } else { $allBlank = $false; break }
133
+ }
134
+ if ($allBlank) { $missing += $field }
135
+ continue
136
+ }
137
+ if ($val -is [string] -and (Test-Sentinel $val)) { $missing += $field }
138
+ }
139
+ return ,$missing
140
+ }
141
+
142
+ function Get-DefaultExtension([string] $name) {
143
+ if ($name -match '^(project-evidence|integrations)$') { return 'yml' }
144
+ return 'json'
145
+ }
146
+
147
+ $ext = if ($PSBoundParameters.ContainsKey('Extension')) { $Extension } else { Get-DefaultExtension $Name }
148
+ $fileName = "$Name.$ext"
149
+
150
+ $configRoot = Join-Path $Workspace '.kushi/config'
151
+ $userPath = Join-Path $configRoot (Join-Path 'user' $fileName)
152
+ $sharedPath = Join-Path $configRoot (Join-Path 'shared' $fileName)
153
+
154
+ $resolved = $null
155
+ if (Test-Path -LiteralPath $userPath -PathType Leaf) { $resolved = $userPath }
156
+ elseif (Test-Path -LiteralPath $sharedPath -PathType Leaf) { $resolved = $sharedPath }
157
+
158
+ if (-not $resolved) {
159
+ throw @"
160
+ Kushi config '$Name' not found. Looked in:
161
+ - $userPath
162
+ - $sharedPath
163
+ Run `npx kushi-agents@latest` to (re)seed config files, then edit
164
+ $userPath with your values.
165
+ "@
166
+ }
167
+
168
+ if ($Path) { return $resolved }
169
+
170
+ $raw = Get-Content -LiteralPath $resolved -Raw
171
+
172
+ if (-not $AllowPlaceholders) {
173
+ $sentinelHit = $false
174
+ foreach ($s in $script:Sentinels) {
175
+ if ($raw -like "*$s*") { $sentinelHit = $true; break }
176
+ }
177
+ if ($sentinelHit) {
178
+ throw @"
179
+ Kushi config '$resolved' still has template placeholders (e.g. __FILL_ME_IN__, <auto>, <TENANT_ID>).
180
+ Edit the file with your actual values, or pass -AllowPlaceholders to bypass this check.
181
+ "@
182
+ }
183
+ }
184
+
185
+ if ($Raw) { return $raw }
186
+
187
+ $parsed = switch ($ext) {
188
+ 'json' {
189
+ $raw | ConvertFrom-Json -Depth 100
190
+ }
191
+ { $_ -in 'yml','yaml' } {
192
+ if (Get-Module -ListAvailable -Name 'powershell-yaml') {
193
+ Import-Module powershell-yaml -ErrorAction Stop
194
+ ConvertFrom-Yaml -Yaml $raw
195
+ } else {
196
+ Write-Warning "powershell-yaml not installed; required-field validation skipped. Install with: Install-Module powershell-yaml -Scope CurrentUser"
197
+ $null
198
+ }
199
+ }
200
+ }
201
+
202
+ if (-not $AllowPlaceholders -and $null -ne $parsed) {
203
+ $missing = Test-RequiredFields -Name $Name -Parsed $parsed
204
+ if ($missing.Count -gt 0) {
205
+ throw @"
206
+ Kushi config '$resolved' is missing required fields:
207
+ $(($missing | ForEach-Object { " - $_" }) -join "`n")
208
+ Edit the file with your actual values, or pass -AllowPlaceholders to bypass this check.
209
+ "@
210
+ }
211
+ }
212
+
213
+ if ($null -eq $parsed) { return $raw }
214
+ return $parsed
@@ -1,35 +1,64 @@
1
- ---
2
- name: bootstrap
3
- description: "First-time setup for a new project — scaffold folders, lay configs side-by-side, do an initial 30-day pull, build State."
4
- argument-hint: "Project name (fuzzy-matched under engagement-root); optional initial pull window"
5
- agent: kushi
6
- tools: [search, read/readFile, edit, agent, execute/runInTerminal, execute/getTerminalOutput, 'workiq/*']
7
- ---
8
-
9
- ## User Input
10
-
11
- ```text
12
- ${input:project:Project name, e.g. HCA}
13
- ${input:window:Optional initial pull window — last N days, since YYYY-MM-DD, or YYYY-MM-DD..YYYY-MM-DD (default 30 days)}
14
- ```
15
-
16
- # /bootstrap
17
-
18
- Route to `@Kushi bootstrap <project>`.
19
-
20
- Inputs the agent will resolve:
21
- - `<project>` — the engagement folder name (fuzzy-match under engagement-root if needed).
22
- - `<window>` — defaults to 30 days. Override with `last 60 days`, `since 2026-04-01`, or `2026-04-01..2026-05-01`.
23
-
24
- Reads:
25
- - `<workspace>/.kushi/config/project-evidence.yml` for alias + engagement-root.
26
-
27
- Produces:
28
- - `<engagement-root>/.project-evidence/m365/{m365-auth,m365-mutable}.json` (per-user filled)
29
- - `<engagement-root>/<project>/integrations.yml`
30
- - `<engagement-root>/<project>/Evidence/{contributors.yml, run-log.yml, <alias>/.settings.yml, <alias>/{email,teams,meetings,onenote,sharepoint,crm,ado}/{snapshot,stream}/}`
31
- - `<engagement-root>/<project>/State/{00..09}_*.md`
32
-
33
- Delegates to `bootstrap-project` skill. After scaffolding, calls each `pull-<source>` skill, then `build-state`.
34
-
35
- Tracking: see `tracking.instructions.md`. Write `<workspace>/.kushi/tracking/runs/{{YYYY-MM-DD}}-<project>-bootstrap.md` as the final step.
1
+ ---
2
+ name: bootstrap
3
+ description: "First-time setup for a new project — scaffold folders, lay configs side-by-side, do an initial 30-day pull, build State."
4
+ argument-hint: "Project name (fuzzy-matched under engagement-root); optional initial pull window"
5
+ agent: kushi
6
+ tools: [search, read/readFile, edit, agent, execute/runInTerminal, execute/getTerminalOutput, 'workiq/*']
7
+ ---
8
+
9
+ ## User Input
10
+
11
+ ```text
12
+ ${input:project:Project name, e.g. HCA}
13
+ ${input:window:Optional initial pull window — last N days, since YYYY-MM-DD, or YYYY-MM-DD..YYYY-MM-DD (default 30 days)}
14
+ ```
15
+
16
+ # /bootstrap
17
+
18
+ Route to `@Kushi bootstrap <project>`.
19
+
20
+ Inputs the agent will resolve:
21
+ - `<project>` — the engagement folder name (fuzzy-match under engagement-root if needed).
22
+ - `<window>` — defaults to 30 days. Override with `last 60 days`, `since 2026-04-01`, or `2026-04-01..2026-05-01`.
23
+
24
+ Reads:
25
+ - `<workspace>/.kushi/config/user/project-evidence.yml` for alias + engagement-root.
26
+ - `<workspace>/.kushi/config/user/m365-auth.json` for tenant + default notebook + mailbox-folder hints.
27
+ - `<workspace>/.kushi/config/shared/integrations.yml` for ADO/CRM org-level connections (shared per project).
28
+
29
+ **Step 0 — verify per-user config is filled** (the minimum the bootstrap skill needs before it can discover the rest). Before any pull, call:
30
+
31
+ ```powershell
32
+ & "<install-dest>/lib/Get-KushiConfig.ps1" -Name 'project-evidence' -AllowPlaceholders | Out-Null # tolerates <auto>; identity-resolution fills it
33
+ & "<install-dest>/lib/Get-KushiConfig.ps1" -Name 'm365-auth' # hard-fails on placeholders in required fields
34
+ ```
35
+
36
+ `Get-KushiConfig -Name 'm365-auth'` (kushi v4.4.1+) checks both: (a) no `__FILL_ME_IN__` / sentinel strings anywhere, AND (b) the required-field set is populated:
37
+ `defaultTenantId`, `oneNote.defaultNotebookName`, `oneNote.defaultNotebookId`, `oneNote.defaultLinkOwner`, `emailContext.folders[]` (≥ 1 entry), `sharePointContext.localProjectsRoot`.
38
+
39
+ If it throws, STOP and prompt the user with the exact missing-field list:
40
+
41
+ > ⚠ `<workspace>/.kushi/config/user/m365-auth.json` is missing required fields: `<list from the helper>`. Open it, fill in those values, then re-run `bootstrap`. Everything else (section IDs, calendar series, specific mailbox folder discovery, channel IDs, SharePoint site IDs, CRM `crmRecordId`, ADO work-item IDs) is the bootstrap skill's job to **discover via WorkIQ + per-source probes** — do NOT pre-fill those.
42
+
43
+ Continue only when both helper calls succeed (`project-evidence.yml` is allowed to still have `<auto>` for identity; identity-resolution.instructions.md fills it in Step 0 of the bootstrap skill).
44
+
45
+ **What the bootstrap SKILL discovers (you do NOT need to pre-fill any of this):**
46
+ - Identity (`alias`, `email`, `display_name`) — resolved via WorkIQ if `<auto>`.
47
+ - OneNote section identifiers (`one_sectionFileId`, `one_sectionGroupId`, `one_sectionOneNoteGuid`, `one_notebookSourceDoc`) — discovered from the user-provided section name.
48
+ - Calendar series, meeting `joinUrl`s, Teams chat/channel hints, SharePoint site/web/list IDs — discovered per source.
49
+ - CRM account `crmRecordId` — discovered via the 4-step Dataverse REST sequence.
50
+ - ADO area path / iteration / work-item IDs — discovered via ADO REST.
51
+
52
+ On WorkIQ failure during discovery, per `deferred-retry-on-workiq-fail.instructions.md`: the bootstrap skill writes a marker, surfaces it in the run report, and continues. **It does NOT call `m365_get_*` / Graph as a fallback.** The next `refresh` drains the queue.
53
+
54
+ Produces:
55
+ - `<workspace>/.kushi/config/user/{m365-auth,m365-mutable}.json` (per-user, hand-edited above + auto-populated by Step 4a)
56
+ - `<workspace>/.kushi/config/shared/integrations.yml` (shared, ADO/CRM connection blocks; per-project `boundaries:` lives in the project file below)
57
+ - `<engagement-root>/<project>/integrations.yml` (per-project boundaries + enabled-sources, shared via OneDrive)
58
+ - `<engagement-root>/<project>/Evidence/{contributors.yml, run-log.yml, <alias>/.settings.yml, <alias>/{email,teams,meetings,onenote,sharepoint,crm,ado}/{snapshot,stream}/, <alias>/_deferred-retries/}`
59
+ - `<engagement-root>/<project>/State/{00..09}_*.md` (full profile only)
60
+ - `<engagement-root>/<project>/bootstrap-status.md` (shared, multi-user safe)
61
+
62
+ Delegates to `bootstrap-project` skill. After scaffolding, calls each `pull-<source>` skill, then `build-state` (full profile only).
63
+
64
+ Tracking: see `tracking.instructions.md`. Write `<workspace>/.kushi/tracking/runs/{{YYYY-MM-DD}}-<project>-bootstrap.md` as the final step.
@@ -33,7 +33,7 @@ Skills must always:
33
33
  What does **not** go in a pack:
34
34
 
35
35
  - Per-engagement data (lives under `<project>/Evidence/`)
36
- - Per-user identity / configs (lives in `<workspace>/.kushi/config/project-evidence.yml`)
36
+ - Per-user identity / configs (lives in `<workspace>/.kushi/config/user/project-evidence.yml`)
37
37
  - Skill behavior (lives in `plugin/skills/<skill>/SKILL.md`)
38
38
  - Cross-cutting agent doctrine (lives in `plugin/instructions/*.instructions.md`)
39
39
 
@@ -30,14 +30,14 @@ Belongs to the **`preview`** profile. Opt in via `npx kushi-agents --clawpilot -
30
30
 
31
31
  ## Deterministic config — do not invent paths
32
32
 
33
- Same as `propose-ado-update`. Reads ADO connection from `<engagement-root>/.project-evidence/ado/config.yml`, per-project `engagement_id` and `writes:` block from `<engagement-root>/<project>/integrations.yml ado:`. Never asks for paths the bootstrap layer already resolved.
33
+ Same as `propose-ado-update`. Reads ADO connection from `<workspace>/.kushi/config/shared/integrations.yml`, per-project `engagement_id` and `writes:` block from `<engagement-root>/<project>/integrations.yml ado:`. Never asks for paths the bootstrap layer already resolved.
34
34
 
35
35
  ## Pre-flight (HARD — do not bypass)
36
36
 
37
37
  1. **Resolve engagement root + project** per `engagement-root-resolution.instructions.md`.
38
38
  2. Confirm `<project>/integrations.yml ado.engagement_id > 0`. If 0 → abort with the same message `propose-ado-update` uses ("ADO Initiative not yet linked").
39
39
  3. Confirm `<project>/ado-updates/<YYYY-MM-DD>/proposed.md` exists. If not → tell user to run `@Kushi propose ado <project>` first. Never fabricate a proposal.
40
- 4. Per `azure-auth-patterns.instructions.md` — Section 1 (session pre-check) + Section 3 (ADO tenant validation, using `<engagement-root>/.project-evidence/ado/config.yml`) **must** complete green before any further step.
40
+ 4. Per `azure-auth-patterns.instructions.md` — Section 1 (session pre-check) + Section 3 (ADO tenant validation, using `<workspace>/.kushi/config/shared/integrations.yml`) **must** complete green before any further step.
41
41
 
42
42
  ## Steps (current preview-stub behavior)
43
43
 
@@ -40,7 +40,7 @@ In order, stop on first hit:
40
40
  1. Exact `<engagement-root>/<project>/Evidence/` folder.
41
41
  2. Fuzzy match against `discovery.project_aliases` in any `<engagement-root>/<project>/Evidence/<alias>/.settings.yml`.
42
42
  3. `<engagement-root>/<project>/external-context/` (lighter-weight projects without full bootstrap).
43
- 4. `<workspace>/.kushi/config/project-evidence.yml` `active_projects` list.
43
+ 4. `<workspace>/.kushi/config/user/project-evidence.yml` `active_projects` list.
44
44
 
45
45
  If zero matches → ask user. If multiple → list candidates and ask user to pick. **Echo the resolved root path before answering** so the user can confirm.
46
46
 
@@ -31,6 +31,8 @@ The active profile is read from `kushi-install.json#profile` next to this agent
31
31
 
32
32
  After every run (success or coverage-gaps), write `<project>/bootstrap-status.md` per the format contract in `instructions/bootstrap-status-format.instructions.md`. This is the project's fast-orientation artifact — required tables, normalized status vocabulary (`resolved`, `populated`, `unsynced`, `degraded-list-only`, `throttled-tooManyRequests`, `ado-not-complete`, `completed-with-coverage-gaps`), one-line final status. Do NOT inline run history here; that goes in `update-status.md`.
33
33
 
34
+ > **Multi-contributor safety (kushi v4.4.0+)**: this file is shared via OneDrive. Per `multi-user-shared-files.instructions.md`, every write MUST: (a) absorb sibling conflict copies, (b) preserve other aliases' rows in `## Contributors who have bootstrapped this project`, (c) cite the discovering alias in per-source rows. Same rules apply to `<project>/integrations.yml`, `<project>/OPEN-QUESTIONS-DRAFT.md`, `<project>/Evidence/run-log.yml`, `<project>/Evidence/contributors.yml`.
35
+
34
36
  ## Inputs
35
37
 
36
38
  - `<project>` — engagement name (fuzzy-matched per `engagement-root-resolution.instructions.md`).
@@ -41,7 +43,7 @@ After every run (success or coverage-gaps), write `<project>/bootstrap-status.md
41
43
 
42
44
  ### Step 0 — Identity resolution (REQUIRED, never asks the user)
43
45
 
44
- Per `identity-resolution.instructions.md`. Read `<workspace>/.kushi/config/project-evidence.yml`:
46
+ Per `identity-resolution.instructions.md`. Read `<workspace>/.kushi/config/user/project-evidence.yml`:
45
47
 
46
48
  * If `alias`, `email`, or `display_name` is missing / `<auto>` / matches a placeholder → call WorkIQ once:
47
49
  ```
@@ -49,7 +51,7 @@ Per `identity-resolution.instructions.md`. Read `<workspace>/.kushi/config/proje
49
51
  ```
50
52
  Map `upn → email`, `displayName → display_name`, `mailNickname → alias` (fallback `email.split('@')[0]`).
51
53
  * Persist resolved values back to the YAML (preserve comments).
52
- * Echo one line: `✓ Identity: <displayName> <<UPN>> (alias=<alias>). Edit .kushi/config/project-evidence.yml to override.`
54
+ * Echo one line: `✓ Identity: <displayName> <<UPN>> (alias=<alias>). Edit .kushi/config/user/project-evidence.yml to override.`
53
55
  * If all three are already explicit non-placeholder values → skip silently.
54
56
 
55
57
  Hard stop if WorkIQ returns auth error or empty — print the WorkIQ sign-in hint and exit. Never fall back to asking the user.
@@ -59,13 +61,13 @@ Hard stop if WorkIQ returns auth error or empty — print the WorkIQ sign-in hin
59
61
  Verify in order. Stop on hard failures.
60
62
 
61
63
  1. **OS + host runtime** — display OS + Node/PowerShell version. Informational.
62
- 2. **WorkIQ install (REQUIRED, hard stop)** — read `<workspace>/.kushi/config/project-evidence.yml workiq.cli_path`. If missing, probe known paths:
64
+ 2. **WorkIQ install (REQUIRED, hard stop)** — read `<workspace>/.kushi/config/user/project-evidence.yml workiq.cli_path`. If missing, probe known paths:
63
65
  - `$HOME\.kushi\bin\workiq.cmd`
64
66
  - `$env:LOCALAPPDATA\Programs\WorkIQ\workiq.cmd`
65
67
  - `$env:ProgramFiles\WorkIQ\workiq.cmd`
66
68
  If found, persist path. If not found, ask user for path (or to install). Test with `<workiq.cli_path> --help`. Without WorkIQ, M365 sources will all fail — STOP.
67
- 3. **Conditional az login** — only if `<engagement-root>/.project-evidence/crm/config.yml` OR `<engagement-root>/.project-evidence/ado/config.yml` exists. Per `az-auth-conditional.instructions.md`. Soft warning on failure, never blocking.
68
- 4. **Engagement-root resolution** — per `engagement-root-resolution.instructions.md`. Persist to `<workspace>/.kushi/config/project-evidence.yml engagement_root` if newly resolved.
69
+ 3. **Conditional az login** — only if `<workspace>/.kushi/config/shared/integrations.yml` OR `<workspace>/.kushi/config/shared/integrations.yml` exists. Per `az-auth-conditional.instructions.md`. Soft warning on failure, never blocking.
70
+ 4. **Engagement-root resolution** — per `engagement-root-resolution.instructions.md`. Persist to `<workspace>/.kushi/config/user/project-evidence.yml engagement_root` if newly resolved.
69
71
 
70
72
  Display SETUP summary table with ✅ / ⚙️ / ❌ / ⚠️ / ➖ markers.
71
73
 
@@ -77,20 +79,20 @@ Required live files:
77
79
 
78
80
  | Live file | Template source |
79
81
  |---|---|
80
- | `<workspace>/.kushi/config/project-evidence.yml` | `templates/init/project-evidence.template.yml` (seeded by installer) |
81
- | `<workspace>/.kushi/config/integrations.yml` | `templates/init/integrations.template.yml` (seeded by installer) |
82
- | `<engagement-root>/.project-evidence/m365/m365-auth.json` | `templates/init/m365-auth.template.json` |
83
- | `<engagement-root>/.project-evidence/m365/m365-mutable.json` | `templates/init/m365-mutable.template.json` |
82
+ | `<workspace>/.kushi/config/user/project-evidence.yml` | `templates/init/project-evidence.template.yml` (seeded by installer) |
83
+ | `<workspace>/.kushi/config/shared/integrations.yml` | `templates/init/integrations.template.yml` (seeded by installer) |
84
+ | `<workspace>/.kushi/config/user/m365-auth.json` | `templates/init/m365-auth.template.json` |
85
+ | `<workspace>/.kushi/config/user/m365-mutable.json` | `templates/init/m365-mutable.template.json` |
84
86
  | `<engagement-root>/<project>/integrations.yml` | `templates/init/project-integrations.template.yml` |
85
87
  | `<engagement-root>/<project>/Evidence/contributors.yml` | `templates/init/project-contributors.template.yml` |
86
88
  | `<engagement-root>/<project>/Evidence/<alias>/.settings.yml` | `templates/init/project-user-settings.template.yml` |
87
89
 
88
- Optional (only if user enables CRM/ADO):
90
+ Optional (only if user enables CRM/ADO — both go into `<workspace>/.kushi/config/shared/integrations.yml` `crm:` and `ado:` blocks, NOT separate files):
89
91
 
90
- | Live file | Template source |
92
+ | Live destination | Source |
91
93
  |---|---|
92
- | `<engagement-root>/.project-evidence/crm/config.yml` | `templates/init/crm-config.template.yml` |
93
- | `<engagement-root>/.project-evidence/ado/config.yml` | `templates/init/ado-config.template.yml` |
94
+ | `<workspace>/.kushi/config/shared/integrations.yml` `crm:` block | already seeded by `templates/init/integrations.template.yml` — fill in `crm.environmentUrl` + `crm.tenantId` |
95
+ | `<workspace>/.kushi/config/shared/integrations.yml` `ado:` block | already seeded by `templates/init/integrations.template.yml` — fill in `ado.organization` + `ado.project` |
94
96
 
95
97
  **Boundaries upgrade for existing projects (v3.7.0+):** If `<engagement-root>/<project>/integrations.yml` already exists but has no top-level `boundaries:` key, append the scaffolded `boundaries:` block from `templates/init/project-integrations.template.yml` (preserving every existing key). Then proceed to Step 4 to populate it.
96
98
 
@@ -119,15 +121,15 @@ The `State/` subtree is created **only on `full` profile**. On `standard`, only
119
121
 
120
122
  **Boundaries gate** (kushi v3.7.0+, per `scope-boundaries.instructions.md`): before dispatching any `pull-*`, read `<engagement-root>/<project>/integrations.yml#boundaries` and verify each enabled source has its required boundary key populated. For sources where bootstrap can auto-populate from existing `m365-mutable.json` discovery hints (e.g. a previously-discovered `section_file_id` lands in `boundaries.onenote.section_file_ids`), do so and continue. For sources where the boundary cannot be auto-populated, write the source as **disabled** in `integrations.yml` and add a one-liner to `<project>/OPEN-QUESTIONS-DRAFT.md` (or `State/09_open-questions.md` on `full` profile) asking the user to fill the boundary and re-enable.
121
123
 
122
- For CRM and ADO additionally verify the global config files exist (`<engagement-root>/.project-evidence/{crm,ado}/config.yml`) with non-placeholder values; if missing, scaffold from `templates/init/{crm,ado}-config.template.yml` and park in Open Questions with the path and template reference. **Do NOT auto-improvise** by inferring a tenant/org or by narrating CRM evidence from email — both are explicit anti-patterns in v3.7.0.
124
+ For CRM and ADO additionally verify the shared connection block exists in `<workspace>/.kushi/config/shared/integrations.yml` (`crm:` block with `environmentUrl` + `tenantId`, OR `ado:` block with `organization` + `project`) with non-placeholder values; if missing, prompt the user to fill those two/four fields directly (no separate template files — they live in `templates/init/integrations.template.yml`) and park in Open Questions with the path. **Do NOT auto-improvise** by inferring a tenant/org or by narrating CRM evidence from email — both are explicit anti-patterns in v3.7.0.
123
125
 
124
- **CRM discovery is REQUIRED before declaring `disabled` (kushi v3.11.0+, per `crm-bootstrap-discovery.instructions.md`).** If `<engagement-root>/.project-evidence/crm/config.yml` exists and `az` auth succeeds, bootstrap MUST run the full 4-step Dataverse REST resolution sequence from `pull-crm/SKILL.md#resolution-order-when-crmrecordid-is-unset` (title-first → all matching accounts → wide-text → recent-slice → ask user) against the live endpoint before writing `boundaries.crm.disabled: true`. Any other path that sets `disabled: true` is a defect. If steps 1–4 all return 0, present the top 5 candidates from step 4 to the user before final disposition. Log the full attempt trail (queries + counts + outcome) to the bootstrap refresh-report under `## CRM resolution attempts`. If auth fails or Dataverse is unreachable, leave the boundary empty with `reason: 'crm-auth-unavailable-<date>'` — NOT `disabled: true` — so the next refresh retries.
126
+ **CRM discovery is REQUIRED before declaring `disabled` (kushi v3.11.0+, per `crm-bootstrap-discovery.instructions.md`).** If `<workspace>/.kushi/config/shared/integrations.yml` exists and `az` auth succeeds, bootstrap MUST run the full 4-step Dataverse REST resolution sequence from `pull-crm/SKILL.md#resolution-order-when-crmrecordid-is-unset` (title-first → all matching accounts → wide-text → recent-slice → ask user) against the live endpoint before writing `boundaries.crm.disabled: true`. Any other path that sets `disabled: true` is a defect. If steps 1–4 all return 0, present the top 5 candidates from step 4 to the user before final disposition. Log the full attempt trail (queries + counts + outcome) to the bootstrap refresh-report under `## CRM resolution attempts`. If auth fails or Dataverse is unreachable, leave the boundary empty with `reason: 'crm-auth-unavailable-<date>'` — NOT `disabled: true` — so the next refresh retries.
125
127
 
126
128
  #### Step 4a — Discovery & registry persistence (REQUIRED, kushi v3.7.8+, per `m365-id-registry.instructions.md`)
127
129
 
128
130
  **Doctrine: bootstrap discovers, refresh consumes.** Bootstrap is the ONLY phase that probes WorkIQ to resolve canonical M365 identifiers. Refresh runs MUST read these from `m365-mutable.json#knownSections.<project>` and pass them into the index extractor verbatim. Refresh must NEVER re-discover — that is the source of "OneNote works for me sometimes" non-determinism.
129
131
 
130
- For each enabled source, resolve and persist the canonical lookup keys into `<engagement-root>/.project-evidence/m365/m365-mutable.json#knownSections.<projectKey>`. The schema is fixed — populate every key the source supports:
132
+ For each enabled source, resolve and persist the canonical lookup keys into `<workspace>/.kushi/config/user/m365-mutable.json#knownSections.<projectKey>`. The schema is fixed — populate every key the source supports:
131
133
 
132
134
  ```jsonc
133
135
  "knownSections": {
@@ -138,7 +138,7 @@ Present this verbatim:
138
138
 
139
139
  Before opening, check whether any project is already bootstrapped:
140
140
 
141
- 1. Read `<workspace>/.kushi/config/project-evidence.yml` (personal config). If `active_projects` has entries, pick the most recent as the demo target — substitute that name into `{{active_project}}` placeholders below.
141
+ 1. Read `<workspace>/.kushi/config/user/project-evidence.yml` (personal config). If `active_projects` has entries, pick the most recent as the demo target — substitute that name into `{{active_project}}` placeholders below.
142
142
  2. If none, propose a fictional project named **Contoso Discovery** and tell the user that Try-it prompts will use that name; they can substitute their own at any time.
143
143
 
144
144
  ### Navigation Keywords
@@ -444,6 +444,6 @@ Reply `next` to see the closing cheat sheet, or `done` to exit.
444
444
  - Rule 1.2: Mode selection must always present BOTH options — never auto-route to one mode.
445
445
  - Rule 1.3: In walkthrough mode, each moment must wait for `next` / `skip` / `done` / `try` before advancing — never auto-advance.
446
446
  - Rule 1.4: `done` must work at every moment — never trap the user.
447
- - Rule 1.5: Try-it prompts must use the active project name from `<workspace>/.kushi/config/project-evidence.yml`, OR the placeholder `Contoso Discovery` if no projects are configured. Never invent a real-sounding project name.
447
+ - Rule 1.5: Try-it prompts must use the active project name from `<workspace>/.kushi/config/user/project-evidence.yml`, OR the placeholder `Contoso Discovery` if no projects are configured. Never invent a real-sounding project name.
448
448
  - Rule 1.6: Skill descriptions in this file must match `kushi.agent.md` and the live `SKILL.md` files — `self-check` D-checks catch drift.
449
449
  - Rule 1.7: Demo moments must reflect the actual verb count in `kushi.agent.md`. If a verb is added or removed, this file must be updated in the same commit (`self-check` warns on drift).