kushi-agents 4.3.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/package.json +2 -4
  2. package/plugin/agents/kushi.agent.md +2 -2
  3. package/plugin/instructions/azure-auth-patterns.instructions.md +2 -2
  4. package/plugin/instructions/bootstrap-status-format.instructions.md +24 -0
  5. package/plugin/instructions/engagement-root-resolution.instructions.md +3 -3
  6. package/plugin/instructions/identity-resolution.instructions.md +4 -4
  7. package/plugin/instructions/multi-user-shared-files.instructions.md +87 -0
  8. package/plugin/instructions/run-reports.instructions.md +1 -1
  9. package/plugin/instructions/side-by-side-config.instructions.md +23 -17
  10. package/plugin/instructions/tracking.instructions.md +1 -1
  11. package/plugin/instructions/workiq-only.instructions.md +2 -2
  12. package/plugin/lib/Get-KushiConfig.ps1 +109 -0
  13. package/plugin/prompts/bootstrap.prompt.md +15 -1
  14. package/plugin/reference-packs/README.md +1 -1
  15. package/plugin/skills/ask-project/SKILL.md +1 -1
  16. package/plugin/skills/bootstrap-project/SKILL.md +8 -6
  17. package/plugin/skills/intro/SKILL.md +2 -2
  18. package/plugin/skills/propose-ado-update/SKILL.md +1 -1
  19. package/plugin/templates/init/azuredevops.template.json +159 -0
  20. package/plugin/templates/init/dynamics365.template.json +412 -0
  21. package/{.github/config/m365-mutable.json.example → plugin/templates/init/m365-mutable.example.json} +1 -1
  22. package/plugin/templates/init/rsi-program-catalog.template.json +107 -0
  23. package/src/config-loader.mjs +69 -0
  24. package/src/constants.mjs +54 -18
  25. package/src/copy-assets.mjs +0 -76
  26. package/src/main.mjs +30 -26
  27. package/src/seed-config.mjs +88 -23
  28. package/src/seed-config.test.mjs +150 -0
  29. /package/{.github/config/m365-auth.json.example → plugin/templates/init/m365-auth.example.json} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kushi-agents",
3
- "version": "4.3.0",
3
+ "version": "4.4.0",
4
4
  "description": "Install Kushi — multi-source project evidence agent with snapshot+stream capture across Email, Teams, OneNote, SharePoint, Meetings, CRM, ADO. WorkIQ-only for M365 sources (Graph / m365_* FORBIDDEN as fallbacks; user-paste is first-class). Host-agnostic.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,8 +10,6 @@
10
10
  "bin/",
11
11
  "src/",
12
12
  "plugin/",
13
- ".github/config/m365-auth.json.example",
14
- ".github/config/m365-mutable.json.example",
15
13
  ".github/copilot-instructions.kushi.md"
16
14
  ],
17
15
  "engines": {
@@ -43,7 +41,7 @@
43
41
  },
44
42
  "license": "MIT",
45
43
  "scripts": {
46
- "test": "node --test src/check-workiq.test.mjs",
44
+ "test": "node --test src/check-workiq.test.mjs src/seed-config.test.mjs",
47
45
  "smoke": "node scripts/smoke.mjs",
48
46
  "prepublishOnly": "npm test && npm run smoke"
49
47
  },
@@ -98,8 +98,8 @@ When a user message arrives:
98
98
  ## Configuration layout
99
99
 
