kushi-agents 4.4.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 (37) hide show
  1. package/package.json +1 -1
  2. package/plugin/agents/kushi.agent.md +1 -1
  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 +6 -6
  6. package/plugin/instructions/bootstrap-status-format.instructions.md +13 -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 +13 -10
  11. package/plugin/instructions/evidence-layout-canonical.instructions.md +2 -2
  12. package/plugin/instructions/identity-resolution.instructions.md +15 -8
  13. package/plugin/instructions/m365-id-registry.instructions.md +1 -1
  14. package/plugin/instructions/scope-boundaries.instructions.md +4 -4
  15. package/plugin/instructions/workiq-only.instructions.md +4 -2
  16. package/plugin/lib/Get-KushiConfig.ps1 +112 -7
  17. package/plugin/prompts/bootstrap.prompt.md +64 -49
  18. package/plugin/skills/apply-ado-update/SKILL.md +2 -2
  19. package/plugin/skills/bootstrap-project/SKILL.md +10 -10
  20. package/plugin/skills/propose-ado-update/SKILL.md +3 -3
  21. package/plugin/skills/pull-ado/SKILL.md +6 -6
  22. package/plugin/skills/pull-crm/SKILL.md +5 -5
  23. package/plugin/skills/pull-email/SKILL.md +2 -2
  24. package/plugin/skills/pull-meetings/SKILL.md +1 -1
  25. package/plugin/skills/pull-onenote/scripts/recapture-section-url.mjs +2 -1
  26. package/plugin/skills/pull-onenote/write-snapshot.mjs +2 -1
  27. package/plugin/skills/pull-sharepoint/SKILL.md +1 -1
  28. package/plugin/skills/pull-teams/SKILL.md +1 -1
  29. package/plugin/skills/refresh-project/SKILL.md +21 -1
  30. package/plugin/skills/self-check/run.ps1 +24 -1
  31. package/plugin/templates/ado-update/integrations-ado-writes.example.yml +1 -1
  32. package/plugin/templates/init/m365-auth.template.json +5 -5
  33. package/plugin/templates/init/project-integrations.template.yml +2 -2
  34. package/plugin/templates/snapshot/onenote-page.template.md +3 -3
  35. package/src/config-loader.mjs +92 -5
  36. package/plugin/templates/init/ado-config.template.yml +0 -21
  37. package/plugin/templates/init/crm-config.template.yml +0 -16
@@ -1,49 +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/user/project-evidence.yml` for alias + engagement-root.
26
- - `<workspace>/.kushi/config/user/m365-auth.json` for tenant + default notebook + mailbox-folder hints.
27
-
28
- **Step 0 — verify per-user config is filled.** Before any pull, call:
29
-
30
- ```powershell
31
- & "<install-dest>/lib/Get-KushiConfig.ps1" -Name 'project-evidence' -AllowPlaceholders | Out-Null # tolerates <auto>; identity-resolution fills it
32
- & "<install-dest>/lib/Get-KushiConfig.ps1" -Name 'm365-auth' # hard-fails on placeholders
33
- ```
34
-
35
- If `Get-KushiConfig -Name 'm365-auth'` throws (file missing or still `__FILL_ME_IN__`), STOP and prompt the user:
36
-
37
- > `<workspace>/.kushi/config/user/m365-auth.json` still has template placeholders. Open it and fill in your tenant ID, default OneNote notebook ID, mailbox folder names, and SharePoint root before re-running `bootstrap`.
38
-
39
- Continue only when both helper calls succeed.
40
-
41
- Produces:
42
- - `<engagement-root>/.project-evidence/m365/{m365-auth,m365-mutable}.json` (per-user filled)
43
- - `<engagement-root>/<project>/integrations.yml`
44
- - `<engagement-root>/<project>/Evidence/{contributors.yml, run-log.yml, <alias>/.settings.yml, <alias>/{email,teams,meetings,onenote,sharepoint,crm,ado}/{snapshot,stream}/}`
45
- - `<engagement-root>/<project>/State/{00..09}_*.md`
46
-
47
- Delegates to `bootstrap-project` skill. After scaffolding, calls each `pull-<source>` skill, then `build-state`.
48
-
49
- 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.
@@ -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
 
@@ -66,7 +66,7 @@ Verify in order. Stop on hard failures.
66
66
  - `$env:LOCALAPPDATA\Programs\WorkIQ\workiq.cmd`
67
67
  - `$env:ProgramFiles\WorkIQ\workiq.cmd`
68
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.
69
- 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.
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
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.
71
71
 
72
72
  Display SETUP summary table with ✅ / ⚙️ / ❌ / ⚠️ / ➖ markers.
@@ -81,18 +81,18 @@ Required live files:
81
81
  |---|---|
82
82
  | `<workspace>/.kushi/config/user/project-evidence.yml` | `templates/init/project-evidence.template.yml` (seeded by installer) |
83
83
  | `<workspace>/.kushi/config/shared/integrations.yml` | `templates/init/integrations.template.yml` (seeded by installer) |
84
- | `<engagement-root>/.project-evidence/m365/m365-auth.json` | `templates/init/m365-auth.template.json` |
85
- | `<engagement-root>/.project-evidence/m365/m365-mutable.json` | `templates/init/m365-mutable.template.json` |
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` |
86
86
  | `<engagement-root>/<project>/integrations.yml` | `templates/init/project-integrations.template.yml` |
