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.
Files changed (69) hide show
  1. package/README.md +33 -0
  2. package/package.json +15 -3
  3. package/plugin/agents/kushi.agent.md +155 -147
  4. package/plugin/instructions/ado-bootstrap-discovery.instructions.md +111 -0
  5. package/plugin/instructions/ado-engagement-tree.instructions.md +73 -0
  6. package/plugin/instructions/answer-from-evidence.instructions.md +1 -1
  7. package/plugin/instructions/auth-and-retry.instructions.md +51 -16
  8. package/plugin/instructions/azure-auth-patterns.instructions.md +13 -6
  9. package/plugin/instructions/bootstrap-status-format.instructions.md +113 -0
  10. package/plugin/instructions/capture-learnings.instructions.md +95 -0
  11. package/plugin/instructions/cleanup-on-resolution.instructions.md +69 -0
  12. package/plugin/instructions/crm-bootstrap-discovery.instructions.md +79 -0
  13. package/plugin/instructions/crm-internal-vs-confirmed.instructions.md +79 -0
  14. package/plugin/instructions/evidence-confidence-ladder.instructions.md +66 -0
  15. package/plugin/instructions/evidence-layout-canonical.instructions.md +115 -0
  16. package/plugin/instructions/evidence-thoroughness.instructions.md +82 -12
  17. package/plugin/instructions/full-view-gate.instructions.md +91 -0
  18. package/plugin/instructions/m365-id-registry.instructions.md +134 -0
  19. package/plugin/instructions/meetings-verbatim-required.instructions.md +176 -0
  20. package/plugin/instructions/run-reports.instructions.md +129 -0
  21. package/plugin/instructions/scope-boundaries.instructions.md +218 -0
  22. package/plugin/instructions/snapshot-vs-stream.instructions.md +2 -0
  23. package/plugin/instructions/update-ledger.instructions.md +132 -0
  24. package/plugin/instructions/verbatim-by-default.instructions.md +73 -0
  25. package/plugin/instructions/workiq-first.instructions.md +15 -31
  26. package/plugin/instructions/workiq-only.instructions.md +193 -0
  27. package/plugin/learnings/README.md +50 -0
  28. package/plugin/learnings/ado.md +45 -0
  29. package/plugin/learnings/crm.md +96 -0
  30. package/plugin/learnings/cross-cutting.md +36 -0
  31. package/plugin/learnings/email.md +33 -0
  32. package/plugin/learnings/meetings.md +30 -0
  33. package/plugin/learnings/misc.md +46 -0
  34. package/plugin/learnings/onenote.md +215 -0
  35. package/plugin/learnings/sharepoint.md +5 -0
  36. package/plugin/learnings/teams.md +5 -0
  37. package/plugin/plugin.json +22 -2
  38. package/plugin/prompts/apply-ado.prompt.md +14 -0
  39. package/plugin/prompts/propose-ado.prompt.md +12 -0
  40. package/plugin/reference-packs/fde/crm-field-manifest.md +165 -0
  41. package/plugin/skills/apply-ado-update/SKILL.md +125 -0
  42. package/plugin/skills/ask-project/SKILL.md +2 -0
  43. package/plugin/skills/bootstrap-project/SKILL.md +81 -3
  44. package/plugin/skills/propose-ado-update/SKILL.md +108 -0
  45. package/plugin/skills/pull-ado/SKILL.md +173 -23
  46. package/plugin/skills/pull-crm/SKILL.md +168 -15
  47. package/plugin/skills/pull-email/SKILL.md +139 -22
  48. package/plugin/skills/pull-meetings/SKILL.md +109 -25
  49. package/plugin/skills/pull-misc/README.md +84 -0
  50. package/plugin/skills/pull-misc/SKILL.md +257 -0
  51. package/plugin/skills/pull-misc/runner.mjs +280 -0
  52. package/plugin/skills/pull-onenote/README.md +90 -0
  53. package/plugin/skills/pull-onenote/SKILL.md +400 -51
  54. package/plugin/skills/pull-onenote/runner.mjs +356 -0
  55. package/plugin/skills/pull-onenote/scripts/recapture-section-url.mjs +295 -0
  56. package/plugin/skills/pull-onenote/write-snapshot.mjs +271 -0
  57. package/plugin/skills/pull-sharepoint/SKILL.md +44 -12
  58. package/plugin/skills/pull-teams/SKILL.md +40 -11
  59. package/plugin/skills/refresh-project/SKILL.md +33 -2
  60. package/plugin/skills/self-check/run.ps1 +186 -4
  61. package/plugin/templates/ado-update/discussion-comment.template.md +26 -0
  62. package/plugin/templates/ado-update/integrations-ado-writes.example.yml +49 -0
  63. package/plugin/templates/ado-update/proposed.template.md +78 -0
  64. package/plugin/templates/init/external-links.template.txt +30 -0
  65. package/plugin/templates/init/project-integrations.template.yml +57 -2
  66. package/plugin/templates/snapshot/meeting-verbatim.template.md +110 -0
  67. package/plugin/templates/snapshot/meetings-series-index.template.md +3 -1
  68. package/plugin/templates/snapshot/onenote-page.template.md +92 -23
  69. package/plugin/templates/weekly/meetings-stream.template.md +11 -6
@@ -67,7 +67,7 @@ Inventing, smoothing-over, or guessing is forbidden.
67
67
  ## Interplay with other doctrine
68
68
 
69
69
  - **`citation-ledger.instructions.md`** — citation format authority. This doctrine adds the "per-assertion" rule.
70
- - **`workiq-first.instructions.md`** — does NOT apply at answer time (read-only).
70
+ - **`workiq-only.instructions.md`** — does NOT apply at answer time (read-only; the producer skills already enforced it during the pull).
71
71
  - **`evidence-thoroughness.instructions.md`** — applies to producer skills only; `ask-project` consumes whatever depth the producers wrote.
72
72
  - **`snapshot-vs-stream.instructions.md`** — `ask-project` reads both shapes; the load order above respects the snapshot/stream split.
73
73
  - **`engagement-root-resolution.instructions.md`** — used by Step 1 (project resolution).
@@ -17,11 +17,11 @@ Before the first call to a given service in a run, run the matching pre-flight.
17
17
 
18
18
  | Service | Pre-flight |
19
19
  |---|---|
