kushi-agents 3.4.2 → 3.12.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/README.md +33 -0
- package/package.json +15 -3
- package/plugin/agents/kushi.agent.md +155 -147
- package/plugin/instructions/ado-bootstrap-discovery.instructions.md +111 -0
- package/plugin/instructions/ado-engagement-tree.instructions.md +73 -0
- package/plugin/instructions/answer-from-evidence.instructions.md +1 -1
- package/plugin/instructions/auth-and-retry.instructions.md +51 -16
- package/plugin/instructions/azure-auth-patterns.instructions.md +13 -6
- package/plugin/instructions/bootstrap-status-format.instructions.md +113 -0
- package/plugin/instructions/capture-learnings.instructions.md +95 -0
- package/plugin/instructions/cleanup-on-resolution.instructions.md +69 -0
- package/plugin/instructions/crm-bootstrap-discovery.instructions.md +79 -0
- package/plugin/instructions/crm-internal-vs-confirmed.instructions.md +79 -0
- package/plugin/instructions/evidence-confidence-ladder.instructions.md +66 -0
- package/plugin/instructions/evidence-layout-canonical.instructions.md +115 -0
- package/plugin/instructions/evidence-thoroughness.instructions.md +82 -12
- package/plugin/instructions/full-view-gate.instructions.md +91 -0
- package/plugin/instructions/m365-id-registry.instructions.md +134 -0
- package/plugin/instructions/meetings-verbatim-required.instructions.md +176 -0
- package/plugin/instructions/run-reports.instructions.md +129 -0
- package/plugin/instructions/scope-boundaries.instructions.md +218 -0
- package/plugin/instructions/snapshot-vs-stream.instructions.md +2 -0
- package/plugin/instructions/update-ledger.instructions.md +132 -0
- package/plugin/instructions/verbatim-by-default.instructions.md +73 -0
- package/plugin/instructions/workiq-first.instructions.md +15 -31
- package/plugin/instructions/workiq-only.instructions.md +193 -0
- package/plugin/learnings/README.md +50 -0
- package/plugin/learnings/ado.md +45 -0
- package/plugin/learnings/crm.md +96 -0
- package/plugin/learnings/cross-cutting.md +36 -0
- package/plugin/learnings/email.md +33 -0
- package/plugin/learnings/meetings.md +30 -0
- package/plugin/learnings/misc.md +46 -0
- package/plugin/learnings/onenote.md +215 -0
- package/plugin/learnings/sharepoint.md +5 -0
- package/plugin/learnings/teams.md +5 -0
- package/plugin/plugin.json +22 -2
- package/plugin/prompts/apply-ado.prompt.md +14 -0
- package/plugin/prompts/propose-ado.prompt.md +12 -0
- package/plugin/reference-packs/fde/crm-field-manifest.md +165 -0
- package/plugin/skills/apply-ado-update/SKILL.md +125 -0
- package/plugin/skills/ask-project/SKILL.md +2 -0
- package/plugin/skills/bootstrap-project/SKILL.md +81 -3
- package/plugin/skills/propose-ado-update/SKILL.md +108 -0
- package/plugin/skills/pull-ado/SKILL.md +173 -23
- package/plugin/skills/pull-crm/SKILL.md +168 -15
- package/plugin/skills/pull-email/SKILL.md +139 -22
- package/plugin/skills/pull-meetings/SKILL.md +109 -25
- package/plugin/skills/pull-misc/README.md +84 -0
- package/plugin/skills/pull-misc/SKILL.md +257 -0
- package/plugin/skills/pull-misc/runner.mjs +280 -0
- package/plugin/skills/pull-onenote/README.md +90 -0
- package/plugin/skills/pull-onenote/SKILL.md +400 -51
- package/plugin/skills/pull-onenote/runner.mjs +356 -0
- package/plugin/skills/pull-onenote/scripts/recapture-section-url.mjs +295 -0
- package/plugin/skills/pull-onenote/write-snapshot.mjs +271 -0
- package/plugin/skills/pull-sharepoint/SKILL.md +44 -12
- package/plugin/skills/pull-teams/SKILL.md +40 -11
- package/plugin/skills/refresh-project/SKILL.md +33 -2
- package/plugin/skills/self-check/run.ps1 +186 -4
- package/plugin/templates/ado-update/discussion-comment.template.md +26 -0
- package/plugin/templates/ado-update/integrations-ado-writes.example.yml +49 -0
- package/plugin/templates/ado-update/proposed.template.md +78 -0
- package/plugin/templates/init/external-links.template.txt +30 -0
- package/plugin/templates/init/project-integrations.template.yml +57 -2
- package/plugin/templates/snapshot/meeting-verbatim.template.md +110 -0
- package/plugin/templates/snapshot/meetings-series-index.template.md +3 -1
- package/plugin/templates/snapshot/onenote-page.template.md +92 -23
- package/plugin/templates/weekly/meetings-stream.template.md +11 -6
|
@@ -1,23 +1,34 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "pull-ado"
|
|
3
|
-
version: "2.
|
|
4
|
-
description: "Pull ADO evidence (
|
|
3
|
+
version: "2.3.1"
|
|
4
|
+
description: "Pull ADO evidence as an engagement TREE (parent Engagement + every child WI). Per-item discussion comments + revision history (verbatim). Deterministic resolution; no parent-only summaries. Per ado-engagement-tree.instructions.md."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Skill: pull-ado
|
|
8
8
|
|
|
9
|
+
|
|
10
|
+
> **v3.7.6 contracts** — This skill operates under four HARD-rule doctrines:
|
|
11
|
+
> - `verbatim-by-default.instructions.md` — full bodies/notetext/fields by default; no preview-grade pulls accepted.
|
|
12
|
+
> - `capture-learnings.instructions.md` — every fix/discovery is logged to `plugin/learnings/<source>.md` immediately.
|
|
13
|
+
> - `cleanup-on-resolution.instructions.md` — when a value resolves, all stale `no-match` / `not yet` notes referencing the prior unresolved state must be rewritten in the same turn.
|
|
14
|
+
> - `run-reports.instructions.md` — every refresh writes a per-user report under `Evidence/<alias>/refresh-reports/YYYY-MM-DD-HHMM_refresh.md`.
|
|
15
|
+
|
|
16
|
+
> **Canonical evidence layout** (HARD, kushi v3.12.1+): all artifacts produced by this skill MUST be written under `<project>/Evidence/<alias>/<source>/{snapshot,stream,...}/` — sibling folders under `<project>/` (e.g. `<project>/<source>-context/`, `<project>/<source>/`, `<project>/_Weekly Summaries/`) are FORBIDDEN. See `evidence-layout-canonical.instructions.md`.
|
|
17
|
+
|
|
9
18
|
Pulls **ado** evidence in two shapes per `snapshot-vs-stream.instructions.md`:
|
|
10
19
|
|
|
11
|
-
- **snapshot/** —
|
|
12
|
-
- **stream/** — comments
|
|
20
|
+
- **snapshot/** — engagement TREE: parent Engagement + every child WI; each item gets full fields + every discussion comment verbatim + full revision history
|
|
21
|
+
- **stream/** — new comments + state changes + field changes + attachments across the entire tree, weekly bucket
|
|
13
22
|
|
|
14
|
-
WorkIQ-first per `workiq-first.instructions.md
|
|
23
|
+
WorkIQ-first per `workiq-first.instructions.md` — but **for ADO, REST is preferred for snapshot** because WorkIQ summarizes discussion threads. Thoroughness per `evidence-thoroughness.instructions.md`; runtime detector + auto-retry + paste-prompt per `thoroughness-detector.instructions.md`. Citations per `citation-ledger.instructions.md`. Side-by-side mutable hints written immediately on discovery per `side-by-side-config.instructions.md`. Tree expansion + per-item discussion + revision pulls per `ado-engagement-tree.instructions.md`.
|
|
15
24
|
|
|
16
25
|
## Inputs
|
|
17
26
|
|
|
18
27
|
- `<project>` — already-resolved project name.
|
|
19
28
|
- `<alias>` — current contributor.
|
|
20
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.
|
|
31
|
+
- (read) `<engagement-root>/<project>/integrations.yml#ado` — per-project pinned IDs.
|
|
21
32
|
- (read) `<engagement-root>/.project-evidence/m365/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints.
|
|
22
33
|
- (read) `<engagement-root>/<project>/Evidence/<alias>/.settings.yml` — per-(project x user) overrides.
|
|
23
34
|
|
|
@@ -27,30 +38,162 @@ WorkIQ-first per `workiq-first.instructions.md`. Thoroughness per `evidence-thor
|
|
|
27
38
|
- `ado.queryId` — saved query ID for the project
|
|
28
39
|
- `ado.areaPath` / `ado.iterationPath` — for filtering
|
|
29
40
|
|
|
30
|
-
##
|
|
41
|
+
## Hard prerequisites (REQUIRED — see `scope-boundaries.instructions.md` Rule 3)
|
|
42
|
+
|
|
43
|
+
This skill is **HARD-fail** without both:
|
|
44
|
+
|
|
45
|
+
1. Global config: `<engagement-root>/.project-evidence/ado/config.yml` exists with non-placeholder `organization` AND `defaultProject`.
|
|
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
|
+
|
|
48
|
+
If either is missing, refuse with the exact message:
|
|
49
|
+
|
|
50
|
+
```
|
|
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
|
|
54
|
+
to <project>/integrations.yml.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Discovery loops (`title_contains`, `tags_contains`, `custom_iscrm_id`) are still
|
|
58
|
+
permitted — but ONLY inside `boundaries.ado.area_paths`. There is no
|
|
59
|
+
tenant-wide title-fuzzy scan in v3.7.0+.
|
|
60
|
+
|
|
61
|
+
## Auth (deterministic, no improvisation)
|
|
62
|
+
|
|
63
|
+
```powershell
|
|
64
|
+
$cfg = Get-Content "<engagement-root>/.project-evidence/ado/config.yml" | ConvertFrom-Yaml
|
|
65
|
+
$tok = az account get-access-token `
|
|
66
|
+
--resource $cfg.azDevOpsResourceId `
|
|
67
|
+
--tenant $cfg.tenantId `
|
|
68
|
+
--query accessToken -o tsv
|
|
69
|
+
$h = @{ Authorization = "Bearer $tok"; 'Content-Type' = 'application/json' }
|
|
70
|
+
$org = $cfg.organization # e.g. https://dev.azure.com/IndustrySolutions
|
|
71
|
+
$proj = $cfg.defaultProject # e.g. "IS Engagements"
|
|
72
|
+
$projUrl = "$org/$([uri]::EscapeDataString($proj))"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
If `az account get-access-token` returns nothing → ask user to `az login --tenant <tenantId>` and stop. Do NOT silently proceed.
|
|
76
|
+
|
|
77
|
+
## Step 1 — Resolve the Engagement WI ID (deterministic order)
|
|
78
|
+
|
|
79
|
+
Per `ado-engagement-tree.instructions.md` § Resolution order:
|
|
80
|
+
|
|
81
|
+
### 1a — Pinned ID
|
|
82
|
+
|
|
83
|
+
If `<project>/integrations.yml#ado.engagement_id` is non-null, use it. Skip to Step 2.
|
|
84
|
+
|
|
85
|
+
### 1b — Pinned query
|
|
86
|
+
|
|
87
|
+
If `ado.queryId` is set:
|
|
88
|
+
|
|
89
|
+
```powershell
|
|
90
|
+
$q = Invoke-RestMethod -Headers $h -Uri "$projUrl/_apis/wit/wiql/$($cfg.ado.queryId)?api-version=7.1"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Take the single `Engagement`-typed row. If multiple → ask user, persist choice.
|
|
94
|
+
|
|
95
|
+
### 1c — Bounded WIQL
|
|
96
|
+
|
|
97
|
+
Build the WIQL ONLY inside `boundaries.ado.area_paths`:
|
|
98
|
+
|
|
99
|
+
```sql
|
|
100
|
+
SELECT [System.Id],[System.Title],[System.WorkItemType],[System.State],
|
|
101
|
+
[System.AreaPath],[System.IterationPath],[System.Tags],[System.AssignedTo],
|
|
102
|
+
[System.Parent],[System.CreatedDate],[System.ChangedDate]
|
|
103
|
+
FROM workitems
|
|
104
|
+
WHERE [System.TeamProject] = '<defaultProject>'
|
|
105
|
+
AND [System.WorkItemType] = 'Engagement'
|
|
106
|
+
AND [System.AreaPath] UNDER '<area_path>'
|
|
107
|
+
AND ([System.Title] CONTAINS '<project>' OR
|
|
108
|
+
[Custom.ISCRMRequestId] = '<crm.request_id>' OR
|
|
109
|
+
[System.Tags] CONTAINS '<project>')
|
|
110
|
+
AND [System.CreatedDate] > '<2-years-ago>'
|
|
111
|
+
AND [System.State] <> 'Removed'
|
|
112
|
+
ORDER BY [System.ChangedDate] DESC
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
POST to `$projUrl/_apis/wit/wiql?api-version=7.1` with body `{ "query": "<wiql>" }`.
|
|
116
|
+
|
|
117
|
+
- 0 rows → write `last_discovery_result: no-match` + the WIQL used + `next_step` and STOP.
|
|
118
|
+
- 1 row → use it. Persist `engagement_id` to `integrations.yml#ado` AND to `m365Mutable.knownSections.<project>.ado.engagementId`.
|
|
119
|
+
- N rows → render the candidates table to the user (id, title, state, areaPath, createdDate). Ask which one. **Never auto-pick.** Persist on confirmation.
|
|
120
|
+
|
|
121
|
+
## Step 2 — Expand the engagement tree
|
|
122
|
+
|
|
123
|
+
```powershell
|
|
124
|
+
$wi = Invoke-RestMethod -Headers $h -Uri "$projUrl/_apis/wit/workitems/$engagementId?`$expand=relations&api-version=7.1"
|
|
125
|
+
$childIds = $wi.relations |
|
|
126
|
+
Where-Object { $_.rel -eq 'System.LinkTypes.Hierarchy-Forward' } |
|
|
127
|
+
ForEach-Object { ($_.url -split '/')[-1] }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Recurse — expand each child's `relations` the same way until the tree closes. Persist the resulting tree (parent → children IDs) to `Evidence/<alias>/ado/snapshot/tree.md`.
|
|
131
|
+
|
|
132
|
+
## Step 3 — Per-item fetch (parent + every child, no batching)
|
|
133
|
+
|
|
134
|
+
For each WI id in the tree:
|
|
135
|
+
|
|
136
|
+
### 3a — Core fields
|
|
137
|
+
|
|
138
|
+
```powershell
|
|
139
|
+
$item = Invoke-RestMethod -Headers $h `
|
|
140
|
+
-Uri "$projUrl/_apis/wit/workitems/$id?`$expand=all&api-version=7.1"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`$expand=all` returns every populated field including custom fields. Don't pre-filter.
|
|
144
|
+
|
|
145
|
+
### 3b — Discussion comments (verbatim, paginated until exhausted)
|
|
146
|
+
|
|
147
|
+
```powershell
|
|
148
|
+
$cmts = @()
|
|
149
|
+
$ct = $null
|
|
150
|
+
do {
|
|
151
|
+
$url = "$projUrl/_apis/wit/workItems/$id/comments?api-version=7.1-preview.4&`$top=200"
|
|
152
|
+
if ($ct) { $url += "&continuationToken=$ct" }
|
|
153
|
+
$page = Invoke-RestMethod -Headers $h -Uri $url
|
|
154
|
+
$cmts += $page.comments
|
|
155
|
+
$ct = $page.continuationToken
|
|
156
|
+
} while ($ct)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Capture EVERY comment: `id`, `text` (verbatim, no truncation), `createdBy.displayName`, `createdDate`, `modifiedDate`. Render newest-first under `## Discussion (verbatim)`.
|
|
160
|
+
|
|
161
|
+
### 3c — Revision history (every state + assignee + field change)
|
|
162
|
+
|
|
163
|
+
```powershell
|
|
164
|
+
$updates = Invoke-RestMethod -Headers $h `
|
|
165
|
+
-Uri "$projUrl/_apis/wit/workItems/$id/updates?api-version=7.1&`$top=500"
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
For each update, render `revisedBy.displayName`, `revisedDate`, and the `fields.<name>.{oldValue → newValue}` deltas. Group by date.
|
|
31
169
|
|
|
32
|
-
|
|
170
|
+
### 3d — Attachments (metadata only)
|
|
33
171
|
|
|
34
|
-
|
|
172
|
+
From `$item.relations` where `rel = 'AttachedFile'` — record `name`, `resourceSize`, `attributes`. Do not download binary unless user asks.
|
|
35
173
|
|
|
36
|
-
|
|
174
|
+
### 3e — Write per-item snapshot file
|
|
37
175
|
|
|
38
|
-
|
|
176
|
+
`<engagement-root>/<project>/Evidence/<alias>/ado/snapshot/items/<id>.md` (parent gets `engagement-<id>.md` at the snapshot root):
|
|
39
177
|
|
|
40
|
-
|
|
178
|
+
- Header: id, type, title, state, areaPath, parent, assignee, last fetched.
|
|
179
|
+
- `## Source Basis` — tool used (rest), boundary applied, comments fetched, revisions fetched.
|
|
180
|
+
- `## AI Narrative Summary` (3+ paragraphs — REQUIRED FIRST).
|
|
181
|
+
- `## All fields` — 2-column table; empty as `_(empty)_`.
|
|
182
|
+
- `## Discussion (verbatim)` — every comment as `### <author> · <date>` then verbatim blockquote.
|
|
183
|
+
- `## Revision history` — every update row.
|
|
184
|
+
- `## Attachments` — metadata table.
|
|
41
185
|
|
|
42
|
-
|
|
186
|
+
## Step 4 — Stream pass
|
|
43
187
|
|
|
44
|
-
|
|
188
|
+
After all snapshots written, build the weekly stream from comments + updates whose `createdDate` / `revisedDate` fall in `<window>`. Bucket by Monday-of-ISO-week into `stream/<YYYY-MM-DD>_ado-stream.md` with AI Narrative Summary first then the verbatim entries grouped by WI.
|
|
45
189
|
|
|
46
|
-
If a week file
|
|
190
|
+
If a week file exists, MERGE (dedupe by `comment.id` and `update.rev`).
|
|
47
191
|
|
|
48
192
|
## Tools (in order)
|
|
49
193
|
|
|
50
|
-
1. **WorkIQ** —
|
|
51
|
-
2. **
|
|
52
|
-
3. **
|
|
53
|
-
4. **Ask user** — paste verbatim source content if all above fail.
|
|
194
|
+
1. **WorkIQ** — discovery only (asking "is there an Engagement WI for HCA?" before doing the WIQL). Never used for discussion-comment fetch — it summarizes.
|
|
195
|
+
2. **ADO REST** — REQUIRED for fields + comments + updates + tree expansion (preferred path). Auth block above.
|
|
196
|
+
3. **Ask user** — paste verbatim source content if REST returns 401 / 404 / persistent 5xx after 2 retries (3s/6s).
|
|
54
197
|
|
|
55
198
|
Document which path succeeded under `## Source Basis` in each output file.
|
|
56
199
|
|
|
@@ -61,17 +204,24 @@ If discovered, immediately write to `m365Mutable.knownSections.<project>.<source
|
|
|
61
204
|
- `ado.engagementId` — pinned engagement work-item ID
|
|
62
205
|
- `ado.queryId` — saved query ID for the project
|
|
63
206
|
- `ado.areaPath` / `ado.iterationPath` — for filtering
|
|
207
|
+
- `ado.tree.lastBuiltOn` + `ado.tree.itemCount` — so consolidate can detect drift between runs.
|
|
64
208
|
|
|
65
209
|
## Update run-log
|
|
66
210
|
|
|
67
211
|
After successful pass:
|
|
68
212
|
- `sources.ado.last_pulled = now`
|
|
69
|
-
- `sources.ado.
|
|
70
|
-
- `sources.ado.
|
|
213
|
+
- `sources.ado.engagement_id = <id>`
|
|
214
|
+
- `sources.ado.tree_size = <N>` — total WIs in the tree
|
|
215
|
+
- `sources.ado.comments_fetched = <N>` — across the tree
|
|
216
|
+
- `sources.ado.updates_fetched = <N>` — across the tree
|
|
217
|
+
- `sources.ado.items_failed = <N>`
|
|
218
|
+
- `sources.ado.watermark = now` (stream only)
|
|
71
219
|
- For each week touched, add to `weekly_files` index.
|
|
72
220
|
|
|
73
221
|
## Stop conditions
|
|
74
222
|
|
|
75
|
-
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
223
|
+
- Hard-prereq missing → refuse (see Hard prerequisites above).
|
|
224
|
+
- Step 1 returns multiple Engagement candidates → ask user, never auto-pick.
|
|
225
|
+
- Step 1 returns zero candidates → record `no-match` + WIQL + `next_step`, stop.
|
|
226
|
+
- A child WI fetch fails → mark `❌ item-fetch-failed` in `tree.md`, continue tree.
|
|
227
|
+
- Discussion paging fails mid-stream → record `❌ comments-partial — N/M fetched` and continue.
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "pull-crm"
|
|
3
|
-
version: "2.
|
|
4
|
-
description: "Pull CRM (Dataverse) evidence (snapshot: engagement record fields; stream: notes/activities/status changes).
|
|
3
|
+
version: "2.3.1"
|
|
4
|
+
description: "Pull CRM (Dataverse) evidence (snapshot: engagement record VERBATIM long-text fields + ALL annotations with formatted values; stream: notes/activities/status changes). Per-record explicit $select + formatted-value annotations. FDE entity sets use crm-field-manifest.md. v2.3.0: exhaustive 4-step resolution sequence (title → account[s] → wide-text → recent-slice → ask). Anti-patterns codified: no statecode filter, new_companyname is NOT a field, never disable from one shallow probe."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Skill: pull-crm
|
|
8
8
|
|
|
9
|
+
|
|
10
|
+
> **v3.7.6 contracts** — This skill operates under four HARD-rule doctrines:
|
|
11
|
+
> - `verbatim-by-default.instructions.md` — full bodies/notetext/fields by default; no preview-grade pulls accepted.
|
|
12
|
+
> - `capture-learnings.instructions.md` — every fix/discovery is logged to `plugin/learnings/<source>.md` immediately.
|
|
13
|
+
> - `cleanup-on-resolution.instructions.md` — when a value resolves, all stale `no-match` / `not yet` notes referencing the prior unresolved state must be rewritten in the same turn.
|
|
14
|
+
> - `run-reports.instructions.md` — every refresh writes a per-user report under `Evidence/<alias>/refresh-reports/YYYY-MM-DD-HHMM_refresh.md`.
|
|
15
|
+
|
|
16
|
+
> **Canonical evidence layout** (HARD, kushi v3.12.1+): all artifacts produced by this skill MUST be written under `<project>/Evidence/<alias>/<source>/{snapshot,stream,...}/` — sibling folders under `<project>/` (e.g. `<project>/<source>-context/`, `<project>/<source>/`, `<project>/_Weekly Summaries/`) are FORBIDDEN. See `evidence-layout-canonical.instructions.md`.
|
|
17
|
+
|
|
9
18
|
Pulls **crm** evidence in two shapes per `snapshot-vs-stream.instructions.md`:
|
|
10
19
|
|
|
11
|
-
- **snapshot/** — engagement record with
|
|
20
|
+
- **snapshot/** — engagement record with EVERY field the API returns (owner, status, account, dates, custom fields), all long-text fields VERBATIM, plus every related annotation (note) verbatim
|
|
12
21
|
- **stream/** — notes added, activities logged, field-level changes (old → new + actor + timestamp)
|
|
13
22
|
|
|
14
23
|
WorkIQ-first per `workiq-first.instructions.md`. Thoroughness per `evidence-thoroughness.instructions.md`; runtime detector + auto-retry + paste-prompt per `thoroughness-detector.instructions.md`. Citations per `citation-ledger.instructions.md`. Side-by-side mutable hints written immediately on discovery per `side-by-side-config.instructions.md`.
|
|
@@ -24,19 +33,159 @@ WorkIQ-first per `workiq-first.instructions.md`. Thoroughness per `evidence-thor
|
|
|
24
33
|
## Resolution hints (pinned in mutable)
|
|
25
34
|
|
|
26
35
|
- `crm.recordId` — pinned engagement record GUID (FE-NNNNN-style)
|
|
27
|
-
- `crm.entitySetName` — Dataverse entity set name (e.g. `msdyn_engagements`)
|
|
36
|
+
- `crm.entitySetName` — Dataverse entity set name (e.g. `msfre_engagementsubmissions`, `msdyn_engagements`)
|
|
37
|
+
- `crm.fieldMap` — resolved logical-name map per entity (filled by discovery probe; cached)
|
|
38
|
+
|
|
39
|
+
## Hard prerequisites (REQUIRED — see `scope-boundaries.instructions.md` Rule 3)
|
|
40
|
+
|
|
41
|
+
This skill is **HARD-fail** without both:
|
|
42
|
+
|
|
43
|
+
1. Global config: `<engagement-root>/.project-evidence/crm/config.yml` exists with non-placeholder `environmentUrl`.
|
|
44
|
+
2. Per-project boundary: `<engagement-root>/<project>/integrations.yml#boundaries.crm.record_ids` OR `boundaries.crm.request_ids` is non-empty.
|
|
45
|
+
|
|
46
|
+
If either is missing, refuse with the exact message:
|
|
47
|
+
|
|
48
|
+
```
|
|
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
|
|
52
|
+
(or request_ids) to <project>/integrations.yml.
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**No "narrate from email" fallback exists in v3.7.0+.** CRM evidence comes from CRM
|
|
56
|
+
or it is absent — Coverage Notes will say so explicitly. Synthesis across sources
|
|
57
|
+
happens at consolidation, not at pull.
|
|
58
|
+
|
|
59
|
+
## Field manifest dispatch
|
|
60
|
+
|
|
61
|
+
Before issuing the snapshot fetch, inspect `crm.entitySetName`:
|
|
62
|
+
|
|
63
|
+
- If it starts with `msfre_` (FDE engagement-submission family) → use `reference-packs/fde/crm-field-manifest.md` for the explicit `$select` list, the annotation `$expand`, and the snapshot file shape. The manifest IS the contract.
|
|
64
|
+
- For any other entity → still use the per-record loop below, but discover the column list from EntityDefinitions metadata once (cache to `crm.fieldMap`) and `$select` every column found (no relying on default projection).
|
|
65
|
+
|
|
66
|
+
## Dataverse retrieval doctrine (REQUIRED — applies to every REST call below)
|
|
67
|
+
|
|
68
|
+
Borrowed from production CRM-sync hardening. Every Dataverse REST call in this skill MUST follow these rules, or option-set / lookup fields will render as raw GUIDs and integer codes instead of human labels:
|
|
69
|
+
|
|
70
|
+
### Rule 1 — Always request formatted values
|
|
71
|
+
|
|
72
|
+
```powershell
|
|
73
|
+
$headers = @{
|
|
74
|
+
Authorization = "Bearer $token"
|
|
75
|
+
Accept = 'application/json'
|
|
76
|
+
'OData-Version' = '4.0'
|
|
77
|
+
'OData-MaxVersion' = '4.0'
|
|
78
|
+
Prefer = 'odata.include-annotations="OData.Community.Display.V1.FormattedValue"'
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
This makes Dataverse return paired properties like `statuscode@OData.Community.Display.V1.FormattedValue` and `_ownerid_value@OData.Community.Display.V1.FormattedValue` alongside the raw codes/GUIDs.
|
|
83
|
+
|
|
84
|
+
### Rule 2 — Use `PSObject.Properties[...]` accessor for `@`-named annotation properties
|
|
85
|
+
|
|
86
|
+
Dotted access fails for property names containing `@`. Always use the indexer:
|
|
87
|
+
|
|
88
|
+
```powershell
|
|
89
|
+
$ownerDisplay = $row.PSObject.Properties['_ownerid_value@OData.Community.Display.V1.FormattedValue'].Value
|
|
90
|
+
$status = $row.PSObject.Properties['statuscode@OData.Community.Display.V1.FormattedValue'].Value
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Rule 3 — Inspect one live row before widening
|
|
94
|
+
|
|
95
|
+
If field names are uncertain (new entity, schema drift, 400 error), fetch a single row first and dump property names. Do NOT keep iterating filters blindly:
|
|
96
|
+
|
|
97
|
+
```powershell
|
|
98
|
+
$url = "$baseUrl/api/data/$api/${entitySet}?`$orderby=modifiedon desc&`$top=1"
|
|
99
|
+
$row = (Invoke-RestMethod -Uri $url -Headers $headers -Method Get).value | Select-Object -First 1
|
|
100
|
+
$row.PSObject.Properties.Name | Sort-Object
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Cache the discovered shape to `crm.fieldMap`.
|
|
104
|
+
|
|
105
|
+
### Rule 4 — Add one field/filter at a time
|
|
106
|
+
|
|
107
|
+
If a query returns `400 Could not find a property named ...`, REVERT to the last known-good query shape, then add ONE field/filter and rerun. Do not widen scope past the failure.
|
|
108
|
+
|
|
109
|
+
### Rule 5 — Always include `statecode` and `statuscode` in candidate searches
|
|
28
110
|
|
|
29
|
-
|
|
111
|
+
Otherwise inactive/closed records become invisible during ranking.
|
|
30
112
|
|
|
31
|
-
|
|
113
|
+
### Rule 6 — Prefer formatted values in rendered output
|
|
32
114
|
|
|
33
|
-
|
|
115
|
+
Snapshot files render the formatted display value in the main body. Keep raw GUID / integer code only in `## All other fields` for diagnostic value.
|
|
34
116
|
|
|
35
|
-
|
|
117
|
+
## Resolution order (when `crm.recordId` is unset)
|
|
118
|
+
|
|
119
|
+
If `boundaries.crm.record_ids` is empty AND a project token is provided, resolve in this order. Persist whichever step resolves to `m365Mutable.knownSections.<project>.crm.recordId` immediately.
|
|
120
|
+
|
|
121
|
+
**CRITICAL anti-patterns — read before writing any filter:**
|
|
122
|
+
|
|
123
|
+
- **NEVER add a `statecode` filter** to any candidate-resolution query. The default CRM list view may only show active records, hiding inactive / withdrawn / closed / deferred records. Always query all states and report `statecode`+`statuscode` formatted values per Rule 5 + Rule 6.
|
|
124
|
+
- **`new_companyname` is NOT a valid attribute on `new_frontierengineeringtriage`.** The customer is a navigation lookup (`_new_customer_value`). Filtering on `new_companyname` will always return 0 and the 400 will look like an auth/scope failure. Always resolve customer via the `accounts` entity then `_new_customer_value eq <accountid>`.
|
|
125
|
+
- **Never give up after one shallow probe.** This 4-step sequence is exhaustive — only step 4's user-ask is a stop. If WorkIQ is the only path tried, that is a defect; the Dataverse REST sequence below MUST be attempted before declaring no-match. See `plugin/instructions/crm-bootstrap-discovery.instructions.md`.
|
|
126
|
+
|
|
127
|
+
1. **Title-first**: filter the entity by its title-equivalent field (FDE: `new_title` or `msfre_name`) `contains '<token>'`, ordered by `modifiedon desc`, top 20.
|
|
128
|
+
2. **Account fallback**: if step 1 returns 0, resolve customer in `accounts` by `name contains '<token>'`. For EACH matching account, query the engagement entity by `_<customerLookup>_value eq <accountId>` (not just the first match — token like "Deere" can resolve to multiple accounts e.g. `JOHN DEERE`, `DEERE COMPANY`, `JOHN DEERE COASTRUCTION`).
|
|
129
|
+
3. **Wide text fallback**: if both above fail, retry contains on the long-text fields most likely to mention the customer by name: `new_businessscenariotechnicalblocker`, `new_engagedwith`, `new_engagementobjectives` (FDE entity). One field per query; combine results.
|
|
130
|
+
4. **Recent-slice client-rank**: if all above fail, pull the most recent 200 records (`$select` minimal: id, title, requestId, customer-formatted, statecode-formatted, statuscode-formatted, modifiedon) and rank client-side by needle match against title / request id / customer display.
|
|
131
|
+
5. **Ask user**: present top 5 candidates (id, title, customer display, status, modifiedon). Never auto-pick on multi-match.
|
|
132
|
+
|
|
133
|
+
After steps 1–4 all return 0, write the candidate-search trail (queries attempted + result counts) to `Evidence/<alias>/refresh-reports/<ts>_refresh.md#crm-resolution-attempts` so the next refresh has audit. Do NOT silently set `boundaries.crm.disabled: true` — only step 5 (user declines or confirms none) writes `disabled` with `reason: 'no-match-after-full-4-step-resolution-<date>'`.
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
For EACH record id in `boundaries.crm.record_ids`:
|
|
138
|
+
|
|
139
|
+
### Step A — Resolve field map (once per project, cached)
|
|
140
|
+
|
|
141
|
+
If `m365Mutable.knownSections.<project>.crm.fieldMap` is empty, probe:
|
|
142
|
+
|
|
143
|
+
```http
|
|
144
|
+
GET /api/data/v9.2/EntityDefinitions(LogicalName='<entity-logical-name>')/Attributes?$select=LogicalName,DisplayName,AttributeType
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Persist the resolved attribute list to `crm.fieldMap`. For FDE entities, cross-check against `reference-packs/fde/crm-field-manifest.md`.
|
|
148
|
+
|
|
149
|
+
### Step B — Fetch the record with explicit $select + annotation $expand
|
|
150
|
+
|
|
151
|
+
Headers per **Dataverse retrieval doctrine Rule 1** (formatted values).
|
|
152
|
+
|
|
153
|
+
```http
|
|
154
|
+
GET /api/data/v9.2/<entitySet>(<recordId>)?$select=<every-attribute-from-fieldMap>&$expand=Annotations($select=annotationid,subject,notetext,createdon,_createdby_value,filename,mimetype)
|
|
155
|
+
Prefer: odata.include-annotations="OData.Community.Display.V1.FormattedValue"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
WorkIQ-first equivalent (when host fallback to REST isn't available):
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
workiq ask -q "Fetch the FULL Dataverse record from entity set <entitySet> with id <recordId>. Return EVERY field VERBATIM (do not summarize, do not truncate any long-text/Memo field). Then fetch ALL Annotations (notes) related to this record (objectid eq <recordId>). For each annotation return: subject, full notetext verbatim, createdon, createdby name. Output as JSON or markdown table — but do not paraphrase any long-text content."
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
If the response is summarized (heuristics: any long-text field appears trimmed, an annotation count exceeds the rendered count, phrases like `"and N more notes"` appear): **retry once** with the wording `Return raw field values verbatim, every annotation in full. Do not summarize, do not paginate, do not omit.`
|
|
165
|
+
|
|
166
|
+
### Step C — Write snapshot file (one per record)
|
|
167
|
+
|
|
168
|
+
Write `snapshot/<entitySet>/<record-id>.md` with:
|
|
169
|
+
|
|
170
|
+
- File header (record id, MSX opp, status, owner, last-fetched timestamp).
|
|
171
|
+
- `## Source Basis` — tool used, boundary applied, fieldMap source, annotation count.
|
|
172
|
+
- `## AI Narrative Summary` (3+ paragraphs — REQUIRED FIRST per `evidence-thoroughness.instructions.md`).
|
|
173
|
+
- `## Engagement context` — every long-text field as a verbatim blockquote (see crm-field-manifest.md for the FDE shape).
|
|
174
|
+
- `## All other fields` — 2-column table; empty fields shown as `_(empty)_` so absence is visible.
|
|
175
|
+
- `## Notes (verbatim)` — one `### Note — <subject> (<createdon> by <createdby>)` block per annotation, full `notetext` as a verbatim blockquote, oldest first. NO truncation regardless of length.
|
|
176
|
+
|
|
177
|
+
Write to: `<engagement-root>/<project>/Evidence/<alias>/crm/snapshot/<entitySet>/<record-id>.md`
|
|
178
|
+
|
|
179
|
+
Use template: `templates/snapshot/crm-<kind>.template.md` (FDE entities: shape comes from `reference-packs/fde/crm-field-manifest.md`).
|
|
180
|
+
|
|
181
|
+
### Step D — Failure handling
|
|
182
|
+
|
|
183
|
+
- Per-record fetch failed → write the snapshot file with a `❌ record-fetch-failed` marker + `next_step: ask user to paste record fields` and continue to next record.
|
|
184
|
+
- Annotation fetch failed but record fetched → write the record fields, mark `## Notes (verbatim)` with `❌ annotation-fetch-failed — N annotations expected` and continue.
|
|
36
185
|
|
|
37
186
|
## Stream pass
|
|
38
187
|
|
|
39
|
-
Per `evidence-thoroughness.instructions.md`: every annotation verbatim with author + timestamp
|
|
188
|
+
Per `evidence-thoroughness.instructions.md`: stream files (per-week) start with an **AI Narrative Summary** covering what changed and what it means, then every annotation verbatim with author + timestamp + every field-level change with old → new + actor + timestamp. Bucketed by ISO week into `stream/<record-id>/<YYYY-MM-DD>_crm-stream.md`.
|
|
40
189
|
|
|
41
190
|
Write to: `<engagement-root>/<project>/Evidence/<alias>/crm/stream/<YYYY-MM-DD>_crm-stream.md` (date = Monday of the ISO week the events fall in).
|
|
42
191
|
|
|
@@ -46,8 +195,8 @@ If a week file already exists, MERGE (dedupe by event ID, append new events, kee
|
|
|
46
195
|
|
|
47
196
|
## Tools (in order)
|
|
48
197
|
|
|
49
|
-
1. **
|
|
50
|
-
2. **
|
|
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`.
|
|
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.
|
|
51
200
|
3. **Graph REST** — last resort, soft-fail per `az-auth-conditional.instructions.md`.
|
|
52
201
|
4. **Ask user** — paste verbatim source content if all above fail.
|
|
53
202
|
|
|
@@ -57,19 +206,23 @@ Document which path succeeded under `## Source Basis` in each output file.
|
|
|
57
206
|
|
|
58
207
|
If discovered, immediately write to `m365Mutable.knownSections.<project>.<source>` with `discoveredOn` + `confidence`:
|
|
59
208
|
|
|
60
|
-
- `crm.recordId` — pinned engagement record GUID
|
|
61
|
-
- `crm.entitySetName` — Dataverse entity set name
|
|
209
|
+
- `crm.recordId` — pinned engagement record GUID
|
|
210
|
+
- `crm.entitySetName` — Dataverse entity set name
|
|
211
|
+
- `crm.fieldMap` — resolved attribute list (from EntityDefinitions probe)
|
|
62
212
|
|
|
63
213
|
## Update run-log
|
|
64
214
|
|
|
65
215
|
After successful pass:
|
|
66
216
|
- `sources.crm.last_pulled = now`
|
|
67
217
|
- `sources.crm.watermark = now` (stream only; snapshot has no watermark)
|
|
68
|
-
- `sources.crm.
|
|
218
|
+
- `sources.crm.records_fetched = <N>`
|
|
219
|
+
- `sources.crm.annotations_fetched = <N>` — total notes captured across all records
|
|
220
|
+
- `sources.crm.records_failed = <N>`
|
|
69
221
|
- For each week touched, add to `weekly_files` index.
|
|
70
222
|
|
|
71
223
|
## Stop conditions
|
|
72
224
|
|
|
225
|
+
- Hard-prereq missing → refuse (see Hard prerequisites above).
|
|
73
226
|
- Hint missing AND fuzzy resolution returns 0 candidates → ask user once, persist answer to mutable, continue.
|
|
74
227
|
- Multiple plausible candidates → ask user to pick, persist answer.
|
|
75
|
-
-
|
|
228
|
+
- All paths failed for a given record → write evidence file with `❌ all paths failed` marker, log to run-log errors, continue with rest of run.
|