87
87
  | `<engagement-root>/<project>/Evidence/contributors.yml` | `templates/init/project-contributors.template.yml` |
88
88
  | `<engagement-root>/<project>/Evidence/<alias>/.settings.yml` | `templates/init/project-user-settings.template.yml` |
89
89
 
90
- 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):
91
91
 
92
- | Live file | Template source |
92
+ | Live destination | Source |
93
93
  |---|---|
94
- | `<engagement-root>/.project-evidence/crm/config.yml` | `templates/init/crm-config.template.yml` |
95
- | `<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` |
96
96
 
97
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.
98
98
 
@@ -121,15 +121,15 @@ The `State/` subtree is created **only on `full` profile**. On `standard`, only
121
121
 
122
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.
123
123
 
124
- 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.
125
125
 
126
- **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.
127
127
 
128
128
  #### Step 4a — Discovery & registry persistence (REQUIRED, kushi v3.7.8+, per `m365-id-registry.instructions.md`)
129
129
 
130
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.
131
131
 
132
- 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:
133
133
 
134
134
  ```jsonc
135
135
  "knownSections": {
@@ -21,7 +21,7 @@ This skill does **NOT** create a new top-level config file. It reads from the co
21
21
 
22
22
  | What | Where | Maintained by |
23
23
  |---|---|---|
24
- | ADO tenant + org + apiVersion + auth strategy | `<engagement-root>/.project-evidence/ado/config.yml` | `bootstrap-project` (global, one-time) |
24
+ | ADO tenant + org + apiVersion + auth strategy | `<workspace>/.kushi/config/shared/integrations.yml` | `bootstrap-project` (global, one-time) |
25
25
  | Per-project Initiative ID (`engagement_id`), area, title filter, discovery hints | `<engagement-root>/<project>/integrations.yml` under `ado:` | `bootstrap-project` + `pull-ado` (auto-discovers and persists) |
26
26
  | Per-project **writes block** (allowlist, autoApply per field, strategy, fieldRefName) | `<engagement-root>/<project>/integrations.yml` under `ado.writes:` | First run of this skill scaffolds the block from the template, then user edits |
27
27
 
@@ -41,7 +41,7 @@ Refuse to produce `proposed.md` and tell the user exactly what's missing if any
41
41
  |---|---|
42
42
  | `<project>/integrations.yml` exists | "Project not bootstrapped. Run `@Kushi bootstrap <project>` first." |
43
43
  | `ado.engagement_id` > 0 | "ADO Initiative not yet linked for `<project>` (engagement_id is 0). pull-ado will auto-discover on next refresh; once `engagement_id` is set in `integrations.yml`, retry." |
44
- | `<engagement-root>/.project-evidence/ado/config.yml` exists with non-placeholder `organization` | "Global ADO config missing or has placeholder org. Run `@Kushi bootstrap <project>` to scaffold, then fill `.project-evidence/ado/config.yml`." |
44
+ | `<workspace>/.kushi/config/shared/integrations.yml` exists with non-placeholder `organization` | "Global ADO config missing or has placeholder org. Run `@Kushi bootstrap <project>` to scaffold, then fill `.kushi/config/shared/integrations.yml`." |
45
45
  | At least one `Evidence/_Consolidated/<date>_consolidated.md` exists | "No consolidated evidence yet for `<project>`. Run `@Kushi refresh <project>` then `consolidate` first." |
46
46
  | Latest consolidated file ≤ 8 days old | Continue but flag `stale-evidence` at top of `proposed.md`. |
47
47
 
@@ -76,7 +76,7 @@ Refuse to produce `proposed.md` and tell the user exactly what's missing if any
76
76
  ## What this skill does NOT do
77
77
 
78
78
  - Does NOT call any ADO `PATCH` or `POST` endpoint.
79
- - Does NOT create or modify the global `.project-evidence/ado/config.yml`.
79
+ - Does NOT create or modify the global `.kushi/config/shared/integrations.yml`.
80
80
  - Does NOT touch `<project>/integrations.yml` other than appending the `ado.writes:` sub-block on first run (preserving every existing key).
81
81
  - Does NOT re-pull evidence (uses the most recent `_Consolidated/` file as-is).
82
82
  - Does NOT write to the ledger (the ledger is written only by `apply-ado-update`).
@@ -27,9 +27,9 @@ WorkIQ-first per `workiq-first.instructions.md` — but **for ADO, REST is prefe
27
27
  - `<project>` — already-resolved project name.
28
28
  - `<alias>` — current contributor.
29
29
  - `<window>` — date range. For snapshot: ignored (always full re-fetch). For stream: `(from, to)`.
30
- - (read) `<engagement-root>/.project-evidence/ado/config.yml` — connection.
30
+ - (read) `<workspace>/.kushi/config/shared/integrations.yml` — connection.
31
31
  - (read) `<engagement-root>/<project>/integrations.yml#ado` — per-project pinned IDs.