20
- | WorkIQ | `Get-Command workiq -ErrorAction SilentlyContinue`. If missing → record `workiq-not-on-path`, fall back to host tools. Then probe with a trivial `workiq ask -q "ping"`; if output contains `accept the End User License Agreement` → run `workiq accept-eula` once and continue. |
21
- | Graph / `m365_*` | Call `m_m365_status`. If `signedIn != true` → record `m365-not-signed-in`, suggest `m_m365_sign_in` to user, skip Graph for this source, continue with WorkIQ + host fallbacks. |
20
+ | WorkIQ | `Get-Command workiq -ErrorAction SilentlyContinue`. If missing → record `workiq-not-on-path`, write evidence file pointing at `docs/getting-started/install-workiq.md`, STOP this source (do NOT fall back to Graph; in-scope M365 sources are WorkIQ-only per `workiq-only.instructions.md`). Then probe with a trivial `workiq ask -q "ping"`; if output contains `accept the End User License Agreement` → run `workiq accept-eula` once and continue. |
21
+ | Graph / `m365_*` | Used ONLY for: (a) CRM/ADO direct REST (out of WorkIQ scope), (b) the `m365_list_chat_messages` parallel structured-data dump in `pull-teams` / `pull-meetings`, and (c) the `m365_download_file` binary carve-out for `recording.mp4` and chat attachments in `pull-meetings`. For these cases only: call `m_m365_status`; if `signedIn != true` → record `m365-not-signed-in`, suggest `m_m365_sign_in` to user, skip the parallel dump (the WorkIQ artifact still stands). |
22
22
  | CRM / Dataverse | `az account show`. If non-zero → suggest `az login --tenant 72f988bf-86f1-41af-91ab-2d7cd011db47`, skip CRM (per `az-auth-conditional`). |
23
23
  | ADO | `az account show` AND tenant must be in allowed list (`72f988bf-86f1-41af-91ab-2d7cd011db47`). If wrong tenant → suggest re-login, skip ADO. |
24
- | SharePoint (host-specific) | Acquire token for the **exact URL origin** (`$($uri.Scheme)://$($uri.Host)`), NOT the configured default host. Personal sites use `<tenant>-my.sharepoint.com`. |
24
+ | SharePoint (host-specific, CRM/ADO-adjacent only) | Used only when CRM/ADO REST needs to read a SharePoint-hosted attachment. For pull-sharepoint content extraction, this entire row is FORBIDDEN use WorkIQ. |
25
25
 
26
26
  ---
27
27
 
@@ -83,25 +83,60 @@ The skill MUST set `last_status` based on errors:
83
83
 
84
84
  ## 5. Path-cascade contract (per source)
85
85
 
86
- Each `pull-<source>` skill tries paths in order. On each failure, append `errors[]` entry, then try next path. If all paths fail, write evidence file with `❌ all paths failed` marker and the actionable next step.
86
+ Each `pull-<source>` skill tries paths in order. On each failure, append `errors[]` entry, then try the next path. If all paths fail, write evidence file with `❌ all paths failed` marker and the actionable next step.
87
87
 
88
- | Source | Path 1 | Path 2 | Path 3 | Path 4 |
89
- |---|---|---|---|---|
90
- | email | WorkIQ | `m365_search_emails` / `m365_list_emails` | Graph REST | ask user |
91
- | teams | WorkIQ | `m365_list_chat_messages` (per chat ID) | Graph REST | ask user |
92
- | onenote | **WorkIQ-only per workspace policy** (see `workiq-first.instructions.md`) | n/a — Graph for OneNote is unreliable; do not attempt | n/a | ask user to paste page text |
93
- | sharepoint | WorkIQ | `m365_list_files` / `m365_search_files` | Graph `shares/{id}/driveItem` (for share links) or SharePoint REST `_api/web/GetFileByServerRelativePath` (for direct paths) | ask user |
94
- | meetings | WorkIQ (transcript) | `m365_get_transcript` | reconstruct from meeting chat (`m365_list_chat_messages`) + Copilot recap message | ask user |
95
- | crm | WorkIQ | Dataverse REST | n/a | ask user |
96
- | ado | WorkIQ | ADO REST WIQL | n/a | ask user |
88
+ **M365 sources are WorkIQ-only** per `workiq-only.instructions.md` (v3.11.0+). Graph REST and `m365_*` host tools are FORBIDDEN as fallbacks for in-scope M365 sources. The single allowed exception is `m365_list_chat_messages` as a parallel structured-data dump (`chat-messages.json`) — NEVER a substitute for the WorkIQ pull.
97
89
 
