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.
- package/package.json +1 -1
- package/plugin/agents/kushi.agent.md +1 -1
- package/plugin/instructions/ado-engagement-tree.instructions.md +1 -1
- package/plugin/instructions/az-auth-conditional.instructions.md +2 -2
- package/plugin/instructions/azure-auth-patterns.instructions.md +6 -6
- package/plugin/instructions/bootstrap-status-format.instructions.md +13 -2
- package/plugin/instructions/cleanup-on-resolution.instructions.md +1 -1
- package/plugin/instructions/crm-bootstrap-discovery.instructions.md +1 -1
- package/plugin/instructions/deferred-retry-on-workiq-fail.instructions.md +155 -0
- package/plugin/instructions/engagement-root-resolution.instructions.md +13 -10
- package/plugin/instructions/evidence-layout-canonical.instructions.md +2 -2
- package/plugin/instructions/identity-resolution.instructions.md +15 -8
- package/plugin/instructions/m365-id-registry.instructions.md +1 -1
- package/plugin/instructions/scope-boundaries.instructions.md +4 -4
- package/plugin/instructions/workiq-only.instructions.md +4 -2
- package/plugin/lib/Get-KushiConfig.ps1 +112 -7
- package/plugin/prompts/bootstrap.prompt.md +64 -49
- package/plugin/skills/apply-ado-update/SKILL.md +2 -2
- package/plugin/skills/bootstrap-project/SKILL.md +10 -10
- package/plugin/skills/propose-ado-update/SKILL.md +3 -3
- package/plugin/skills/pull-ado/SKILL.md +6 -6
- package/plugin/skills/pull-crm/SKILL.md +5 -5
- package/plugin/skills/pull-email/SKILL.md +2 -2
- package/plugin/skills/pull-meetings/SKILL.md +1 -1
- package/plugin/skills/pull-onenote/scripts/recapture-section-url.mjs +2 -1
- package/plugin/skills/pull-onenote/write-snapshot.mjs +2 -1
- package/plugin/skills/pull-sharepoint/SKILL.md +1 -1
- package/plugin/skills/pull-teams/SKILL.md +1 -1
- package/plugin/skills/refresh-project/SKILL.md +21 -1
- package/plugin/skills/self-check/run.ps1 +24 -1
- package/plugin/templates/ado-update/integrations-ado-writes.example.yml +1 -1
- package/plugin/templates/init/m365-auth.template.json +5 -5
- package/plugin/templates/init/project-integrations.template.yml +2 -2
- package/plugin/templates/snapshot/onenote-page.template.md +3 -3
- package/src/config-loader.mjs +92 -5
- package/plugin/templates/init/ado-config.template.yml +0 -21
- package/plugin/templates/init/crm-config.template.yml +0 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kushi-agents",
|
|
3
|
-
"version": "4.4.
|
|
3
|
+
"version": "4.4.1",
|
|
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": {
|
|
@@ -100,7 +100,7 @@ When a user message arrives:
|
|
|
100
100
|
```
|
|
101
101
|
<workspace>/.kushi/config/user/project-evidence.yml ← personal: alias, engagement_root, active_projects (seeded by installer; never overwritten)
|
|
102
102
|
<workspace>/.kushi/config/shared/integrations.yml ← optional global CRM/ADO defaults (seeded by installer; never overwritten)
|
|
103
|
-
<
|
|
103
|
+
<workspace>/.kushi/config/ ← per-machine, per-user M365 config + per-project shared ADO/CRM connection (v4.4.0+, replaces legacy `<engagement-root>/.project-evidence/`)
|
|
104
104
|
m365/m365-auth.json
|
|
105
105
|
m365/m365-mutable.json
|
|
106
106
|
crm/config.yml
|
|
@@ -49,7 +49,7 @@ After the Engagement WI ID is resolved:
|
|
|
49
49
|
|
|
50
50
|
## Source files
|
|
51
51
|
|
|
52
|
-
- `<
|
|
52
|
+
- `<workspace>/.kushi/config/shared/integrations.yml` — connection: `tenantId`, `organization`, `defaultProject`, `apiVersion`, `azDevOpsResourceId`.
|
|
53
53
|
- `<engagement-root>/<project>/integrations.yml#ado` — per-project: `engagement_id`, `queryId`, `area_paths`, `iteration_paths`.
|
|
54
54
|
- `<engagement-root>/<project>/integrations.yml#boundaries.ado` — required scope (`area_paths` + optional `work_item_ids`).
|
|
55
55
|
|
|
@@ -10,8 +10,8 @@ The skill calls `az account get-access-token` only when CRM (Dataverse) or ADO a
|
|
|
10
10
|
## Decision logic
|
|
11
11
|
|
|
12
12
|
```
|
|
13
|
-
crm_enabled = Test-Path <
|
|
14
|
-
ado_enabled = Test-Path <
|
|
13
|
+
crm_enabled = Test-Path <workspace>/.kushi/config/shared/integrations.yml
|
|
14
|
+
ado_enabled = Test-Path <workspace>/.kushi/config/shared/integrations.yml
|
|
15
15
|
|
|
16
16
|
if (NOT crm_enabled AND NOT ado_enabled):
|
|
17
17
|
Skip az check entirely. Display "az sign-in skipped (no CRM/ADO configured)".
|
|
@@ -39,7 +39,7 @@ Acquire each token **once per run** (per `auth-and-retry.instructions.md §2`).
|
|
|
39
39
|
### 2.1 CRM / Dataverse
|
|
40
40
|
|
|
41
41
|
```powershell
|
|
42
|
-
$crmConfig = Get-Content "$
|
|
42
|
+
$crmConfig = (Get-Content "$workspace\.kushi\config\shared\integrations.yml" -Raw | ConvertFrom-Yaml).crm # v4.4.0+ — was <engagement-root>\.project-evidence\crm\config.yml
|
|
43
43
|
$tenant = $crmConfig.tenantId
|
|
44
44
|
$crmBaseUrl = $crmConfig.baseUrl
|
|
45
45
|
$crmToken = (az account get-access-token --tenant $tenant --resource $crmBaseUrl --query accessToken -o tsv --only-show-errors 2>&1).Trim()
|
|
@@ -60,7 +60,7 @@ $crmHeaders = @{
|
|
|
60
60
|
ADO's resource is the constant `499b84ac-1321-427f-aa17-267ca6975798` (Visual Studio Team Services). Read it from config — never hardcode:
|
|
61
61
|
|
|
62
62
|
```powershell
|
|
63
|
-
$adoConfig = Get-Content "$
|
|
63
|
+
$adoConfig = (Get-Content "$workspace\.kushi\config\shared\integrations.yml" -Raw | ConvertFrom-Yaml).ado # v4.4.0+ — was <engagement-root>\.project-evidence\ado\config.yml
|
|
64
64
|
$tenant = $adoConfig.tenantId
|
|
65
65
|
$adoRes = $adoConfig.resource # 499b84ac-1321-427f-aa17-267ca6975798
|
|
66
66
|
$adoToken = (az account get-access-token --tenant $tenant --resource $adoRes --query accessToken -o tsv --only-show-errors 2>&1).Trim()
|
|
@@ -127,7 +127,7 @@ Allowed tenants are config-driven, not hardcoded. Validate before the first ADO
|
|
|
127
127
|
|
|
128
128
|
```powershell
|
|
129
129
|
# $account from Section 1
|
|
130
|
-
$adoConfig = Get-Content "$
|
|
130
|
+
$adoConfig = (Get-Content "$workspace\.kushi\config\shared\integrations.yml" -Raw | ConvertFrom-Yaml).ado # v4.4.0+
|
|
131
131
|
$allowedTenants = @($adoConfig.allowedTenantIds)
|
|
132
132
|
$currentTenant = $account.tenantId
|
|
133
133
|
if ($allowedTenants.Count -gt 0 -and $allowedTenants -notcontains $currentTenant) {
|
|
@@ -224,9 +224,9 @@ Maintained alongside `auth-and-retry §3` (canonical) — quick lookup:
|
|
|
224
224
|
|
|
225
225
|
| Service | Config file (engagement-scoped) |
|
|
226
226
|
|---------|---------------------------------|
|
|
227
|
-
| Dynamics 365 / CRM | `<
|
|
228
|
-
| Azure DevOps | `<
|
|
229
|
-
| Microsoft Graph / M365 / OneNote | `<
|
|
227
|
+
| Dynamics 365 / CRM | `<workspace>/.kushi/config/shared/integrations.yml` |
|
|
228
|
+
| Azure DevOps | `<workspace>/.kushi/config/shared/integrations.yml` |
|
|
229
|
+
| Microsoft Graph / M365 / OneNote | `<workspace>/.kushi/config/user/m365-mutable.json` + `<workspace>/.kushi/config/user/m365-auth.json` |
|
|
230
230
|
| WorkIQ CLI path | `<workspace>/.kushi/config/user/project-evidence.yml` (workspace-scoped) |
|
|
231
231
|
| Global integrations (optional) | `<workspace>/.kushi/config/shared/integrations.yml` |
|
|
232
232
|
|
|
@@ -22,8 +22,8 @@ When `bootstrap-project` (or any full refresh / retry workflow) finishes a proje
|
|
|
22
22
|
|
|
23
23
|
| Check | Status | Notes |
|
|
24
24
|
|---|---|---|
|
|
25
|
-
| `.
|
|
26
|
-
| `.
|
|
25
|
+
| `.kushi/config/shared/integrations.yml` filled | resolved \| blocked-auth \| missing | ... |
|
|
26
|
+
| `.kushi/config/shared/integrations.yml` filled | ... | ... |
|
|
27
27
|
| `<project>/integrations.yml` boundaries present | ... | ... |
|
|
28
28
|
| `az` CLI tenant matches | cli-available \| blocked-auth | ... |
|
|
29
29
|
|
|
@@ -54,6 +54,17 @@ When `bootstrap-project` (or any full refresh / retry workflow) finishes a proje
|
|
|
54
54
|
| CRM | blocked-auth | tenant mismatch | `az login --tenant <id>` |
|
|
55
55
|
| Email | throttled-tooManyRequests | WorkIQ rate-limited | retry next refresh |
|
|
56
56
|
|
|
57
|
+
## Deferred Retries (kushi v4.4.1+, per `deferred-retry-on-workiq-fail.instructions.md`)
|
|
58
|
+
|
|
59
|
+
Omit this section entirely if `<project>/Evidence/<alias>/_deferred-retries/` is empty across all contributors.
|
|
60
|
+
|
|
61
|
+
| Source | Target | Attempts | Marker | First seen | Discovered by |
|
|
62
|
+
|---|---|---|---|---|---|
|
|
63
|
+
| meetings | "JD FDE Intake" 2026-05-13 | 1 | `Evidence/ushak/_deferred-retries/2026-05-20-1230_meetings_empty.yml` | 2026-05-20 | ushak |
|
|
64
|
+
| onenote | "Architecture overview" | 2 | `Evidence/ushak/_deferred-retries/2026-05-19-0810_onenote_throttled.yml` | 2026-05-19 | ushak |
|
|
65
|
+
|
|
66
|
+
Markers older than 5 attempts auto-promote to `OPEN-QUESTIONS-DRAFT.md`. The next `refresh` drains the queue (Step 2a in `refresh-project/SKILL.md`) — do NOT manually retry by calling `m365_get_*` / Graph; those are forbidden per `workiq-only.instructions.md`.
|
|
67
|
+
|
|
57
68
|
## Bootstrap Status
|
|
58
69
|
|
|
59
70
|
ONE final line, normalized:
|
|
@@ -28,7 +28,7 @@ When a resolution succeeds, in the same turn, prune:
|
|
|
28
28
|
- Remove inline comments like `# TODO: find this`, `# could not resolve as of YYYY-MM-DD`.
|
|
29
29
|
- Keep `pinned_on` + `pinned_by` (those are durable provenance).
|
|
30
30
|
|
|
31
|
-
### `<
|
|
31
|
+
### `<workspace>/.kushi/config/user/m365-mutable.json`
|
|
32
32
|
- Remove entries under `m365Mutable.unresolved.<project>.<source>` if the same key was just resolved into `m365Mutable.knownSections.<project>.<source>`.
|
|
33
33
|
- Replace any `confidence: 'low'` entry with the new `confidence: 'high'` entry on resolution; do not keep both.
|
|
34
34
|
|
|
@@ -21,7 +21,7 @@ Any other path that writes `disabled: true` is a defect. The correct fallback fo
|
|
|
21
21
|
|
|
22
22
|
```powershell
|
|
23
23
|
# 1. Acquire Dataverse token (per pull-crm Step Auth)
|
|
24
|
-
$config = Get-Content "<
|
|
24
|
+
$config = Get-Content "<workspace>/.kushi/config/shared/integrations.yml" | ConvertFrom-Yaml
|
|
25
25
|
$token = (az account get-access-token --tenant $config.tenant_id --resource $config.base_url --query accessToken -o tsv --only-show-errors).Trim()
|
|
26
26
|
$headers = @{
|
|
27
27
|
Authorization = "Bearer $token"
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**"
|
|
3
|
+
description: "When WorkIQ fails (and the doubled-strict retry also fails), Kushi MUST NOT fall back to Graph / m365_get_* / Dataverse REST as a workaround. Instead, write a deferred-retry marker, surface it in the run report, and continue. The next refresh drains the queue. This is the v4.4.1 hard rule that closes the last Graph-fallback escape hatch."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Deferred-Retry on WorkIQ Failure (HARD RULE, kushi v4.4.1+)
|
|
7
|
+
|
|
8
|
+
## Why this exists
|
|
9
|
+
|
|
10
|
+
`workiq-only.instructions.md` (v3.11.0+) forbade Graph / `m365_get_*` as a **first-class fallback**. But the doctrine's step 3 still said *"ask the user to paste"* — which in practice agents interpreted as:
|
|
11
|
+
|
|
12
|
+
1. *"WorkIQ failed → block the entire run and wait for the human."* (bootstrap stops mid-flight, evidence for OTHER sources is lost)
|
|
13
|
+
2. *"WorkIQ failed → silently try `m365_get_*` since it's right there in the tool list."* (the very anti-pattern workiq-only was meant to kill)
|
|
14
|
+
3. *"WorkIQ failed → skip the source, no record."* (next refresh never retries; the gap is permanent)
|
|
15
|
+
|
|
16
|
+
All three are defects. This rule replaces them with a deterministic, auditable, **non-blocking** flow.
|
|
17
|
+
|
|
18
|
+
## The rule (HARD)
|
|
19
|
+
|
|
20
|
+
When a WorkIQ call fails (auth error, empty after doubled-strict retry, throttled, CLI error, or any non-success), Kushi MUST:
|
|
21
|
+
|
|
22
|
+
1. **NOT call Graph, `m365_get_*`, or any other M365 host tool as a fallback.** No exceptions. Even if the tool is right there and looks like it would work. The doctrine in `workiq-only.instructions.md` is absolute.
|
|
23
|
+
2. **Write a deferred-retry marker** (see schema below).
|
|
24
|
+
3. **Surface a one-liner** in coverage.md, the per-user run report, and the project's `bootstrap-status.md` `## Deferred Retries` section.
|
|
25
|
+
4. **Continue the run** for all other sources. Never block the orchestrator on a single source's failure.
|
|
26
|
+
5. **Inform the user** at end-of-run with the count of deferred items and the path to the queue directory.
|
|
27
|
+
|
|
28
|
+
The next `refresh-project` run drains the queue first (Step 2a). On success, the marker is deleted. On repeated failure, the marker's `attempts` counter increments and the user is re-informed.
|
|
29
|
+
|
|
30
|
+
## Marker file location
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
<engagement-root>/<project>/Evidence/<alias>/_deferred-retries/<YYYY-MM-DD-HHmm>_<source>_<short-reason>.yml
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- Per-contributor (`<alias>`) — never shared, so multi-user safety is moot.
|
|
37
|
+
- Sortable by filename (chronological).
|
|
38
|
+
- One marker per failed call. Do NOT batch — granular markers make retry deterministic.
|
|
39
|
+
|
|
40
|
+
## Marker schema
|
|
41
|
+
|
|
42
|
+
```yaml
|
|
43
|
+
# Deferred-retry marker — auto-generated. Do NOT hand-edit.
|
|
44
|
+
# Drained by refresh-project Step 2a. Deleted on retry success.
|
|
45
|
+
schema_version: 1
|
|
46
|
+
source: meetings # one of: email, teams, meetings, onenote, sharepoint, crm, ado, misc, identity
|
|
47
|
+
project: HCA
|
|
48
|
+
alias: ushak
|
|
49
|
+
created_at: 2026-05-20T12:30:00Z
|
|
50
|
+
attempts: 1
|
|
51
|
+
last_attempt_at: 2026-05-20T12:30:00Z
|
|
52
|
+
window: { from: '2026-04-20', to: '2026-05-20' }
|
|
53
|
+
target:
|
|
54
|
+
# source-specific identifying fields — exactly what the retry needs to re-issue the call
|
|
55
|
+
subject: "JD FDE Intake"
|
|
56
|
+
date: "2026-05-13"
|
|
57
|
+
workiq:
|
|
58
|
+
command: 'workiq ask -q "Find the Teams meeting titled \"JD FDE Intake\" that occurred on 2026-05-13..."'
|
|
59
|
+
request_id: '54d9c6bc-6e56-43b6-9eb7-ac23e86e2cc0' # if WorkIQ returned one before failing
|
|
60
|
+
error_class: 'empty-after-doubled-strict' # auth-error | empty | empty-after-doubled-strict | throttled | cli-error | timeout
|
|
61
|
+
error_message: 'WorkIQ returned a summary only after both prompts.'
|
|
62
|
+
user_message: |
|
|
63
|
+
Could not retrieve the full verbatim transcript of "JD FDE Intake" (2026-05-13) via WorkIQ.
|
|
64
|
+
The next refresh will retry automatically. To populate now, paste the transcript into
|
|
65
|
+
Evidence/ushak/meetings/snapshot/2026-05-13_jd-fde-intake_transcript.md and mark this marker
|
|
66
|
+
resolved by deleting the file.
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Producer contract (every pull-* skill + identity-resolution)
|
|
70
|
+
|
|
71
|
+
Before considering a source "failed and skipped", the skill MUST:
|
|
72
|
+
|
|
73
|
+
1. Call WorkIQ once with the canonical prompt from `workiq-only.instructions.md`.
|
|
74
|
+
2. On weak result, call the doubled-strict retry prompt from the same table.
|
|
75
|
+
3. If both fail:
|
|
76
|
+
- Write the marker per the schema above.
|
|
77
|
+
- Add a row to coverage.md: `Source: WorkIQ → DEFERRED (marker: <relative-path>)`.
|
|
78
|
+
- Continue the run. Do NOT throw. Do NOT call any other M365 tool.
|
|
79
|
+
|
|
80
|
+
Pseudocode (PowerShell shape):
|
|
81
|
+
|
|
82
|
+
```powershell
|
|
83
|
+
$result = Invoke-WorkIQ -Query $canonical
|
|
84
|
+
if (-not (Test-Sufficient $result)) {
|
|
85
|
+
$result = Invoke-WorkIQ -Query $doubledStrict
|
|
86
|
+
}
|
|
87
|
+
if (-not (Test-Sufficient $result)) {
|
|
88
|
+
Write-DeferredRetryMarker -Source $src -Target $target -Window $win -Workiq $workiqMeta
|
|
89
|
+
Add-CoverageRow -Source 'WorkIQ' -Status 'DEFERRED' -MarkerPath $markerPath
|
|
90
|
+
return # CONTINUE the orchestrator with the next source. NEVER call m365_get_*.
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Consumer contract (refresh-project)
|
|
95
|
+
|
|
96
|
+
`refresh-project` SKILL Step 2a (NEW, REQUIRED) runs **before** the per-source dispatch loop:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
For each <alias>/_deferred-retries/*.yml in chronological order:
|
|
100
|
+
- Load marker
|
|
101
|
+
- Re-issue the canonical WorkIQ query with original window + target
|
|
102
|
+
- If success → write artifact to its canonical Evidence/ path, delete marker, log "drained" in refresh report
|
|
103
|
+
- If failure → increment attempts, update last_attempt_at, leave marker in place, log "still-deferred"
|
|
104
|
+
After drain → run normal Step 2 per-source dispatch
|
|
105
|
+
After Step 2 → report drain results in the run report's `## Deferred-retry drain` section
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
If `attempts` reaches 5, do NOT keep silently retrying. Promote to a project-level Open Question:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
- [ ] DEFERRED-RETRY (5 attempts): <source> for <target>. See <marker-path>. Manual intervention required (paste verbatim, or remove the marker if no longer needed).
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## bootstrap-status.md integration
|
|
115
|
+
|
|
116
|
+
`bootstrap-status-format.instructions.md` mandates a `## Deferred Retries` section. Format:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
## Deferred Retries
|
|
120
|
+
|
|
121
|
+
| Source | Target | Attempts | Marker | First seen |
|
|
122
|
+
|---|---|---|---|---|
|
|
123
|
+
| meetings | JD FDE Intake 2026-05-13 | 1 | Evidence/ushak/_deferred-retries/2026-05-20-1230_meetings_empty.yml | 2026-05-20 |
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Section is omitted entirely when no markers exist.
|
|
127
|
+
|
|
128
|
+
## What "informing the user" looks like
|
|
129
|
+
|
|
130
|
+
At end-of-run summary (every `pull-*`, `bootstrap-project`, `refresh-project`):
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
⚠ Deferred retries this run: 2 (will retry on next `refresh <project>`)
|
|
134
|
+
- meetings/JD FDE Intake 2026-05-13 → Evidence/ushak/_deferred-retries/2026-05-20-1230_meetings_empty.yml
|
|
135
|
+
- onenote/Architecture overview → Evidence/ushak/_deferred-retries/2026-05-20-1231_onenote_throttled.yml
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Never silent. Never collapsed. The user sees exactly which calls deferred and where to find the markers.
|
|
139
|
+
|
|
140
|
+
## Anti-patterns (defects)
|
|
141
|
+
|
|
142
|
+
1. **Calling `m365_get_*` / Graph REST after a WorkIQ failure.** FORBIDDEN. Even "just to check if it works." Even "as a last resort." Write the marker; move on.
|
|
143
|
+
2. **Throwing / blocking the orchestrator on a single source's WorkIQ failure.** FORBIDDEN. Bootstrap and refresh dispatch every enabled source; a single failure must not stop the others.
|
|
144
|
+
3. **Silently skipping a source with no marker.** FORBIDDEN. If WorkIQ failed and you did NOT write a marker, the user has no signal and `refresh` will never retry. Always mark.
|
|
145
|
+
4. **Asking the user to paste mid-run as the primary recovery.** Pasting is a manual override the user MAY do later (by populating the artifact directly and deleting the marker). It is NOT the agent's recovery flow. The agent's recovery flow is: mark + continue + inform.
|
|
146
|
+
5. **Promoting markers to OPEN-QUESTIONS-DRAFT.md before 5 attempts.** Too noisy. Let refresh drain naturally.
|
|
147
|
+
6. **Storing markers outside `Evidence/<alias>/_deferred-retries/`.** Wrong location = the drainer can't find them. Wrong scope = breaks multi-user (markers are per-contributor by design).
|
|
148
|
+
|
|
149
|
+
## Cross-references
|
|
150
|
+
|
|
151
|
+
- `workiq-only.instructions.md` — WorkIQ is the only path. This file defines what happens when that path fails.
|
|
152
|
+
- `identity-resolution.instructions.md` — "Failure modes" table now cites this rule.
|
|
153
|
+
- `bootstrap-status-format.instructions.md` — `## Deferred Retries` section contract.
|
|
154
|
+
- `multi-user-shared-files.instructions.md` — `_deferred-retries/` is per-contributor (under `Evidence/<alias>/`) so multi-user collision doctrine does not apply.
|
|
155
|
+
- `verbatim-by-default.instructions.md` — deferring is NOT a silent skip. A marker IS the audit trail.
|
|
@@ -15,20 +15,23 @@ Resolve `<engagement-root>` in this order — first match wins:
|
|
|
15
15
|
2. **`customer_workspace/FDEDocs/`** — if the user's workspace has this symlink, follow it. Common in FDE-style installs.
|
|
16
16
|
3. **Ask the user** once and persist the answer to `<workspace>/.kushi/config/user/project-evidence.yml engagement_root`.
|
|
17
17
|
|
|
18
|
-
## Live config location
|
|
18
|
+
## Live config location (v4.4.0+)
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Live filled configs live under the workspace (where `.kushi/` is), NOT under the engagement root:
|
|
21
21
|
|
|
22
22
|
```
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
<workspace>/.kushi/config/
|
|
24
|
+
user/ ← per-contributor (gitignored)
|
|
25
|
+
project-evidence.yml identity, engagement_root, workiq cli path
|
|
26
|
+
m365-auth.json tenant + default notebook + mailbox folders + SP root
|
|
27
|
+
m365-mutable.json discovered IDs (knownSections.<projectKey>)
|
|
28
|
+
shared/ ← team-owned (safe to commit)
|
|
29
|
+
integrations.yml ADO + CRM connection blocks (per project)
|
|
29
30
|
```
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
The legacy location `<engagement-root>/.project-evidence/` is no longer used as of kushi v4.4.0. Existing installs are migrated on first run of v4.4.0+. The new layout is host-agnostic: workspace is always where `.kushi/` lives (vscode target: project root; clawpilot target: any `cwd` where the user invokes Kushi).
|
|
33
|
+
|
|
34
|
+
`<engagement-root>` is still where per-project Evidence/, State/, and `<project>/integrations.yml` (per-project boundaries) live.
|
|
32
35
|
|
|
33
36
|
## Project resolution
|
|
34
37
|
|
|
@@ -42,7 +45,7 @@ Once `<engagement-root>` is known, individual projects are subfolders:
|
|
|
42
45
|
|
|
43
46
|
Project name resolution (always fuzzy):
|
|
44
47
|
|
|
45
|
-
1. Match against keys in `<
|
|
48
|
+
1. Match against keys in `<workspace>/.kushi/config/user/m365-mutable.json m365Mutable.knownSections`.
|
|
46
49
|
2. Match against `active_projects:` in `<workspace>/.kushi/config/user/project-evidence.yml`.
|
|
47
50
|
3. Match against actual subfolder names under `<engagement-root>`.
|
|
48
51
|
|
|
@@ -85,7 +85,7 @@ Anything outside these paths is invisible to them — by design. The path IS the
|
|
|
85
85
|
|
|
86
86
|
`plugin/skills/self-check/run.ps1` -Deep MUST detect any `<project>/<sibling>/` folder under engagement roots where:
|
|
87
87
|
|
|
88
|
-
- `<sibling>` is not in the allow-list `@('Evidence','State','Reports','.kushi','.kushi-reference','.
|
|
88
|
+
- `<sibling>` is not in the allow-list `@('Evidence','State','Reports','.kushi','.kushi-reference','.vscode')`, AND
|
|
89
89
|
- `<sibling>` contains markdown files matching the per-source patterns (`*-summary.md`, `*-stream.md`, `*-context*`, `current-state.md`, `index.md`, etc.).
|
|
90
90
|
|
|
91
91
|
When detected → emit a D14 finding with the canonical replacement path from Rule 2.
|
|
@@ -105,7 +105,7 @@ Together they guarantee that two contributors running the same verb on the same
|
|
|
105
105
|
|
|
106
106
|
- `snapshot-vs-stream.instructions.md` — the two shapes inside each `<source>/` folder.
|
|
107
107
|
- `scope-boundaries.instructions.md` — what each source is allowed to query (orthogonal: scope vs path).
|
|
108
|
-
- `side-by-side-config.instructions.md` — config files (mutable hints, integrations.yml)
|
|
108
|
+
- `side-by-side-config.instructions.md` — config files (mutable hints, integrations.yml) live under `<workspace>/.kushi/config/` (v4.4.0+, was `<engagement-root>/.project-evidence/`).
|
|
109
109
|
- `run-reports.instructions.md` — every layout-migration MUST appear in the refresh report.
|
|
110
110
|
- `cleanup-on-resolution.instructions.md` — once a legacy folder is migrated, all stale references in older summaries/notes get rewritten in the same turn.
|
|
111
111
|
- `bootstrap-project/SKILL.md` — creates the canonical tree on first run.
|
|
@@ -37,30 +37,37 @@ Map the response:
|
|
|
37
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
|
-
## Failure modes
|
|
40
|
+
## Failure modes (kushi v4.4.1+: never block, never fallback to Graph)
|
|
41
|
+
|
|
42
|
+
Per `deferred-retry-on-workiq-fail.instructions.md`, identity resolution NEVER blocks the orchestrator and NEVER calls `m365_get_*` / Graph as a fallback. On WorkIQ failure, the agent writes a deferred-retry marker and continues with a derived alias so evidence still lands somewhere it can later be re-keyed.
|
|
41
43
|
|
|
42
44
|
| Scenario | Behavior |
|
|
43
45
|
|-----------------------------------------------|------------------------------------------------------------------------------------------------------------------------------|
|
|
44
|
-
| WorkIQ returns auth error |
|
|
45
|
-
| WorkIQ returns empty / NO_RESULTS |
|
|
46
|
-
| WorkIQ binary missing |
|
|
47
|
-
| User has explicit non-placeholder values | Skip resolution entirely. Never overwrite user-set values.
|
|
46
|
+
| WorkIQ returns auth error | Write deferred-retry marker (`source: identity`, `error_class: auth-error`). Echo: *"⚠ Identity unresolved (WorkIQ auth). Using alias=`<from-config-or-env>`; will retry on next refresh. Sign in with `workiq accept-eula && workiq ask -q ping` and re-run."* Continue. **Do NOT call `m365_get_my_profile` or Graph `/me`.** |
|
|
47
|
+
| WorkIQ returns empty / NO_RESULTS | Write marker (`error_class: empty`). Echo: *"⚠ Identity unresolved (WorkIQ empty). Using alias=`<env-user>`; will retry on next refresh."* Continue. |
|
|
48
|
+
| WorkIQ binary missing | Installer pre-flight should catch this. If reached at skill-time, write marker (`error_class: cli-missing`), echo the install hint, continue with alias=`<env-user>`. |
|
|
49
|
+
| User has explicit non-placeholder values | Skip resolution entirely. Never overwrite user-set values. No marker needed. |
|
|
48
50
|
| Alias collision with another contributor | Bootstrap detects existing `Evidence/<alias>/` whose `contributors.yml` records a different email → ask the user to disambiguate (suggest `<alias>-<tenant-prefix>` e.g. `alex-ms`). |
|
|
49
51
|
|
|
52
|
+
**Derived alias fallback (when WorkIQ unavailable):** use `$env:USERNAME` (Windows) or `$env:USER` (POSIX) lowercased. Persist provisionally with `alias_resolved_from: env-fallback-pending-workiq`. The next refresh's deferred-retry drain will re-issue the WorkIQ probe and rename `Evidence/<env-alias>/` → `Evidence/<workiq-alias>/` if they differ.
|
|
53
|
+
|
|
50
54
|
## What NOT to do
|
|
51
55
|
|
|
52
56
|
* Do NOT ask the user `What alias should Kushi use?`. The legacy onboarding prompt is gone.
|
|
53
|
-
* Do NOT call `m365_
|
|
57
|
+
* Do NOT call `m365_get_my_profile`, `m365_*`, or Graph `/me` as a fallback. WorkIQ is the single source of identity truth. On failure, write a marker and use the env-fallback. See `workiq-only.instructions.md` and `deferred-retry-on-workiq-fail.instructions.md`.
|
|
58
|
+
* Do NOT block the bootstrap orchestrator on identity failure. Continue with the env-fallback alias; the marker drives the eventual reconciliation.
|
|
54
59
|
* Do NOT resolve on every run. Once persisted, the config values are authoritative.
|
|
55
60
|
|
|
56
61
|
## Integration with other doctrine
|
|
57
62
|
|
|
58
|
-
* `workiq-
|
|
63
|
+
* `workiq-only.instructions.md` — identity resolution is the canonical example of WorkIQ-only. M365 fallbacks are forbidden.
|
|
64
|
+
* `deferred-retry-on-workiq-fail.instructions.md` — on WorkIQ failure, mark + continue + inform; never block.
|
|
59
65
|
* `bootstrap-project` SKILL — Step 0 is identity resolution. Step 1 is project context.
|
|
60
66
|
* `tracking.instructions.md` — the resolved identity goes into the tracking artifact's frontmatter under `actor:`.
|
|
61
67
|
|
|
62
68
|
## References
|
|
63
69
|
|
|
64
|
-
* `workiq-
|
|
70
|
+
* `workiq-only.instructions.md` — the parent doctrine (supersedes legacy `workiq-first`).
|
|
71
|
+
* `deferred-retry-on-workiq-fail.instructions.md` — failure-mode contract.
|
|
65
72
|
* `engagement-root-resolution.instructions.md` — `projects_root` resolution (separate from identity).
|
|
66
73
|
* `templates/init/project-evidence.template.yml` — defaults to `<auto>` for these three fields.
|
|
@@ -12,7 +12,7 @@ priority: HARD
|
|
|
12
12
|
|
|
13
13
|
## The registry
|
|
14
14
|
|
|
15
|
-
`<
|
|
15
|
+
`<workspace>/.kushi/config/user/m365-mutable.json#knownSections.<projectKey>` is the **single source of truth** for canonical M365 identifiers per project.
|
|
16
16
|
|
|
17
17
|
Schema (populate every key the source supports):
|
|
18
18
|
|
|
@@ -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 `<
|
|
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` | `<
|
|
61
|
-
| `pull-ado` | `<
|
|
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 `<
|
|
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.
|
|
@@ -42,8 +42,8 @@ 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**:
|
|
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)
|
|
@@ -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.
|
|
@@ -53,6 +53,92 @@ param(
|
|
|
53
53
|
|
|
54
54
|
$ErrorActionPreference = 'Stop'
|
|
55
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
|
+
|
|
56
142
|
function Get-DefaultExtension([string] $name) {
|
|
57
143
|
if ($name -match '^(project-evidence|integrations)$') { return 'yml' }
|
|
58
144
|
return 'json'
|
|
@@ -84,9 +170,13 @@ if ($Path) { return $resolved }
|
|
|
84
170
|
$raw = Get-Content -LiteralPath $resolved -Raw
|
|
85
171
|
|
|
86
172
|
if (-not $AllowPlaceholders) {
|
|
87
|
-
|
|
173
|
+
$sentinelHit = $false
|
|
174
|
+
foreach ($s in $script:Sentinels) {
|
|
175
|
+
if ($raw -like "*$s*") { $sentinelHit = $true; break }
|
|
176
|
+
}
|
|
177
|
+
if ($sentinelHit) {
|
|
88
178
|
throw @"
|
|
89
|
-
Kushi config '$resolved' still has template placeholders (__FILL_ME_IN__
|
|
179
|
+
Kushi config '$resolved' still has template placeholders (e.g. __FILL_ME_IN__, <auto>, <TENANT_ID>).
|
|
90
180
|
Edit the file with your actual values, or pass -AllowPlaceholders to bypass this check.
|
|
91
181
|
"@
|
|
92
182
|
}
|
|
@@ -94,16 +184,31 @@ Edit the file with your actual values, or pass -AllowPlaceholders to bypass this
|
|
|
94
184
|
|
|
95
185
|
if ($Raw) { return $raw }
|
|
96
186
|
|
|
97
|
-
switch ($ext) {
|
|
187
|
+
$parsed = switch ($ext) {
|
|
98
188
|
'json' {
|
|
99
|
-
|
|
189
|
+
$raw | ConvertFrom-Json -Depth 100
|
|
100
190
|
}
|
|
101
191
|
{ $_ -in 'yml','yaml' } {
|
|
102
192
|
if (Get-Module -ListAvailable -Name 'powershell-yaml') {
|
|
103
193
|
Import-Module powershell-yaml -ErrorAction Stop
|
|
104
|
-
|
|
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
|
|
105
198
|
}
|
|
106
|
-
Write-Warning "powershell-yaml not installed; returning raw YAML text. Install with: Install-Module powershell-yaml -Scope CurrentUser"
|
|
107
|
-
return $raw
|
|
108
199
|
}
|
|
109
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
|