32
- - (read) `<engagement-root>/.project-evidence/m365/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints.
32
+ - (read) `<workspace>/.kushi/config/user/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints.
33
33
  - (read) `<engagement-root>/<project>/Evidence/<alias>/.settings.yml` — per-(project x user) overrides.
34
34
 
35
35
  ## Resolution hints (pinned in mutable)
@@ -42,15 +42,15 @@ WorkIQ-first per `workiq-first.instructions.md` — but **for ADO, REST is prefe
42
42
 
43
43
  This skill is **HARD-fail** without both:
44
44
 
45
- 1. Global config: `<engagement-root>/.project-evidence/ado/config.yml` exists with non-placeholder `organization` AND `defaultProject`.
45
+ 1. Global config: `<workspace>/.kushi/config/shared/integrations.yml` exists with non-placeholder `organization` AND `defaultProject`.
46
46
  2. Per-project boundary: `<engagement-root>/<project>/integrations.yml#boundaries.ado.area_paths` is non-empty (OR `boundaries.ado.work_item_ids` is pinned).
47
47
 
48
48
  If either is missing, refuse with the exact message:
49
49
 
50
50
  ```
51
51
  ado-config-missing — drop a filled config.yml at
52
- <engagement-root>/.project-evidence/ado/config.yml. See template at
53
- plugin/templates/init/ado-config.template.yml. Then add boundaries.ado.area_paths
52
+ <workspace>/.kushi/config/shared/integrations.yml. See template at
53
+ plugin/templates/init/integrations.template.yml (ado: block). Then add boundaries.ado.area_paths
54
54
  to <project>/integrations.yml.
55
55
  ```
56
56
 
@@ -61,7 +61,7 @@ tenant-wide title-fuzzy scan in v3.7.0+.
61
61
  ## Auth (deterministic, no improvisation)
62
62
 
63
63
  ```powershell
64
- $cfg = Get-Content "<engagement-root>/.project-evidence/ado/config.yml" | ConvertFrom-Yaml
64
+ $cfg = Get-Content "<workspace>/.kushi/config/shared/integrations.yml" | ConvertFrom-Yaml
65
65
  $tok = az account get-access-token `
66
66
  --resource $cfg.azDevOpsResourceId `
67
67
  --tenant $cfg.tenantId `