98
- For meetings specifically: **transcripts may legitimately not exist** (transcription was off). If WorkIQ + `m365_get_transcript` both return "not found" / "tool error" → **immediately reconstruct from the meeting chat** (`m365_list_chat_messages` for the meeting's chat ID — already captured during teams pull). This is "evidence reconstructed from chat" not "no evidence". Record `transcripts-not-generated` in errors with `action_taken: reconstructed-from-chat`.
90
+ | Source | Path 1 | Path 2 | Path 3 |
91
+ |---|---|---|---|
92
+ | email | WorkIQ (enumerate + per-message body) | doubled-strict WorkIQ retry | ask user to paste |
93
+ | teams | WorkIQ (thread reproduction) + `m365_list_chat_messages` in parallel (structured-data dump only) | doubled-strict WorkIQ retry | ask user to paste |
94
+ | onenote | Playwright browser-scrape (PRIMARY, see `pull-onenote/SKILL.md`) | WorkIQ tier-C per-page | ask user to paste |
95
+ | sharepoint | WorkIQ (content extraction) + local synced folder walk in parallel (metadata; filesystem, not API) | doubled-strict WorkIQ retry | ask user to paste |
96
+ | meetings | WorkIQ transcript pull + `m365_list_chat_messages` in parallel + WorkIQ recap + WorkIQ recording-URL discovery | doubled-strict WorkIQ retry | ask user to paste |
97
+ | calendar discovery | WorkIQ meeting-discovery prompt | doubled-strict WorkIQ retry | ask user to paste joinUrl |
98
+ | crm (NOT in workiq scope) | Dataverse REST per `pull-crm/SKILL.md` | WorkIQ supplementary | ask user |
99
+ | ado (NOT in workiq scope) | ADO REST WIQL per `pull-ado/SKILL.md` | WorkIQ supplementary | ask user |
100
+
101
+ **Forbidden** as cascade steps for M365 sources (in-scope): `m365_get_email`, `m365_search_emails`, `m365_list_emails`, `m365_list_mail_folders`, `m365_list_chats`, `m365_search_chats`, `m365_get_transcript`, `m365_get_facilitator_notes`, `m365_list_meetings`, `m365_list_events`, `m365_search_files`, `m365_list_files`, `m365_get_recent_files`, `m365_download_file` (for content; binary downloads in pull-meetings for `recording.mp4`/attachments are a separate carve-out), and any Graph REST URL for those resources. Calling them is a defect; coverage.md must NOT show them in the attempt trail.
102
+
103
+ For meetings specifically: **transcripts may legitimately not exist** (transcription was off). If WorkIQ returns "not found" / empty after the doubled-strict retry → reconstruct from the meeting chat (`m365_list_chat_messages` for the meeting's chat ID — already captured during teams pull, allowed as structured-data dump). This is "evidence reconstructed from chat" not "no evidence". Record `transcripts-not-generated` in errors with `action_taken: reconstructed-from-chat`.
99
104
 
100
105
  ---
101
106
 
102
- ## 6. Try-again UX
107
+ ## 6. Try-again UX + auto-retry from run-log (HARD RULE v3.12.0+)
108
+
109
+ **Failure surface is part of the run summary, every time.** Every orchestrator verb (`bootstrap`, `refresh`, `aggregate`) MUST:
110
+
111
+ 1. **At the end of the run**, read `Evidence/run-log.yml` and compute a per-source summary table including `last_status`, `signature`, `items_pulled`, and `next_step`.
112
+ 2. **If ANY source has `last_status` in {`failed`, `partial`, `skipped-auth`} with a retryable `signature`**, auto-surface a retry prompt to the user with two options:
113
+ - **Retry now (failed sources only)** — re-invokes only the per-source pull-* skills whose run-log entries are retryable, scoped to the same boundary and window as the original run.
114
+ - **Skip retry (continue)** — leaves the failure markers in place. Run-log keeps the error for next-run pickup.
115
+
116
+ The prompt MUST display the per-source signatures so the user can decide. Example:
117
+ ```
118
+ ⚠️ 2 sources failed last run:
119
+ - email partial signature: workiq-empty-after-retry items: 0
120
+ - meetings failed signature: transient-server-error items: 0
121
+ Retry these now? [Y/n]
122
+ ```
123
+
124
+ 3. **Retryable signatures** (auto-prompt to retry):
125
+ - `tool-error`, `transient-server-error`, `workiq-empty-after-retry` (first-time only), `workiq-eula-required` (after `workiq accept-eula`), `token-401-after-reacquire` (after re-auth), `auth-required` (OneNote, after re-bootstrap), `notebook-unavailable` (OneNote, after user re-syncs).
126
+
127
+ 4. **Non-retryable signatures** (do NOT auto-retry — surface guidance only):
128
+ - `transcripts-not-generated` (recording was off; need user action to re-record),
129
+ - `no-match` (boundary too narrow; need user to widen `integrations.yml boundaries:`),
130
+ - `accessDenied` / `403` (need permission grant from owner),
131
+ - `Sorry, I can't` (copyright/blocked content),
132
+ - `workiq-not-on-path` (need install/repair WorkIQ),
133
+ - `<source>-boundary-missing` (need user to populate integrations.yml).
134
+
135
+ 5. **"try again" / "refresh failed" / "retry errors" user message** → read `Evidence/run-log.yml errors[]`, retry only the entries where `signature` is in the retryable set. Surface a one-line skip notice per non-retryable entry with the actionable next step. Do NOT silently skip — the user must see what was skipped and why.
136
+
137
+ 6. **Auto-retry budget**: a single source can be auto-retried at most **once per orchestrator run**. If the retried call fails again with a retryable signature, the orchestrator MUST stop retrying that source for the run and surface the failure for user follow-up (do not loop).
103
138
 
104
- When the user says "try again" / "refresh" / "fix the errors" read `Evidence/run-log.yml errors[]`, retry only the entries where `signature` is in the retryable set (`tool-error`, `transient-server-error`, `graph-401-*` after re-auth). Skip entries where `signature` is in `transcripts-not-generated`, `no-match`, `accessDenied`, `Sorry, I can't` those need user action (re-record meeting, wait for ADO WI, file access request).
139
+ This codifies the user-asked feature: "is there a way to check the failure report and redo if failed automatically or at least ask about what failed and redo?" yes, every run ends with the retry prompt; non-retryable failures surface actionable guidance instead.
105
140
 
106
141
  ---
107
142
 
@@ -9,7 +9,7 @@ This file is the **concrete** companion to:
9
9
 
10
10
  - `auth-and-retry.instructions.md` — pre-flight, token reuse, retry, error-log schema, path cascade.
11
11
  - `az-auth-conditional.instructions.md` — when to require `az login` (only if CRM/ADO are configured).
12
- - `workiq-first.instructions.md` — WorkIQ is the required path for M365 sources.
12
+ - `workiq-only.instructions.md` — WorkIQ is the ONLY path for in-scope M365 sources (supersedes the deprecated `workiq-first.instructions.md`).
13
13
 
14
14
  Where those files describe **what** to do, this one shows **how** in PowerShell. Skill authors implementing `pull-crm`, `pull-ado`, `pull-sharepoint`, and any other external-service caller MUST follow the patterns here. Silent 401 loops and "no evidence found" misreports almost always come from skipping one of these.
15
15
 
@@ -99,18 +99,25 @@ A raw browser-facing SharePoint URL is often the **wrong endpoint** for bearer-t
99
99
  - **For direct file paths** under `/personal/.../Documents/...` → prefer SharePoint REST `_api/web/GetFileByServerRelativePath(...)` on the exact host.
100
100
  - If the canonical API then returns `403 accessDenied` or SharePoint `UnauthorizedAccessException`, treat it as a **real permission / sharing problem** — do not chase it as a token-host mismatch. Record `errors[]` with `signature: blocked-host-or-permissions-401` per `auth-and-retry.instructions.md §4`.
101
101
 
102
- ### 2.4 Microsoft Graph / OneNote
102
+ ### 2.4 Microsoft Graph / `m365_*` proxies — narrow carve-outs ONLY
103
103
 
104
- **Do not use Microsoft Graph for OneNote in this workspace.** Per `workiq-first.instructions.md`, OneNote is **WorkIQ-only**. Graph's OneNote endpoint repeatedly fails with 401/consent issues that don't represent real permission problems.
104
+ **Do not use Microsoft Graph or `m365_*` host tools for in-scope M365 sources** (Email / Teams human-readable threads / OneNote bodies / SharePoint content / meeting transcripts / calendar discovery). Per `workiq-only.instructions.md` (v3.11.0+), these are WorkIQ-only. Graph / `m365_*` have a near-100% failure rate in this workspace and pollute the coverage trail.
105
+
106
+ The ONLY allowed Graph / `m365_*` calls inside kushi pull-* skills are:
107
+
108
+ 1. **`m365_list_chat_messages(chatId)`** — parallel structured-data dump in `pull-teams` and `pull-meetings`. Writes `chat-messages.json`. Runs in parallel with the WorkIQ human-readable thread pull; NEVER a substitute. If it fails, the WorkIQ artifact still stands and the JSON dump is skipped.
109
+ 2. **`m365_download_file(itemId)`** for **binary media only** in `pull-meetings`: `recording.mp4` (when a SharePoint Stream URL is found and size < 200MB) and chat `attachments/<original-name>`. WorkIQ does not return binaries, so this carve-out is required for media survival.
110
+ 3. **CRM / ADO** — direct REST via Azure CLI tokens (sections 2.1 and 2.2 of this file). Out of WorkIQ scope.
105
111
 
106
112
  ```powershell
107
113
  if (-not (Get-Command workiq -ErrorAction SilentlyContinue)) {
108
- throw "workiq CLI not found on PATH. OneNote queries cannot proceed. See: https://gim-home.github.io/kushi/getting-started/install-workiq/"
114
+ throw "workiq CLI not found on PATH. M365 source queries cannot proceed. See: https://gim-home.github.io/kushi/getting-started/install-workiq/"
109
115
  }
110
- # Prefer narrowest WorkIQ query: one_sectionFileId > one_sectionName > project-alias keyword search.
116
+ # Prefer the canonical WorkIQ prompts codified in workiq-only.instructions.md.
117
+ # Do NOT improvise. Do NOT fall back to m365_* for in-scope sources.
111
118
  ```
112
119
 
113
- For other Graph endpoints (Mail, Teams, Calendar) Kushi defers to the host's `m365_*` proxies; see `auth-and-retry.instructions.md §5` for the path cascade.
120
+ For the allowed carve-outs (`m365_list_chat_messages`, `m365_download_file` for binaries): no `az` token is needed — these go through the Clawpilot M365 proxy authenticated via `m_m365_status` / `m_m365_sign_in`.
114
121
 
115
122
  ---
116
123
 
@@ -0,0 +1,113 @@
1
+ ---
2
+ applyTo: "**"
3
+ excludeAgent: "code-review"
4
+ ---
5
+
6
+ # Bootstrap Status Artifact — Format Contract
7
+
8
+ When `bootstrap-project` (or any full refresh / retry workflow) finishes a project run, it MUST write `<project>/bootstrap-status.md` using the format below. This file is the **fast orientation artifact** for the project — what was bootstrapped, what durable context now exists, what stage the engagement is in, what gaps remain. It is NOT a transcript of the run.
9
+
10
+ ## Required section order
11
+
12
+ ```
13
+ # Bootstrap Status
14
+
15
+ - Bootstrap Date: YYYY-MM-DD HH:MM TZ
16
+ - Project: <name>
17
+ - Customer Hint: <token used to resolve>
18
+ - Mode: bootstrap | refresh | retry | force
19
+ - Lookback Window: <days>
20
+
21
+ ## Preflight Checks
22
+
23
+ | Check | Status | Notes |
24
+ |---|---|---|
25
+ | `.project-evidence/ado/config.yml` filled | resolved \| blocked-auth \| missing | ... |
26
+ | `.project-evidence/crm/config.yml` filled | ... | ... |
27
+ | `<project>/integrations.yml` boundaries present | ... | ... |
28
+ | `az` CLI tenant matches | cli-available \| blocked-auth | ... |
29
+
30
+ ## Context Artifact Status
31
+
32
+ | Artifact | Status | Notes |
33
+ |---|---|---|
34
+ | `crm/snapshot/<entity>/<id>.md` | populated \| unsynced \| degraded \| partial-template | <annotation count> |
35
+ | `ado/snapshot/engagement-<id>.md` | populated \| ado-not-complete | <tree size> |
36
+ | `ado/snapshot/items/*.md` | populated \| skipped \| ado-not-complete | <N items> |
37
+ | `onenote/snapshot/<section>/*.md` | populated \| degraded | <pages enumerated/failed> |
38
+ | `email/_index/<week>_message-index.md` | populated \| degraded-list-only \| throttled-tooManyRequests | <N enumerated> |
39
+ | `email/stream/<week>_email-stream.md` | populated \| degraded-list-only | <N threads> |
40
+ | `teams/...` | ... | ... |
41
+
42
+ ## Current Bootstrap Outcome
43
+
44
+ - One-line current state per source.
45
+ - CRM stage: <plain-language interpretation of statuscode> (`internal-only` per `evidence-confidence-ladder`).
46
+ - ADO linkage: pinned engagement <id> | pending pick from N candidates | `ado-not-complete`.
47
+ - Notable gaps: ...
48
+
49
+ ## Access Limitations
50
+
51
+ | System | Status | Reason | Workaround |
52
+ |---|---|---|---|
53
+ | ADO | resolved | — | — |
54
+ | CRM | blocked-auth | tenant mismatch | `az login --tenant <id>` |
55
+ | Email | throttled-tooManyRequests | WorkIQ rate-limited | retry next refresh |
56
+
57
+ ## Bootstrap Status
58
+
59
+ ONE final line, normalized:
60
+ - `bootstrap-complete`
61
+ - `bootstrap-complete-with-coverage-gaps`
62
+ - `bootstrap-blocked — <primary reason>`
63
+ - `refresh-complete`
64
+ - `refresh-complete-with-coverage-gaps`
65
+ ```
66
+
67
+ ## Normalized status vocabulary
68
+
69
+ Use these exact strings everywhere (table cells, run-log, narrative):
70
+
71
+ - `resolved` — check passed
72
+ - `cli-available` — required CLI is installed and authenticated
73
+ - `blocked-auth` — auth missing/failed
74
+ - `partial-template` — file exists but only template scaffolding written
75
+ - `unsynced` — file does not contain real data yet
76
+ - `populated` — file contains real fetched data
77
+ - `degraded` / `degraded-list-only` — partial fetch (e.g. email body-unavailable for some messages)
78
+ - `throttled-tooManyRequests` — source rate-limited
79
+ - `ado-not-complete` — engagement record not found yet OR ADO linkage missing
80
+ - `completed-with-coverage-gaps` — final outcome with explicit gaps recorded
81
+
82
+ ## Sections to AVOID in bootstrap-status
83
+
84
+ Do NOT keep these in `bootstrap-status.md` (they belong elsewhere or become stale):
85
+
86
+ - `Fallback Inputs Used` → run-log only
87
+ - `Evidence & Current Context` → State files
88
+ - `FDE Fitness Assessment` → State/00_overview.md
89
+ - `Source Coverage` → already covered by Access Limitations
90
+ - `Recommended Actions` → State/05_action-items.md or Open Questions/
91
+ - long historical retry logs → run-log only
92
+
93
+ ## Stable-summary test
94
+
95
+ When deciding whether a detail belongs in `bootstrap-status.md`, ask: **"Does this help a future reader understand the current durable project state?"** If it's mainly describing how this one run unfolded, push it to `update-status.md` (per-run history) or the run-log instead.
96
+
97
+ ## ADO-not-complete note
98
+
99
+ When a bootstrap cannot resolve the ADO Engagement WI, include in `## Current Bootstrap Outcome`:
100
+
101
+ > ADO context sync pending: engagement record not found yet; bootstrap is usable with CRM/transcript context and should be revisited after ADO engagement creation or when the candidate WI is pinned to `integrations.yml#ado.engagement_id`.
102
+
103
+ And reflect `ado-not-complete` in the relevant Context Artifact Status rows.
104
+
105
+ ## Relationship to other artifacts
106
+
107
+ - Detailed CRM content → `crm-context/record-profile.md` + `crm/snapshot/`
108
+ - Detailed ADO tree → `ado/snapshot/tree.md` + per-item files
109
+ - Detailed transcripts → `meeting-transcripts/index.md`
110
+ - Per-run history → `update-status.md` (project-local)
111
+ - Cross-source synthesis → `State/`
112
+
113
+ `bootstrap-status.md` summarizes; it never duplicates these artifacts.
@@ -0,0 +1,95 @@
1
+ ---
2
+ applyTo: "**"
3
+ description: "Capture-learnings loop — every fix, quirk, or workaround discovered mid-run must be written back into doctrine in the same turn it is learned."
4
+ ---
5
+
6
+ # Capture-learnings loop (HARD RULE)
7
+
8
+ Every kushi skill is a learning agent. When the agent discovers a new fact, fixes a bug, hits an API quirk, or finds a workaround — that lesson MUST be persisted back to doctrine **in the same run, before the skill exits**. Otherwise the next run repeats the same mistake.
9
+
10
+ This is the difference between a skilled engineer (writes the runbook entry as soon as they finish the fix) and an amateur (fixes it in their head and forgets next month).
11
+
12
+ ## What counts as a "learning"
13
+
14
+ Capture all of these:
15
+
16
+ - **API quirks** — undocumented limits (`$top` max, mandatory headers, navigation properties that don't exist on custom entities), required preview API versions, encoding gotchas.
17
+ - **Auth fixes** — tenant IDs, audience strings, env overrides per project.
18
+ - **Schema surprises** — fields that don't exist where you'd expect, entity-set name plurals, custom-field naming conventions.
19
+ - **Resolution misses** — "the title-substring probe missed this project because…", "the default folder fast-path didn't apply here because…".
20
+ - **Workarounds** — pagination switch from one endpoint to another, separate filtered query instead of `$expand`, fallback ordering changes.
21
+ - **User pushback** — any time the user corrects scope, identity, or interpretation, that correction is doctrine.
22
+
23
+ Don't capture: one-off transient errors (network blip, expired token), or things already in the doctrine you just didn't read.
24
+
25
+ ## Where learnings live
26
+
27
+ | File | Purpose |
28
+ |---|---|
29
+ | `<KUSHI_ROOT>/plugin/learnings/<source>.md` | Per-source register: `ado.md`, `crm.md`, `email.md`, `teams.md`, `onenote.md`, `sharepoint.md`, `meetings.md`. Owned by the matching `pull-<source>` skill. |
30
+ | `<KUSHI_ROOT>/plugin/learnings/cross-cutting.md` | Lessons that span multiple sources (auth, encoding, host-tool quirks). |
31
+ | `<KUSHI_ROOT>/plugin/learnings/README.md` | Index + how-to-add-an-entry. |
32
+
33
+ ## Entry format (one per learning)
34
+
35
+ ```markdown
36
+ ### YYYY-MM-DD — short title
37
+
38
+ **Symptom**: what broke or what was confusing (one sentence, verbatim error if applicable).
39
+
40
+ **Root cause**: why it happened.
41
+
42
+ **Fix / workaround**: the exact thing that worked (code snippet or 1–2 sentences).
43
+
44
+ **Doctrine impact**: which SKILL.md, instructions file, or template was updated in the same commit. Link by relative path. If "none yet — register only", say so explicitly and open a TODO.
45
+
46
+ **Discovered during**: project name + skill that hit it (e.g. `HCA / pull-crm`).
47
+ ```
48
+
49
+ Newest entry on top. Never delete — only mark superseded with a one-liner.
50
+
51
+ ## When the loop runs (every skill, every time)
52
+
53
+ ### Preflight (start of run)
54
+ 1. Read `plugin/learnings/<source>.md` for the skill being run.
55
+ 2. Apply known workarounds without re-discovering them. If a learning says "use `$top=200` not `$top=500`", use it.
56
+
57
+ ### Mid-run (the moment a fix lands)
58
+ 1. Stop. Don't move on yet.
59
+ 2. Append a new entry to the appropriate `learnings/<source>.md` (or `cross-cutting.md`).
60
+ 3. If the fix changes how the skill should always behave, also edit the relevant SKILL.md or instructions file in the same edit batch.
61
+ 4. Then resume.
62
+
63
+ ### Postflight (end of run)
64
+ 1. If any learnings were appended, the run summary MUST list them with file path + entry title.
65
+ 2. If a doctrine file was updated, the run summary MUST mention the version bump or commit pending.
66
+
67
+ ## Doctrine vs register: when to upgrade
68
+
69
+ A `learnings/<source>.md` entry is **the proven workaround**. If the same learning hits twice (same project re-run, or a different project), promote it from register to first-class doctrine:
70
+
71
+ - Move the rule into the matching SKILL.md or a focused `*.instructions.md`.
72
+ - Leave a one-liner in the register: `→ promoted to plugin/instructions/<file>.md on YYYY-MM-DD`.
73
+
74
+ Don't pre-promote — wait for the second sighting. Otherwise the SKILL.md balloons with one-off fixes.
75
+
76
+ ## Self-check enforcement
77
+
78
+ `plugin/skills/self-check/run.ps1` deep-mode rule **D7** verifies:
79
+ - `plugin/learnings/` exists with `README.md` + at least the per-source files for `pull-*` skills.
80
+ - Each `pull-*` SKILL.md references this instruction file (`capture-learnings.instructions.md`) by relative path in its frontmatter or body.
81
+ - Each `learnings/<source>.md` has the standard entry format header.
82
+
83
+ A missing register file or unreferenced skill is a hard fail.
84
+
85
+ ## Why this matters
86
+
87
+ Without this loop:
88
+ - Same Dataverse `$expand=Annotations` 500 hits us every quarter.
89
+ - Same `$top=500` ADO updates 400-error gets re-discovered every refresh.
90
+ - Same per-project env override (iscrm vs microsoftit) gets re-debugged from scratch.
91
+
92
+ With it:
93
+ - The agent gets smarter every run.
94
+ - The next contributor reads `learnings/crm.md` once and skips a half-day of debugging.
95
+ - Doctrine stays small (only proven, recurring rules) and the register catches everything else.
@@ -0,0 +1,69 @@
1
+ ---
2
+ applyTo: "**"
3
+ description: "Cleanup-on-resolution rule — when a probe resolves a real ID/match, prune the stale 'no-match' / 'probe-trail' notes left by previous failed attempts. Don't accumulate audit cruft next to a resolved fact."
4
+ ---
5
+
6
+ # Cleanup-on-resolution (HARD RULE)
7
+
8
+ When a `pull-*` or `bootstrap-project` skill resolves a previously-unknown ID, folder, section, or record (e.g. ADO engagement WI, CRM record, OneNote section, email folder, Teams chat) — the skill MUST prune the stale "didn't find it" notes left by earlier failed attempts in the **same turn** the success lands.
9
+
10
+ ## Why
11
+
12
+ Without cleanup, every project's `integrations.yml`, `m365-mutable.json`, run-log, and `bootstrap-status.md` slowly accumulate:
13
+
14
+ - `last_discovery_result: 'no-match'` next to a now-resolved ID
15
+ - A list of probe attempts ("tried query X — 0 hits", "tried query Y — TF51005") next to the proven-correct query
16
+ - "❌ ado-not-complete" status notes next to a now-resolved engagement WI
17
+ - "⚠️ folder hint missing — root-fallback used" warnings next to a now-pinned `emailContext.folder`
18
+
19
+ The audit trail becomes noise. The user can't tell what's the current state vs what's historic failure.
20
+
21
+ ## What to clean (per surface)
22
+
23
+ When a resolution succeeds, in the same turn, prune:
24
+
25
+ ### `<project>/integrations.yml`
26
+ - Replace `<source>.last_discovery_result: 'no-match'` (or `'partial'`, `'ambiguous'`) with `'resolved'`.
27
+ - Remove any `<source>.probe_attempts` / `<source>.probe_history` arrays — those were debugging breadcrumbs, not configuration.
28
+ - Remove inline comments like `# TODO: find this`, `# could not resolve as of YYYY-MM-DD`.
29
+ - Keep `pinned_on` + `pinned_by` (those are durable provenance).
30
+
31
+ ### `<engagement-root>/.project-evidence/m365/m365-mutable.json`
32
+ - Remove entries under `m365Mutable.unresolved.<project>.<source>` if the same key was just resolved into `m365Mutable.knownSections.<project>.<source>`.
33
+ - Replace any `confidence: 'low'` entry with the new `confidence: 'high'` entry on resolution; do not keep both.
34
+
35
+ ### `<engagement-root>/<project>/Evidence/run-log.yml`
36
+ - Update `sources.<src>.coverage_state` from `degraded-list-only` / `unresolved` / `throttled-tooManyRequests` to the new healthy state on the run that succeeded.
37
+ - Keep historical run entries in `runs:` (those are durable history) — but the current `sources.<src>` summary block must reflect TODAY's resolved state, not last run's failure.
38
+
39
+ ### `<project>/bootstrap-status.md`
40
+ - Per the bootstrap-status format contract, status tables show CURRENT durable state. When a resolution fixes a previously-blocked check, update the matching row in `## Preflight Checks` / `## Context Artifact Status` / `## Access Limitations` to `resolved` / `populated` / `cli-available` and remove the previous "blocked" row (don't keep both).
41
+ - Specifically: when an ADO engagement is resolved, change `ado-not-complete` to `resolved` AND delete the `## Current Bootstrap Outcome` note that said `ADO context sync pending: engagement record not found yet`.
42
+
43
+ ## What to KEEP
44
+
45
+ Cleanup-on-resolution is about pruning **stale state**, not history. The following are durable and never pruned:
46
+
47
+ - `runs:` array in `run-log.yml` — full run history (timestamp, mode, outcome).
48
+ - Per-user `refresh-reports/<timestamp>_<mode>.md` files — per-run reports per `run-reports.instructions.md`.
49
+ - `learnings/<source>.md` entries — register entries persist; they document the fix, not the failure.
50
+ - `pinned_on` + `pinned_by` audit fields on resolved IDs.
51
+ - Coverage Notes inside evidence files for runs where the boundary truly was empty.
52
+
53
+ ## When NOT to clean
54
+
55
+ If the user explicitly pinned a `last_discovery_result: 'no-match'` themselves (they said "this project genuinely has no CRM record, stop probing") — leave it. The skill should detect explicit user-set state and not auto-clean.
56
+
57
+ Heuristic: if `<key>.pinned_by` matches the current user alias and the entry is older than 1 day, treat it as user-authoritative. If it was written by the skill itself in a previous run, it's prune-eligible.
58
+
59
+ ## Self-check enforcement
60
+
61
+ `plugin/skills/self-check/run.ps1` deep-mode rule **D9** (added v3.7.6):
62
+ - Warns if `<project>/integrations.yml` has both a resolved ID (`<source>.engagement_id` / `record_id` / `section_id`) AND a stale `<source>.last_discovery_result: 'no-match'`. That combination is impossible after cleanup.
63
+ - Warns if `bootstrap-status.md` shows `ado-not-complete` while `integrations.yml` has `ado.engagement_id` populated.
64
+
65
+ A flagged file is a doctrine miss to be fixed in the next run, not a hard fail.
66
+
67
+ ## Apply across all pull-* skills
68
+
69
+ Every `pull-<source>` skill's "Mutable hints to upsert" section MUST also reference this instruction file by relative path. The cleanup is part of the upsert, not a separate phase.
@@ -0,0 +1,79 @@
1
+ ---
2
+ applyTo: "**/skills/bootstrap-project/**, **/skills/pull-crm/**, **/skills/refresh-project/**"
3
+ ---
4
+
5
+ # CRM bootstrap discovery — HARD rule (kushi v3.11.0+)
6
+
7
+ ## The defect this rule exists to prevent
8
+
9
+ Bootstrap runs that wrote `boundaries.crm.disabled: true` after a single shallow probe (e.g. WorkIQ-only, or no live Dataverse REST call at all) — when in fact a live `contains(new_title, '<token>')` query would have resolved the record instantly. The result: pull-crm is then never dispatched on subsequent refreshes, the project Evidence/crm/ folder stays empty, and the CRM record drifts unsynced. Discovered 2026-05-18 on the John Deere bootstrap — FE-2026-001791 was missed because the title-first REST probe was never issued.
10
+
11
+ ## The rule
12
+
13
+ Before `bootstrap-project` (or `refresh-project` on first CRM pull) is allowed to write `boundaries.crm.disabled: true`, the FULL 4-step Dataverse REST resolution sequence from `plugin/skills/pull-crm/SKILL.md#resolution-order-when-crmrecordid-is-unset` MUST be attempted with live tokens against the configured `iscrm.crm.dynamics.com` (or equivalent) endpoint. `disabled: true` is ONLY legitimate when:
14
+
15
+ 1. **Steps 1–4 all returned 0 results** (logged with the exact OData URLs attempted, counts, and timestamps), AND
16
+ 2. **Step 5 (ask user)** was actually presented to the user and the user confirmed there is no CRM record, OR the user is unreachable and the project owner explicitly opted to defer.
17
+
18
+ Any other path that writes `disabled: true` is a defect. The correct fallback for "couldn't reach Dataverse" or "auth failed" is to leave the boundary empty (NOT `disabled: true`) with `reason: 'crm-auth-unavailable-YYYY-MM-DD'` so the next run retries.
19
+
20
+ ## Required execution at bootstrap time
21
+
22
+ ```powershell
23
+ # 1. Acquire Dataverse token (per pull-crm Step Auth)
24
+ $config = Get-Content "<engagement-root>/.project-evidence/crm/config.yml" | ConvertFrom-Yaml
25
+ $token = (az account get-access-token --tenant $config.tenant_id --resource $config.base_url --query accessToken -o tsv --only-show-errors).Trim()
26
+ $headers = @{
27
+ Authorization = "Bearer $token"
28
+ Accept = 'application/json'
29
+ 'OData-Version' = '4.0'
30
+ 'OData-MaxVersion' = '4.0'
31
+ Prefer = 'odata.include-annotations="OData.Community.Display.V1.FormattedValue"'
32
+ }
33
+
34
+ # 2. Step 1 — Title-first (REQUIRED)
35
+ $url = "$($config.base_url)/api/data/v9.2/$($config.entity.entitySetName)?`$select=$($config.entity.primary_id_attribute),$($config.entity.primary_name_attribute),$($config.entity.request_id_attribute),statecode,statuscode,createdon,modifiedon,_new_customer_value&`$filter=contains($($config.entity.primary_name_attribute),'<token>')&`$top=20"
36
+ # If results >= 1 → present candidates, persist chosen record_id, STOP.
37
+
38
+ # 3. Step 2 — Account(s) fallback (REQUIRED if Step 1 returns 0)
39
+ # Resolve customer in accounts entity, then for EACH matching account query engagement records.
40
+ # Multiple Deere-style accounts (e.g. JOHN DEERE, DEERE COMPANY, JOHN DEERE COASTRUCTION) MUST all be tried.
41
+
42
+ # 4. Step 3 — Wide-text fallback (REQUIRED if Step 2 returns 0)
43
+ # contains() on new_businessscenariotechnicalblocker, new_engagedwith, new_engagementobjectives.
44
+
45
+ # 5. Step 4 — Recent-slice client-rank (REQUIRED if Step 3 returns 0)
46
+ # Pull top 200 by modifiedon desc, rank client-side by token overlap.
47
+
48
+ # 6. Step 5 — Present top 5 to user; never auto-pick on multi-match.
49
+ ```
50
+
51
+ ## Required logging
52
+
53
+ Every bootstrap (and first-refresh) CRM-discovery run MUST write the attempt trail to `<project>/Evidence/<alias>/refresh-reports/<ts>_bootstrap.md` under a `## CRM resolution attempts` section, including:
54
+
55
+ | Step | Query | Result count | Outcome |
56
+ |---|---|---|---|
57
+ | 1 | `contains(new_title,'<token>')` | N | resolved / 0-results |
58
+ | 2 | accounts `contains(name,'<token>')` → FE per account | N1+N2+… | resolved / 0-results |
59
+ | 3 | `contains(new_businessscenariotechnicalblocker,'<token>')` etc | N | resolved / 0-results |
60
+ | 4 | recent-slice top 200 client-rank | N | top match score / 0-results |
61
+ | 5 | user presented top 5 candidates | yes/no | picked / declined / deferred |
62
+
63
+ This is non-negotiable. The trail is the audit that justifies whichever value lands in `boundaries.crm` (`record_ids: [...]` OR empty-with-reason OR `disabled: true`).
64
+
65
+ ## Anti-patterns
66
+
67
+ 1. **Setting `boundaries.crm.disabled: true` without trying live Dataverse REST.** Defect — pull-crm will never run again.
68
+ 2. **Trying only WorkIQ for CRM resolution.** WorkIQ summarizes and may miss records; REST is the canonical path.
69
+ 3. **Filtering by `statecode` during candidate search.** Hides closed / withdrawn / inactive records. See pull-crm Rule 5.
70
+ 4. **Filtering by `new_companyname`.** Not a valid attribute on `new_frontierengineeringtriage`. Customer is `_new_customer_value` lookup. Always returns 0; looks like a permissions problem but is a schema problem.
71
+ 5. **Stopping at first account match in Step 2.** Multiple accounts can share a token (Deere example). Iterate ALL.
72
+ 6. **Silent fallback to "narrate CRM from email".** Forbidden by `scope-boundaries.instructions.md` Rule 3 and pull-crm hard-prereq.
73
+
74
+ ## Cross-references
75
+
76
+ - `plugin/skills/pull-crm/SKILL.md#resolution-order-when-crmrecordid-is-unset` — the canonical 4-step sequence (now 5 with user-ask).
77
+ - `plugin/skills/bootstrap-project/SKILL.md` Step 4a — must dispatch this discovery before deciding `disabled`.
78
+ - `plugin/instructions/crm-internal-vs-confirmed.instructions.md` — once resolved, do not over-claim from CRM-only fields.
79
+ - `plugin/instructions/cleanup-on-resolution.instructions.md` — when this discovery resolves a record, prune stale `disabled: true` notes in the same turn.
@@ -0,0 +1,79 @@
1
+ ---
2
+ applyTo: "**"
3
+ ---
4
+
5
+ # CRM field values vs confirmed facts — HARD rule (kushi v3.11.0+)
6
+
7
+ ## The rule
8
+
9
+ **A CRM field value is NEVER automatically a confirmed fact.** A Dataverse field update records an internal decision; it does not prove the decision has been (a) communicated to the customer, (b) approved by deal desk / finance / legal, or (c) confirmed in a customer-facing transcript, note, email, or meeting artifact.
10
+
11
+ Adapted into kushi from Nova doctrine (see `fde-grounding.instructions.md > CRITICAL: CRM Field Values vs. Confirmed Facts`).
12
+
13
+ ## Three states every CRM-sourced assertion must be classified into
14
+
15
+ 1. **Internal-only decision** — team recorded a direction in a CRM field, but no customer-facing or external-stakeholder evidence confirms it. Status fields commonly look like this (e.g. `Not Yet Requested`, `Pending`, `Evaluating`, `TBD`). **DO NOT** write this as a settled fact in State/, FDE reports, or 6Q.
16
+ 2. **Communicated to customer** — there exists a customer-facing transcript / note / email / meeting that postdates the CRM field update and references the decision. Still not final — typically still pending commercial / deal-desk approval.
17
+ 3. **Confirmed with all stakeholders** — customer agreement + commercial approval + (where applicable) legal sign-off / signed artifact. Only this state is a settled fact and may be reported as such.
18
+
19
+ ## How to apply recency precedence correctly to CRM fields
20
+
21
+ - The CRM field's `modifiedon` timestamp shows when the record was **updated**, NOT when the decision was **confirmed**.
22
+ - For every materially important CRM field claim (funding model, scope decision, owner, timeline gate, SI lane, ECIF / ESIF state), **cross-check against the latest customer-facing artifact** in `Evidence/<alias>/{meetings,email,onenote,teams,sharepoint}/` (use `meetings/verbatim/*/transcript.*` first per `meetings-verbatim-required.instructions.md`).
23
+ - If the latest customer-facing artifact predates the CRM field update AND no post-update customer-facing artifact confirms the decision → state explicitly as `internal decision recorded in CRM (<date>), pending customer/deal-desk confirmation (no confirming artifact found post-<date>)`.
24
+ - If a post-update artifact confirms → cite both (CRM date + confirming artifact + date).
25
+ - If a post-update artifact contradicts → mark `Conflicting evidence` and surface to `<project>/State/09_open-questions.md` (full profile) or `<project>/OPEN-QUESTIONS-DRAFT.md` (standard profile).
26
+
27
+ ## Required output classification for State/, FDE reports, consolidation, ask-project answers
28
+
29
+ Every materially important CRM-sourced assertion in any kushi-generated artifact must be tagged as one of:
30
+
31
+ - `CRM-only` — internal signal, no external confirmation
32
+ - `Cross-verified` — confirmed by ≥ 1 non-CRM artifact (cite both)
33
+ - `Conflicting evidence` — non-CRM source contradicts CRM/form (cite both, list in open questions)
34
+
35
+ Acceptable non-CRM confirmation sources (in priority order per kushi precedence):
36
+
37
+ 1. Meeting transcripts (`Evidence/<alias>/meetings/verbatim/*/transcript.*`)
38
+ 2. Curated meeting stream notes (`Evidence/<alias>/meetings/stream/*.md`)
39
+ 3. OneNote pages
40
+ 4. ADO work-item discussions / comments
41
+ 5. Emails (sender-domain matching customer)
42
+ 6. Teams chats / channels
43
+ 7. Approved shared artifacts (SharePoint / Loop / docs)
44
+
45
+ ## Citation format
46
+
47
+ ```
48
+ [source: CRM record FE-XXX field <field-name> · YYYY-MM-DD (internal-only)]
49
+ [source: CRM record FE-XXX field <field-name> · YYYY-MM-DD; cross-verified by Evidence/<alias>/meetings/verbatim/<dir>/transcript.vtt · YYYY-MM-DD]
50
+ [source: CRM record FE-XXX field <field-name> · YYYY-MM-DD; conflicts with Evidence/<alias>/email/snapshot/<file>.md · YYYY-MM-DD — see open questions]
51
+ ```
52
+
53
+ ## Worked example
54
+
55
+ - 2026-03-20 transcript: team exploring funding options.
56
+ - 2026-03-23 transcript: alternative under discussion.
57
+ - 2026-03-26: CRM `new_ecifstatus` updated to `Not Yet Requested`.
58
+ - 2026-04-08 (today): no post-3/26 transcript confirms ECIF with customer or deal desk.
59
+
60
+ **Correct write-up:**
61
+ > Internal team decision favors ECIF (per CRM 3/26 update) but formal approval from deal desk and customer confirmation are still pending. Latest customer-facing transcript (3/23) predates the CRM decision; no post-decision confirmation found. `[source: CRM record FE-XXX field new_ecifstatus · 2026-03-26 (internal-only)]`
62
+
63
+ **Incorrect write-up (forbidden):**
64
+ > ECIF is the selected funding model. `[source: CRM record FE-XXX field new_ecifstatus · 2026-03-26]`
65
+
66
+ ## Where this rule binds
67
+
68
+ - `skills/build-state/` — every CRM-sourced row in `State/01_decisions.md`, `State/05_action-items.md`, `State/06_risks-and-issues.md`, `State/07_timeline-and-milestones.md` must carry the 3-state tag and the cross-check citation chain.
69
+ - `skills/ask-project/` — answers grounded in CRM fields must explicitly distinguish internal-only vs cross-verified; never collapse into a bare assertion.
70
+ - `skills/fde-report/` / `skills/fde-triage/` — FDE Triage Living Reports follow this rule; the `Verification Evidence` section is required for any CRM-sourced gate.
71
+ - `skills/consolidate-evidence/` — when merging across contributors, conflicting CRM-vs-non-CRM evidence becomes a `Conflicting evidence` row, never silently resolved.
72
+
73
+ ## Anti-patterns
74
+
75
+ 1. Treating a CRM `Status` field as proof of customer commitment.
76
+ 2. Reading CRM `modifiedon` as the decision-confirmation date.
77
+ 3. Claiming "ECIF selected" / "Lane X chosen" / "Customer signed off" from CRM alone with no post-date customer-facing artifact.
78
+ 4. Blending CRM internal decision and older customer-facing context into one averaged narrative without flagging the gap.
79
+ 5. Dropping the `(internal-only)` tag in citations because it "reads cleaner".