100
100
  ```
101
- <workspace>/.kushi/config/project-evidence.yml ← personal: alias, engagement_root, active_projects (seeded by installer; never overwritten)
102
- <workspace>/.kushi/config/integrations.yml ← optional global CRM/ADO defaults (seeded by installer; never overwritten)
101
+ <workspace>/.kushi/config/user/project-evidence.yml ← personal: alias, engagement_root, active_projects (seeded by installer; never overwritten)
102
+ <workspace>/.kushi/config/shared/integrations.yml ← optional global CRM/ADO defaults (seeded by installer; never overwritten)
103
103
  <engagement-root>/.project-evidence/ ← per-machine, per-user M365 + CRM + ADO config (OneDrive-synced)
104
104
  m365/m365-auth.json
105
105
  m365/m365-mutable.json
@@ -227,7 +227,7 @@ Maintained alongside `auth-and-retry §3` (canonical) — quick lookup:
227
227
  | Dynamics 365 / CRM | `<engagement-root>/.project-evidence/crm/config.yml` |
228
228
  | Azure DevOps | `<engagement-root>/.project-evidence/ado/config.yml` |
229
229
  | Microsoft Graph / M365 / OneNote | `<engagement-root>/.project-evidence/m365/m365-mutable.json` + `<engagement-root>/.project-evidence/m365/m365-auth.json` |
230
- | WorkIQ CLI path | `<workspace>/.kushi/config/project-evidence.yml` (workspace-scoped) |
231
- | Global integrations (optional) | `<workspace>/.kushi/config/integrations.yml` |
230
+ | WorkIQ CLI path | `<workspace>/.kushi/config/user/project-evidence.yml` (workspace-scoped) |
231
+ | Global integrations (optional) | `<workspace>/.kushi/config/shared/integrations.yml` |
232
232
 
233
233
  Always read tenant IDs, resource URLs, org URLs, and allowed-tenant lists from these files at the start of each run. **Never hardcode them in skills or prompts.** The only acceptable literal in instructions/prompts is the public Microsoft tenant ID `72f988bf-86f1-41af-91ab-2d7cd011db47` in the user-facing `az login --tenant ...` suggestion text.
@@ -79,6 +79,30 @@ Use these exact strings everywhere (table cells, run-log, narrative):
79
79
  - `ado-not-complete` — engagement record not found yet OR ADO linkage missing
80
80
  - `completed-with-coverage-gaps` — final outcome with explicit gaps recorded
81
81
 
82
+ ## Multi-contributor safety (kushi v4.4.0+)
83
+
84
+ `<project>/bootstrap-status.md` is shared across all contributors via OneDrive. Per `multi-user-shared-files.instructions.md`:
85
+
86
+ 1. **Before writing**, scan `<project>/` for sibling conflict copies (e.g. `bootstrap-status-conflict-<alias>-<host>.md` or OneDrive's `bootstrap-status (Stan's conflicted copy *)`). If any exist, merge their content into the canonical file (taking the most recent per-source row) and delete the conflict copies. Log this in the per-user bootstrap report under `## Conflict copies merged`.
87
+ 2. **Preserve other contributors' attribution.** Read the existing file; the `## Contributors who have bootstrapped this project` section MUST keep one row per alias that has ever written it. Update only the row matching the current alias.
88
+ 3. **Per-source rows** in `## Context Artifact Status` reflect the most recent discovery across all contributors. Add a trailing `Discovered by` column showing which alias performed the latest discovery (cross-referenced from each alias's `m365-mutable.json` via `Get-KushiConfig`).
89
+ 4. **The final one-line status** is for the most recent run only and may be overwritten by any contributor.
90
+
91
+ ### Required `## Contributors who have bootstrapped this project` section
92
+
93
+ Append this section immediately after `## Bootstrap Status`:
94
+
95
+ ```
96
+ ## Contributors who have bootstrapped this project
97
+
98
+ | Alias | Display Name | Last Run | Mode | Outcome |
99
+ |---|---|---|---|---|
100
+ | ushak | Usha Kandregula | 2026-05-20 07:42 EDT | bootstrap | bootstrap-complete-with-coverage-gaps |
101
+ | stand | Stan Doe | 2026-05-18 16:01 EDT | refresh | refresh-complete |
102
+ ```
103
+
104
+ Preserve every existing row except the one matching your alias.
105
+
82
106
  ## Sections to AVOID in bootstrap-status
83
107
 
84
108
  Do NOT keep these in `bootstrap-status.md` (they belong elsewhere or become stale):
@@ -11,9 +11,9 @@ The **engagement-root** is the parent folder that contains all of the user's eng
11
11
 
12
12
  Resolve `<engagement-root>` in this order — first match wins:
13
13
 
14
- 1. **`<workspace>/.kushi/config/project-evidence.yml`** — read `engagement_root:` field (preferred).
14
+ 1. **`<workspace>/.kushi/config/user/project-evidence.yml`** — read `engagement_root:` field (preferred).
15
15
  2. **`customer_workspace/FDEDocs/`** — if the user's workspace has this symlink, follow it. Common in FDE-style installs.
16
- 3. **Ask the user** once and persist the answer to `<workspace>/.kushi/config/project-evidence.yml engagement_root`.
16
+ 3. **Ask the user** once and persist the answer to `<workspace>/.kushi/config/user/project-evidence.yml engagement_root`.
17
17
 
18
18
  ## Live config location
19
19
 
@@ -43,7 +43,7 @@ Once `<engagement-root>` is known, individual projects are subfolders:
43
43
  Project name resolution (always fuzzy):
44
44
 
45
45
  1. Match against keys in `<engagement-root>/.project-evidence/m365/m365-mutable.json m365Mutable.knownSections`.
46
- 2. Match against `active_projects:` in `<workspace>/.kushi/config/project-evidence.yml`.
46
+ 2. Match against `active_projects:` in `<workspace>/.kushi/config/user/project-evidence.yml`.
47
47
  3. Match against actual subfolder names under `<engagement-root>`.
48
48
 
49
49
  Case-insensitive; ranking `exact > prefix > contains`. Multiple plausible candidates → ask user to pick. Zero candidates AND verb is `bootstrap` → create the folder. Zero candidates AND verb is anything else → ask user.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  applyTo: "**"
3
- description: "Identity auto-resolution — Kushi never asks the user for alias / email / display_name. On the first bootstrap (or whenever those fields are <auto> / placeholder in .kushi/config/project-evidence.yml), Kushi resolves them from WorkIQ in a single call, persists them, and continues. Skipped entirely if the user has set explicit values."
3
+ description: "Identity auto-resolution — Kushi never asks the user for alias / email / display_name. On the first bootstrap (or whenever those fields are <auto> / placeholder in .kushi/config/user/project-evidence.yml), Kushi resolves them from WorkIQ in a single call, persists them, and continues. Skipped entirely if the user has set explicit values."
4
4
  ---
5
5
 
6
6
  # Identity Resolution — Don't Ask, Probe
@@ -9,7 +9,7 @@ Kushi must not prompt the user for `alias`, `email`, or `display_name`. These ar
9
9
 
10
10
  ## When to resolve
11
11
 
12
- On the **first step of every prompt** that reads contributor identity (bootstrap, refresh, aggregate, ask, fde-*, propose-ado, apply-ado), check `<workspace>/.kushi/config/project-evidence.yml`:
12
+ On the **first step of every prompt** that reads contributor identity (bootstrap, refresh, aggregate, ask, fde-*, propose-ado, apply-ado), check `<workspace>/.kushi/config/user/project-evidence.yml`:
13
13
 
14
14
  * If `alias`, `email`, or `display_name` is **missing**, set to `<auto>`, or matches a placeholder pattern (`<your-alias>`, `<Your Full Name>`, `your.email@example.com`) → **resolve from WorkIQ**.
15
15
  * If all three are explicit non-placeholder values → **skip**. Respect the user's override.
@@ -32,9 +32,9 @@ Map the response:
32
32
 
33
33
  ## After resolution
34
34
 
35
- 1. **Persist back** to `<workspace>/.kushi/config/project-evidence.yml` (preserving comments + indentation). The user sees the resolved values on next open; no surprise.
35
+ 1. **Persist back** to `<workspace>/.kushi/config/user/project-evidence.yml` (preserving comments + indentation). The user sees the resolved values on next open; no surprise.
36
36
  2. **Echo back** to the user, one line:
37
- > ✓ Identity: `Alex Smith <alex@microsoft.com>` (alias=`alex`). Edit `.kushi/config/project-evidence.yml` to override.
37
+ > ✓ Identity: `Alex Smith <alex@microsoft.com>` (alias=`alex`). Edit `.kushi/config/user/project-evidence.yml` to override.
38
38
  3. **Continue** the prompt.
39
39
 
40
40
  ## Failure modes
@@ -0,0 +1,87 @@
1
+ ---
2
+ applyTo: "**/SKILL.md,**/*.prompt.md,**/Evidence/run-log.yml,**/integrations.yml,**/bootstrap-status.md,**/OPEN-QUESTIONS-DRAFT.md,**/State/09_open-questions.md,**/Evidence/contributors.yml"
3
+ ---
4
+
5
+ # Multi-user shared-file safety
6
+
7
+ ## Why this exists
8
+
9
+ A Kushi engagement folder (`<engagement-root>/<project>/`) is shared across all contributors via OneDrive. Several artifacts inside it are **logically shared** — every contributor reads and writes the same path:
10
+
11
+ | Shared artifact | Path | Writers |
12
+ |---|---|---|
13
+ | Per-project integrations | `<project>/integrations.yml` | `bootstrap-project`, `propose-ado-update`, `apply-ado-update`, every `pull-*` that discovers an ID |
14
+ | Bootstrap status snapshot | `<project>/bootstrap-status.md` | `bootstrap-project` (every run, any contributor) |
15
+ | Open questions draft | `<project>/OPEN-QUESTIONS-DRAFT.md` (or `State/09_open-questions.md` on full profile) | `bootstrap-project`, `consolidate-evidence`, `build-state`, `propose-ado-update` |
16
+ | Run log | `<project>/Evidence/run-log.yml` | every `pull-*`, `bootstrap-project`, `refresh-project`, `fde-*` |
17
+ | Contributors list | `<project>/Evidence/contributors.yml` | every `pull-*` (first time an alias touches the project) |
18
+
19
+ Without rules, two contributors running concurrently produce one of:
20
+ - OneDrive conflict-copy (`*-conflict-<alias>-<host>.yml`) — silent loss until manually merged.
21
+ - Last-writer-wins overwrite — silent loss of the other contributor's data.
22
+
23
+ This doctrine codifies the contract every writer must honor. It is **not** a substitute for proper locking, but it makes concurrent runs eventually consistent and recoverable.
24
+
25
+ ## Universal rules (apply to every shared artifact)
26
+
27
+ 1. **Read-modify-write, never blind-write.** Before writing any shared artifact, the skill MUST read the current file, merge its new contribution, and write the merged result. Blind overwrite is forbidden.
28
+ 2. **Detect and absorb conflict-copies.** Before reading the canonical file, glob for sibling files matching `<basename>-conflict-*` or `<basename> (<host>'s conflicted copy *)`. If any exist, merge their contents into the canonical file, then delete the conflict copies. Log the merge in the run report.
29
+ 3. **Stamp every contribution.** Every line / block / list-entry that a skill adds must carry its alias (from `project-evidence.yml`) and an ISO timestamp so future readers (and the merge logic above) can deterministically reconcile.
30
+ 4. **Idempotent appends.** Re-running the same skill in the same window MUST NOT duplicate prior entries. Use a `(alias, source, window_key)` tuple as the dedupe key.
31
+ 5. **Never delete another contributor's contribution.** A skill may only modify or remove entries it created (matched by alias). Cleanup of other aliases' entries is reserved for `consolidate-evidence`.
32
+
33
+ ## Per-artifact contracts
34
+
35
+ ### `<project>/integrations.yml`
36
+
37
+ - **Pattern**: read-modify-write with alias stamp on changed fields.
38
+ - Whenever a skill discovers a new ID (CRM `record_id`, ADO `engagement_id`, OneNote `section_file_id`, etc.), it MUST:
39
+ 1. Read the current file.
40
+ 2. If the target field is non-empty AND differs from the discovered value, write the discovered value into a sibling key `<field>_proposed_<alias>` and add a Q-row to `OPEN-QUESTIONS-DRAFT.md` for human reconciliation. Do not overwrite.
41
+ 3. If the target field is empty (or matches), write the value and append a comment line: `# resolved by <alias> on <YYYY-MM-DD>` immediately above the changed key.
42
+ - The `last_discovery_attempt` / `last_discovery_result` keys MAY be overwritten by any alias (they are intentionally last-writer-wins; the value is "the most recent attempt by anyone").
43
+
44
+ ### `<project>/bootstrap-status.md`
45
+
46
+ - **Pattern**: full rewrite is permitted, but the file MUST carry a `## Contributors who have bootstrapped this project` section listing every alias that has ever written it, with their last-run timestamp.
47
+ - Before rewriting, the skill MUST read the existing Contributors section and preserve every entry whose alias differs from the current alias. Update only its own alias row.
48
+ - The per-source status tables (`OneNote`, `Email`, `Teams`, etc.) reflect the **most recent** discovery across all contributors. Skills MUST cross-reference `m365-mutable.json` (which is per-user) — when a row shows discovered IDs, also note which alias performed the discovery in a trailing `(by <alias>)` cell.
49
+
50
+ ### `<project>/OPEN-QUESTIONS-DRAFT.md` and `<project>/State/09_open-questions.md`
51
+
52
+ - **Pattern**: append-only with dedup.
53
+ - Every question row carries `[asked by <alias> on <YYYY-MM-DD>]` as the trailing column.
54
+ - Before appending a new question, the skill MUST scan existing rows for a question whose normalized text (lowercased, whitespace-collapsed) matches. If a match exists, do not append a duplicate; instead, append `, <alias> on <date>` to the existing row's attribution.
55
+ - Rows are removed only by an explicit `resolve-open-question` action (out of scope here) — never by another `bootstrap` / `refresh` run.
56
+
57
+ ### `<project>/Evidence/run-log.yml`
58
+
59
+ - **Pattern**: structured append + max() merge.
60
+ - Run history (`runs:` list) is append-only. Every entry carries `alias:` and `run_at:`.
61
+ - Per-source watermarks (`sources.<source>.watermark`) use **max()** merge: when writing, the skill reads the current value and writes `max(current, new)`. Never overwrite with an older watermark.
62
+ - Per-source errors (`sources.<source>.errors[]`) are append-only with `alias` + `at` on every entry; dedup on `(alias, code, at)`.
63
+
64
+ ### `<project>/Evidence/contributors.yml`
65
+
66
+ - **Pattern**: append-only, alias-keyed.
67
+ - The first time an alias runs against the project, append `{ alias, display_name, email, first_seen: <YYYY-MM-DD> }`. Subsequent runs MUST NOT modify the entry.
68
+ - `last_seen` MAY be updated by any run, max()-merged.
69
+
70
+ ## Implementation guidance for skill authors
71
+
72
+ - Read `<workspace>/.kushi/config/user/project-evidence.yml` to get your alias via `Get-KushiConfig -Name 'project-evidence'`. Never hardcode aliases.
73
+ - For YAML merges, use `Get-Content -Raw | ConvertFrom-Yaml`, mutate, then `ConvertTo-Yaml`. Preserve comments where possible by line-based merging when structure permits.
74
+ - For Markdown table merges in `OPEN-QUESTIONS-DRAFT.md`, parse the body to a list of rows, dedup by normalized question text, re-emit.
75
+ - On any merge conflict the skill cannot resolve deterministically, write the alternative as a sibling-key `<field>_proposed_<alias>` (for YAML) or a `> Disagreement: <alias-A> says ..., <alias-B> says ...` callout (for Markdown) and add a Q-row to `OPEN-QUESTIONS-DRAFT.md`.
76
+
77
+ ## Out of scope
78
+
79
+ - True file locking (filesystem advisory locks, OneDrive's checkout API) — relies on platform support Kushi can't assume.
80
+ - Server-side merge service — Kushi is local-only by design.
81
+
82
+ ## Related doctrine
83
+
84
+ - `evidence-layout-canonical.instructions.md` — what lives where under `Evidence/`.
85
+ - `run-reports.instructions.md` — per-user run narrative (always under `Evidence/<alias>/`, no concurrency risk).
86
+ - `bootstrap-status-format.instructions.md` — format contract for the shared status artifact.
87
+ - `citation-ledger.instructions.md` — every assertion in shared artifacts must carry a citation; alias attribution is the citation for shared-file entries.
@@ -24,7 +24,7 @@ Critical for multi-user projects: each contributor's runs are independent, and e
24
24
  YYYY-MM-DD-HHmm_<mode>.md
25
25
  ```
26
26
 
27
- - `<alias>` — the contributor running the skill (from `<workspace>/.kushi/config/project-evidence.yml#alias`).
27
+ - `<alias>` — the contributor running the skill (from `<workspace>/.kushi/config/user/project-evidence.yml#alias`).
28
28
  - `<mode>` — one of `bootstrap`, `refresh`, `force-refresh`, `consolidate`.
29
29
  - Timestamp uses the **start time** of the run, in local time, format `YYYY-MM-DD-HHmm` (no seconds, no timezone — local-time prefix is enough for chronological sort).
30
30
 
@@ -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
 
@@ -48,7 +48,7 @@ For every M365 source in scope:
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
 
@@ -0,0 +1,109 @@
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
+ function Get-DefaultExtension([string] $name) {
57
+ if ($name -match '^(project-evidence|integrations)$') { return 'yml' }
58
+ return 'json'
59
+ }
60
+
61
+ $ext = if ($PSBoundParameters.ContainsKey('Extension')) { $Extension } else { Get-DefaultExtension $Name }
62
+ $fileName = "$Name.$ext"
63
+
64
+ $configRoot = Join-Path $Workspace '.kushi/config'
65
+ $userPath = Join-Path $configRoot (Join-Path 'user' $fileName)
66
+ $sharedPath = Join-Path $configRoot (Join-Path 'shared' $fileName)
67
+
68
+ $resolved = $null
69
+ if (Test-Path -LiteralPath $userPath -PathType Leaf) { $resolved = $userPath }
70
+ elseif (Test-Path -LiteralPath $sharedPath -PathType Leaf) { $resolved = $sharedPath }
71
+
72
+ if (-not $resolved) {
73
+ throw @"
74
+ Kushi config '$Name' not found. Looked in:
75
+ - $userPath
76
+ - $sharedPath
77
+ Run `npx kushi-agents@latest` to (re)seed config files, then edit
78
+ $userPath with your values.
79
+ "@
80
+ }
81
+
82
+ if ($Path) { return $resolved }
83
+
84
+ $raw = Get-Content -LiteralPath $resolved -Raw
85
+
86
+ if (-not $AllowPlaceholders) {
87
+ if ($raw -match '__FILL_ME_IN__' -or $raw -match '<auto>') {
88
+ throw @"
89
+ Kushi config '$resolved' still has template placeholders (__FILL_ME_IN__ or <auto>).
90
+ Edit the file with your actual values, or pass -AllowPlaceholders to bypass this check.
91
+ "@
92
+ }
93
+ }
94
+
95
+ if ($Raw) { return $raw }
96
+
97
+ switch ($ext) {
98
+ 'json' {
99
+ return ($raw | ConvertFrom-Json -Depth 100)
100
+ }
101
+ { $_ -in 'yml','yaml' } {
102
+ if (Get-Module -ListAvailable -Name 'powershell-yaml') {
103
+ Import-Module powershell-yaml -ErrorAction Stop
104
+ return ConvertFrom-Yaml -Yaml $raw
105
+ }
106
+ Write-Warning "powershell-yaml not installed; returning raw YAML text. Install with: Install-Module powershell-yaml -Scope CurrentUser"
107
+ return $raw
108
+ }
109
+ }
@@ -22,7 +22,21 @@ Inputs the agent will resolve:
22
22
  - `<window>` — defaults to 30 days. Override with `last 60 days`, `since 2026-04-01`, or `2026-04-01..2026-05-01`.
23
23
 
24
24
  Reads:
25
- - `<workspace>/.kushi/config/project-evidence.yml` for alias + engagement-root.
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.
26
40
 
27
41
  Produces:
28
42
  - `<engagement-root>/.project-evidence/m365/{m365-auth,m365-mutable}.json` (per-user filled)
@@ -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
 
@@ -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
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.
68
- 4. **Engagement-root resolution** — per `engagement-root-resolution.instructions.md`. Persist to `<workspace>/.kushi/config/project-evidence.yml engagement_root` if newly resolved.
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,8 +79,8 @@ 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
+ | `<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) |
82
84
  | `<engagement-root>/.project-evidence/m365/m365-auth.json` | `templates/init/m365-auth.template.json` |
83
85
  | `<engagement-root>/.project-evidence/m365/m365-mutable.json` | `templates/init/m365-mutable.template.json` |
84
86
  | `<engagement-root>/<project>/integrations.yml` | `templates/init/project-integrations.template.yml` |
@@ -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).
@@ -27,7 +27,7 @@ This skill does **NOT** create a new top-level config file. It reads from the co
27
27
 
28
28
  **Resolution is fully deterministic** — never ask the user for paths the bootstrap layer already resolved:
29
29
 
30
- 1. `<engagement-root>` ← `<workspace>/.kushi/config/project-evidence.yml engagement_root` (per `engagement-root-resolution.instructions.md`).
30
+ 1. `<engagement-root>` ← `<workspace>/.kushi/config/user/project-evidence.yml engagement_root` (per `engagement-root-resolution.instructions.md`).
31
31
  2. `<project>` folder ← fuzzy-match per `engagement-root-resolution.instructions.md` (knownSections → active_projects → subfolders).
32
32
  3. `<project>/integrations.yml ado.engagement_id` — REQUIRED. If `0` or missing → see "Prerequisites" below; never invent an ID.
33
33
  4. `<project>/integrations.yml ado.writes` — if missing, append the block from `<KUSHI_ROOT>/plugin/templates/ado-update/integrations-ado-writes.example.yml` and stop for user confirmation of `fieldRefName`.