@@ -27,7 +27,7 @@ WorkIQ-first per `workiq-first.instructions.md`. Thoroughness per `evidence-thor
27
27
  - `<project>` — already-resolved project name.
28
28
  - `<alias>` — current contributor.
29
29
  - `<window>` — date range. For snapshot: ignored (always full re-fetch). For stream: `(from, to)`.
30
- - (read) `<engagement-root>/.project-evidence/m365/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints.
30
+ - (read) `<workspace>/.kushi/config/user/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints.
31
31
  - (read) `<engagement-root>/<project>/Evidence/<alias>/.settings.yml` — per-(project x user) overrides.
32
32
 
33
33
  ## Resolution hints (pinned in mutable)
@@ -40,15 +40,15 @@ WorkIQ-first per `workiq-first.instructions.md`. Thoroughness per `evidence-thor
40
40
 
41
41
  This skill is **HARD-fail** without both:
42
42
 
43
- 1. Global config: `<engagement-root>/.project-evidence/crm/config.yml` exists with non-placeholder `environmentUrl`.
43
+ 1. Global config: `<workspace>/.kushi/config/shared/integrations.yml` exists with non-placeholder `environmentUrl`.
44
44
  2. Per-project boundary: `<engagement-root>/<project>/integrations.yml#boundaries.crm.record_ids` OR `boundaries.crm.request_ids` is non-empty.
45
45
 
46
46
  If either is missing, refuse with the exact message:
47
47
 
48
48
  ```
49
49
  crm-config-missing — drop a filled config.yml at
50
- <engagement-root>/.project-evidence/crm/config.yml. See template at
51
- plugin/templates/init/crm-config.template.yml. Then add boundaries.crm.record_ids
50
+ <workspace>/.kushi/config/shared/integrations.yml. See template at
51
+ plugin/templates/init/integrations.template.yml (crm: block). Then add boundaries.crm.record_ids
52
52
  (or request_ids) to <project>/integrations.yml.
53
53
  ```
54
54
 
@@ -195,7 +195,7 @@ If a week file already exists, MERGE (dedupe by event ID, append new events, kee
195
195
 
196
196
  ## Tools (in order)
197
197
 
198
- 1. **Dataverse REST Web API** (preferred for snapshot — gives explicit `$select` + `$expand` control) via `az account get-access-token` against the configured tenant. Read connection from `<engagement-root>/.project-evidence/crm/config.yml`.
198
+ 1. **Dataverse REST Web API** (preferred for snapshot — gives explicit `$select` + `$expand` control) via `az account get-access-token` against the configured tenant. Read connection from `<workspace>/.kushi/config/shared/integrations.yml`.
199
199
  2. **WorkIQ** — only when REST is unavailable; phrase queries to demand verbatim long-text + every annotation (see Step B template above). WorkIQ summarizes by default, so use this path only as fallback.
200
200
  3. **Graph REST** — last resort, soft-fail per `az-auth-conditional.instructions.md`.
201
201
  4. **Ask user** — paste verbatim source content if all above fail.
@@ -26,7 +26,7 @@ Thoroughness per `evidence-thoroughness.instructions.md`; runtime detector + aut
26
26
  - `<project>` — already-resolved project name.
27
27
  - `<alias>` — current contributor.
28
28
  - `<window>` — date range. For snapshot: ignored (always full re-fetch). For stream: `(from, to)`.
29
- - (read) `<engagement-root>/.project-evidence/m365/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints.
29
+ - (read) `<workspace>/.kushi/config/user/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints.
30
30
  - (read) `<engagement-root>/<project>/Evidence/<alias>/.settings.yml` — per-(project x user) overrides.
31
31
 
32
32
  ## Resolution hints (pinned in mutable)
@@ -43,7 +43,7 @@ This skill REFUSES to query unless `<engagement-root>/<project>/integrations.yml
43
43
  - `boundaries.email.subject_keywords` — optional narrowing.
44
44
  - `boundaries.date_window_days` — defaults to 30 if absent.
45
45
 
46
- Every WorkIQ ask, every `m365_search_emails` / `m365_list_emails` call, every Graph fallback MUST be scoped to those mailboxes + (if set) sender_domains + subject_keywords. Empty hits inside the boundary → write Coverage Notes citing the limiting key; do NOT widen the scope.
46
+ Every WorkIQ ask MUST be scoped to those mailboxes + (if set) sender_domains + subject_keywords. Empty hits inside the boundary → write Coverage Notes citing the limiting key; do NOT widen the scope. (`m365_search_emails` / `m365_list_emails` / any Graph call is FORBIDDEN per `workiq-only.instructions.md`; on WorkIQ failure, write a deferred-retry marker per `deferred-retry-on-workiq-fail.instructions.md` and continue.)
47
47
 
48
48
  Refusal message when boundary is missing:
49
49
 
@@ -30,7 +30,7 @@ Auth + retry + error logging per `auth-and-retry.instructions.md`. WorkIQ-only p
30
30
  - `<project>` — already-resolved project name.
31
31
  - `<alias>` — current contributor.
32
32
  - `<window>` — date range. For snapshot: ignored (always full re-fetch). For stream: `(from, to)`.
33
- - (read) `<engagement-root>/.project-evidence/m365/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints (`calendarContext.subjectKeywords`, `calendarContext.knownSeries`).
33
+ - (read) `<workspace>/.kushi/config/user/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints (`calendarContext.subjectKeywords`, `calendarContext.knownSeries`).
34
34
 
35
35
  ## Discovery (snapshot pass — WorkIQ ONLY per `workiq-only.instructions.md`)
36
36
 
@@ -49,7 +49,8 @@ if (!PROJECT || !ENGAGEMENT_ROOT) {
49
49
  process.exit(2);
50
50
  }
51
51
 
52
- const REG_PATH = path.join(ENGAGEMENT_ROOT, '.project-evidence', 'm365', 'm365-mutable.json');
52
+ const WORKSPACE = args.workspace || process.cwd();
53
+ const REG_PATH = path.join(WORKSPACE, '.kushi', 'config', 'user', 'm365-mutable.json');
53
54
  if (!fs.existsSync(REG_PATH)) {
54
55
  console.error(`[recapture-section-url] Registry not found: ${REG_PATH}`);
55
56
  process.exit(3);
@@ -54,7 +54,8 @@ const TIMEOUT_MS = args.timeout || '120000';
54
54
  const PROJECT = args.project;
55
55
  const ROOT = args['engagement-root'];
56
56
  const ALIAS = args.alias || 'ushak';
57
- const MUTABLE_PATH = args.mutable || path.join(ROOT || '.', '.project-evidence', 'm365', 'm365-mutable.json');
57
+ const WORKSPACE = args.workspace || process.cwd();
58
+ const MUTABLE_PATH = args.mutable || path.join(WORKSPACE, '.kushi', 'config', 'user', 'm365-mutable.json');
58
59
 
59
60
  if ((!JSON_PATH && !SECTION_URL) || !PROJECT || !ROOT) {
60
61
  console.error('Usage: (--json <runner-output> | --section-url <url>) --project <name> --engagement-root <root> [--alias ushak] [--mutable <path>] [--timeout 120000]');
@@ -28,7 +28,7 @@ Thoroughness per `evidence-thoroughness.instructions.md`; runtime detector + aut
28
28
  - `<project>` — already-resolved project name.
29
29
  - `<alias>` — current contributor.
30
30
  - `<window>` — date range. For snapshot: ignored (always full re-fetch). For stream: `(from, to)`.
31
- - (read) `<engagement-root>/.project-evidence/m365/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints.
31
+ - (read) `<workspace>/.kushi/config/user/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints.
32
32
  - (read) `<engagement-root>/<project>/Evidence/<alias>/.settings.yml` — per-(project x user) overrides.
33
33
 
34
34
  ## Resolution hints (pinned in mutable)
@@ -28,7 +28,7 @@ Thoroughness per `evidence-thoroughness.instructions.md`; runtime detector + aut
28
28
  - `<project>` — already-resolved project name.
29
29
  - `<alias>` — current contributor.
30
30
  - `<window>` — date range. For snapshot: ignored (always full re-fetch). For stream: `(from, to)`.
31
- - (read) `<engagement-root>/.project-evidence/m365/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints.
31
+ - (read) `<workspace>/.kushi/config/user/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints.
32
32
  - (read) `<engagement-root>/<project>/Evidence/<alias>/.settings.yml` — per-(project x user) overrides.
33
33
 
34
34
  ## Resolution hints (pinned in mutable)
@@ -8,7 +8,7 @@ description: "Incremental refresh for an already-bootstrapped project. Reads run
8
8
 
9
9
  > **Verbatim-by-default**: This orchestrator MUST dispatch every enabled `pull-<source>` skill whose boundary in `integrations.yml#boundaries.<source>` is satisfied. Skipping a source silently is a defect. See `verbatim-by-default.instructions.md`.
10
10
  >
11
- > **Discover once, consume deterministically**: Refresh MUST read canonical M365 IDs from `<engagement-root>/.project-evidence/m365/m365-mutable.json#knownSections.<projectKey>` and pass them verbatim into each `pull-*`'s index-extractor query. **Refresh never re-discovers.** If the project entry is missing or a per-source key is empty, re-dispatch through `bootstrap-project` for that source's discovery only, then resume. See `m365-id-registry.instructions.md`.
11
+ > **Discover once, consume deterministically**: Refresh MUST read canonical M365 IDs from `<workspace>/.kushi/config/user/m365-mutable.json#knownSections.<projectKey>` and pass them verbatim into each `pull-*`'s index-extractor query. **Refresh never re-discovers.** If the project entry is missing or a per-source key is empty, re-dispatch through `bootstrap-project` for that source's discovery only, then resume. See `m365-id-registry.instructions.md`.
12
12
  >
13
13
  > **Per-user refresh report REQUIRED**: At end of run, write `<project>/Evidence/<alias>/refresh-reports/<YYYY-MM-DD-HHmm>_refresh.md` per `run-reports.instructions.md`. Even on a no-op run. Cite the registry: `Resolved IDs sourced from m365-mutable.json#knownSections.<projectKey>`.
14
14
  >
@@ -50,6 +50,26 @@ Profile is read from `kushi-install.json#profile` next to the agent file. Defaul
50
50
  - Else if `sources.<src>.watermark` exists → use `(watermark, today)`.
51
51
  - Else (first refresh after bootstrap, or new source) → fallback to `last 7 days`.
52
52
 
53
+ ### Step 2a — Drain deferred-retry queue (REQUIRED, kushi v4.4.1+, per `deferred-retry-on-workiq-fail.instructions.md`)
54
+
55
+ **Before per-source dispatch**, drain any markers that previous runs deferred. The queue lives at:
56
+
57
+ ```
58
+ <engagement-root>/<project>/Evidence/<alias>/_deferred-retries/*.yml
59
+ ```
60
+
61
+ For each marker (chronological order):
62
+
63
+ 1. Load the YAML marker. Parse `source`, `target`, `window`, `workiq.command`.
64
+ 2. Re-issue the canonical WorkIQ query (NOT the doubled-strict — give the first prompt another chance first; if it fails, then doubled-strict).
65
+ 3. If success → write the artifact to its canonical `Evidence/<alias>/<source>/{snapshot,stream}/` path per the source's normal write rules. Delete the marker. Append a `drained:` entry to the refresh report's `## Deferred-retry drain` section.
66
+ 4. If failure → increment `attempts`, update `last_attempt_at`, leave marker in place. Append a `still-deferred:` entry to the report.
67
+ 5. If `attempts >= 5` after this run → also promote to a row in `<project>/OPEN-QUESTIONS-DRAFT.md` (or `State/09_open-questions.md` on `full` profile) per the doctrine's escalation rule.
68
+
69
+ **NEVER call `m365_get_*` / Graph as a fallback during drain.** A drain failure is just another deferral.
70
+
71
+ After drain, proceed to Step 2 even if some markers remain — the orchestrator never blocks on deferred-retry failures.
72
+
53
73
  ### Step 2 — Per-source dispatch
54
74
 
55
75
  For each enabled source (or just the requested one), call its `pull-<source>` skill with the effective window.
@@ -616,7 +616,7 @@ if ($Deep) {
616
616
  if (-not (Test-Path $layoutInst)) {
617
617
  Add-Finding D14 'Evidence layout' 'warning' "plugin/instructions/evidence-layout-canonical.instructions.md is missing" "Restore the canonical-layout doctrine from kushi v3.12.1." $layoutInst 0
618
618
  }
619
- $allowedSiblings = @('Evidence','State','Reports','.kushi','.kushi-reference','.project-evidence','.vscode','.git')
619
+ $allowedSiblings = @('Evidence','State','Reports','.kushi','.kushi-reference','.vscode','.git')
620
620
  $sourceTokens = @(
621
621
  'email-context','email-summary','email-summaries',
622
622
  'teams-context','teams-summary','teams-summaries',
@@ -655,6 +655,29 @@ if ($Deep) {
655
655
  }
656
656
  }
657
657
  }
658
+
659
+ # D15: legacy path regression guard — no `.project-evidence/{m365,crm,ado}` refs outside CHANGELOG/learnings.
660
+ # v4.4.0+ moved per-user config to `<workspace>/.kushi/config/{user,shared}/`. Any plugin/docs file
661
+ # still referencing the legacy `<engagement-root>/.project-evidence/(m365|crm|ado)` path will mislead
662
+ # skills and authors. CHANGELOG and learnings/ legitimately mention the legacy path for history.
663
+ $legacyPattern = '\.project-evidence[\\/](?:m365|crm|ado)'
664
+ $legacyTargets = @(
665
+ (Join-Path $Root 'plugin'),
666
+ (Join-Path $Root 'docs')
667
+ )
668
+ foreach ($target in $legacyTargets) {
669
+ if (-not (Test-Path $target)) { continue }
670
+ $legacyHits = Get-ChildItem -Path $target -Recurse -File -Include '*.md','*.ps1','*.mjs','*.json','*.yml','*.yaml' -ErrorAction SilentlyContinue |
671
+ Where-Object { $_.FullName -notmatch '[\\/]learnings[\\/]' -and $_.Name -ne 'CHANGELOG.md' } |
672
+ Select-String -Pattern $legacyPattern -ErrorAction SilentlyContinue
673
+ foreach ($m in $legacyHits) {
674
+ # Allow lines that explicitly mark as historical breadcrumb (contain "legacy" or "v4.4.0+" or "was")
675
+ if ($m.Line -match '(?i)\blegacy\b|v4\.4\.0\+|\bwas\b|\breplaces?\b|\bdeprecated\b|\bhistorical\b') { continue }
676
+ $msg = 'Legacy .project-evidence/{m365|crm|ado} reference — v4.4.0+ moved per-user config to <workspace>/.kushi/config/{user,shared}/'
677
+ $fix = "Replace with the new path; if intentionally documenting history, add the word 'legacy' or 'v4.4.0+ replaces' on the same line."
678
+ Add-Finding D15 'Legacy paths' 'warning' $msg $fix $m.Path $m.LineNumber
679
+ }
680
+ }
658
681
  }
659
682
 
660
683
  # === Output ===
@@ -4,7 +4,7 @@
4
4
  # read from the SAME integrations.yml that pull-ado already uses — no separate config file.
5
5
  #
6
6
  # Global ADO connection (tenantId, organization, apiVersion, auth) lives at:
7
- # <engagement-root>/.project-evidence/ado/config.yml
7
+ # <workspace>/.kushi/config/shared/integrations.yml
8
8
  # Do NOT duplicate those keys here.
9
9
 
10
10
  ado:
@@ -15,16 +15,16 @@
15
15
  "dataverse": "https://iscrm.crm.dynamics.com"
16
16
  },
17
17
  "oneNote": {
18
- "defaultNotebookName": "",
19
- "defaultNotebookId": "",
18
+ "defaultNotebookName": "__FILL_ME_IN__",
19
+ "defaultNotebookId": "__FILL_ME_IN__",
20
20
  "defaultSectionResolverUrl": "",
21
21
  "defaultNotebookRootLink": "",
22
- "defaultLinkOwner": ""
22
+ "defaultLinkOwner": "__FILL_ME_IN__"
23
23
  },
24
24
  "emailContext": {
25
25
  "enabled": true,
26
26
  "dateFloor": "",
27
- "folders": [],
27
+ "folders": ["__FILL_ME_IN__"],
28
28
  "includeSubfolders": true,
29
29
  "sourceCoverageLabel": "",
30
30
  "matchingPolicy": {
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "sharePointContext": {
58
58
  "enabled": true,
59
- "localProjectsRoot": "",
59
+ "localProjectsRoot": "__FILL_ME_IN__",
60
60
  "matchingPolicy": {
61
61
  "mode": "fuzzy-folder-name",
62
62
  "rankingOrder": ["exact", "prefix", "contains"],
@@ -3,8 +3,8 @@
3
3
  # AND declares the explicit boundaries inside which Kushi is allowed to query.
4
4
  #
5
5
  # Connection / auth / field mappings are NOT here — those are global at:
6
- # <engagement-root>/.project-evidence/crm/config.yml
7
- # <engagement-root>/.project-evidence/ado/config.yml
6
+ # <workspace>/.kushi/config/shared/integrations.yml
7
+ # <workspace>/.kushi/config/shared/integrations.yml
8
8
  #
9
9
  # Boundaries doctrine: see plugin/instructions/scope-boundaries.instructions.md.
10
10
  # Required boundary keys MUST be filled or the source is disabled (not silently widened).
@@ -20,7 +20,7 @@ captured_at: "<ISO-8601 if captured/user-pasted, else empty>"
20
20
  - **Capture status:** `<last_status>`
21
21
  - **Attempts:** <int>
22
22
  - **Last attempt:** <ts>
23
- - **Source basis:** WorkIQ enumerate + per-page verbatim probe (pull-onenote v2.5.0). Graph `/me/onenote/*` skipped per workspace policy. Per-page retry registry: `.project-evidence/m365/m365-mutable.json#knownSections.<projectKey>.one_pages`.
23
+ - **Source basis:** WorkIQ enumerate + per-page verbatim probe (pull-onenote v2.5.0). Graph `/me/onenote/*` skipped per workspace policy. Per-page retry registry: `.kushi/config/user/m365-mutable.json#knownSections.<projectKey>.one_pages`.
24
24
 
25
25
  <!-- IF last_status == "captured" or "user-pasted" -->
26
26
 
@@ -43,7 +43,7 @@ captured_at: "<ISO-8601 if captured/user-pasted, else empty>"
43
43
 
44
44
  ❌ **page-body-unavailable: WorkIQ returned BODY-NOT-EXPOSED on attempt <N>.**
45
45
 
46
- > Per `pull-onenote` v2.5.0 § "Empirical contract": WorkIQ OneNote body retrieval is non-deterministic — the same page may return a body on a later refresh. This page IS registered for retry in `.project-evidence/m365/m365-mutable.json#knownSections.<projectKey>.one_pages` and will be re-attempted on every refresh until `last_status` becomes `captured` or `user-pasted`. Inferring the page contents from adjacent emails or chat traffic is forbidden.
46
+ > Per `pull-onenote` v2.5.0 § "Empirical contract": WorkIQ OneNote body retrieval is non-deterministic — the same page may return a body on a later refresh. This page IS registered for retry in `.kushi/config/user/m365-mutable.json#knownSections.<projectKey>.one_pages` and will be re-attempted on every refresh until `last_status` becomes `captured` or `user-pasted`. Inferring the page contents from adjacent emails or chat traffic is forbidden.
47
47
 
48
48
  ### next_step
49
49
 
@@ -93,5 +93,5 @@ Per `thoroughness-detector.instructions.md`, the writer must tick all that apply
93
93
  - [ ] If `last_status == captured` or `user-pasted`: AI Narrative Summary (3+ paragraphs) AND verbatim Body section are present and non-empty
94
94
  - [ ] If `last_status == BODY-NOT-EXPOSED`: explicit marker, `next_step` block, and metadata table all present; NO inferred or paraphrased body content
95
95
  - [ ] If `last_status == enumeration-only`: explicit transient marker present; NO body content of any kind
96
- - [ ] Page is registered in `.project-evidence/m365/m365-mutable.json#knownSections.<projectKey>.one_pages` with matching `last_status` and `attempts`
96
+ - [ ] Page is registered in `.kushi/config/user/m365-mutable.json#knownSections.<projectKey>.one_pages` with matching `last_status` and `attempts`
97
97
  - [ ] No fabrication: nothing in this file came from adjacent emails, chats, or other sources to "fill in" what WorkIQ did not return