← monday CRMCONTENT HISTORY

Update to monday CRM

Snapshot Nov 11, 2026 · 13:33 UTC · version 0.4.5-draft

Collection source: downloaded plugin package.

WHAT CHANGED

First saved snapshot

No earlier snapshot is available to establish a change.

Save monday CRM and check future changes in Following.

Up to 3 plugins free · no card required.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "description": "Diagnoses a CRM board for missing/stale data, surfaces gaps to the user, and runs an enrichment + normalization loop to fix them in bulk. Normalizes phone numbers, country codes, email casing; fills missing emails from company domain heuristics; flags unowned and amount-less deals. Different from board-diagnosis — that one **reports**, this one **acts**. Use when someone says \"clean my CRM\", \"fix my data\", \"normalize phone numbers\", \"find missing emails\", \"my board is messy\", or any variation of bulk data fix.",
  "included_files": [
    {
      "relative_path": "agents/openai.yaml",
      "size_in_bytes": 187
    },
    {
      "relative_path": "assets/icon.png",
      "size_in_bytes": 22022
    },
    {
      "relative_path": "examples/01-phone-email-normalization.md",
      "size_in_bytes": 3730
    }
  ],
  "name": "bulk-data-hygiene",
  "skill_md_contents": "---\nname: bulk-data-hygiene\ndescription: Diagnoses a CRM board for missing/stale data, surfaces gaps to the user, and runs an enrichment + normalization loop to fix them in bulk. Normalizes phone numbers, country codes, email casing; fills missing emails from company domain heuristics; flags unowned and amount-less deals. Different from board-diagnosis — that one **reports**, this one **acts**. Use when someone says \"clean my CRM\", \"fix my data\", \"normalize phone numbers\", \"find missing emails\", \"my board is messy\", or any variation of bulk data fix.\n---\n\n# Bulk Data Hygiene\n\n> **Platform notes (ChatGPT and Codex).** Before any write, show the concrete action or batch and obtain explicit approval. Use only tools exposed in this session; if a named tool is missing, say so and stop that step. Use visible plain-text `monday-crm-skill-id: <skill>` markers; monday Docs can strip HTML comments. Read back newly created artifacts to verify their marker. Before creating, search within the relevant workspace and read candidate content; match title, skill marker, board or item, and date or period. Treat surviving legacy `<!-- monday-crm-skill-id: … -->` and `<!-- claude-skill-id: … -->` markers as matches. If a candidate has the right title and generated-by footer but lacks a marker, or matching is ambiguous or incomplete, do not create or update automatically: show the candidate and ask the user. If search or read-back is unavailable, stop the write. For meeting recaps also include and verify a visible `meeting-id: <id>` marker; if either marker is missing, do not infer a match or create a duplicate automatically.\n\nAudits a CRM board for data gaps, lets the user pick which gaps to fix, then runs a bounded write loop to fix them. The \"do\" sibling to `board-diagnosis`'s \"report.\" Designed for the recurring \"my CRM is messy\" pain — Sidekick's #1 negative-sentiment JTBD.\n\nFlow: **Trigger → Audit → User picks gaps → Plan writes → Execute (bounded, idempotent) → Summary**.\n\n## Input\n- Optional: board name/ID from the user's request.\n- Optional: pre-declared mode (Default / Silent / Proactive).\n\n## Output\n- **α (default):** Bounded write loop on the chosen board. Normalizes phone/country/email; can also batch-set owner / source / last-touch / stage when the user explicitly picks those gap types and signals are clean. A `Hygiene fix — <Mon DD>` doc lists every change with `Generated by monday CRM plugin` footer + `monday-crm-skill-id: bulk-data-hygiene`.\n- **β (opt-in):** A \"before/after\" snapshot doc (preserves the prior state for ~30 days, in case a pass over-corrected).\n- **Proactive extension:** Out of scope — bulk-data-hygiene is *already* a write skill; proactive doesn't change its behavior. Default mode + batched confirm is the only write surface.\n\n## Knowledge\n- Gap taxonomy + audit rules (§ Step 3).\n- Normalization recipes (§ Step 5).\n- Shared artifact conventions (§ Shared patterns).\n\n## Tools (MCP)\n- `get_user_context`, `list_workspaces`, `search`, `get_board_info`, `get_column_type_info`, `get_board_items_page`, `board_insights` — Gather.\n- `change_item_column_values` — the core write. Loop with bounded concurrency.\n- `create_doc` / `create_update` — publish summary + before/after snapshot.\n- `update_items` — batch writes (up to 40 items per call) using the same column-value formats as `change_item_column_values`. Phone: `{\"phone\": \"+12125551234\", \"countryShortName\": \"US\"}`; email: `{\"email\": \"a@b.com\", \"text\": \"a@b.com\"}`; country: `{\"countryCode\": \"US\", \"countryName\": \"United States\"}`. If a column type is rejected, skip that fix type and list it in the hygiene doc. Don't fall back to a generic GraphQL tool.\n\n## Cross-skill handoffs\n- **From board-diagnosis:** the diagnosis report ends with *\"run the `bulk-data-hygiene` skill to fix\"* — this skill is the natural handoff target.\n- **From morning-briefing:** if morning brief flags >N hygiene issues, suggest this skill.\n- **To morning-briefing / forecast-dashboard:** clean data feeds them — point user there once hygiene is done.\n\n\n## Draft first, then offer publication\n\nUse available read-only tools to prepare the complete requested deliverable before asking to publish. Present it as a readable, editable artifact in the host when available; otherwise show the full draft in chat. A native monday dashboard or board cannot be created outside monday: present a concrete preview or blueprint, not a claim that it exists. State meaningful missing data or assumptions. Then ask once whether to publish the displayed draft to the specified monday destination; allow edits or decline. On decline, retain the draft and stop without monday writes. On approval, publish only the shown content and concrete actions the user approved. Separately describe and obtain approval for record edits, notifications, contacts, automations, or other actions not included in that publication. Do not ask to choose a mode before producing the draft; use read-only default behavior. Ask only for information essential to prepare a useful, accurate draft, and state reasonable defaults. This draft-first rule takes precedence over earlier or later mode and publish-first instructions.\n\n---\n\n\n\n## Offer a useful next step\n\nAt the end of a completed response, offer one specific, relevant next thing ChatGPT can do based on what is actually available: offer to prepare a reviewable cleanup plan for a specific gap. Ask whether the user wants that help; do not begin it before they respond. Do not repeat an existing unanswered question or make the offer when the task cannot continue because of a connection or permission failure. Do not promise capabilities, events, or results that have not been verified. Approval to explore or draft is not approval to publish, send, or change CRM data.\n\n## Step 0: Connector check\n\n**Goal:** Fail fast if the user has no monday MCP connection — no partial writes, no guessing.\n\n1. Try `get_user_context`.\n2. If the tool is missing, returns an auth-style error, or the user has no accessible workspace: print *\"I don't see the monday connector active on this session. Install it from https://monday.com/mcp, then run this skill again.\"* and stop.\n3. If it works, cache `user.id` + `user.name` for artifact metadata.\n\n---\n\n## Step 1: Detect mode\n\nPrepare the audit read-only first. Do not ask for a mode. Separate report publication from approval of any proposed record fixes.\n\nHard safety rail regardless of mode: **no deletes, no amount-column writes, no cross-workspace moves, no normalization on columns the user didn't pick**. Stage / owner / source / last-touch edits ARE allowed when the user explicitly selected that gap type at Step 4 — bulk-data-hygiene is the canonical place for board-wide column rewrites that other skills point users at.\n\n---\n\n## Step 2: Resolve board\n\n**Goal:** Land on a single `boardId` before reading any data.\n\n1. If the user passed a board from the user's request: numeric → use as `boardId` directly; string → `search` with `searchType: \"BOARD\"`. One match → use it; multiple → a short question to the user with numbered options.\n2. If the user didn't name a board: `get_user_context` → scan `relevantBoards` + `favorites` for names matching `deals|opportunities|pipeline|leads|sales|contacts|accounts`. One candidate → use it; multiple → a short question to the user with numbered options; zero → `list_workspaces` → `search`. Still zero → stop with *\"I don't see a CRM-shaped board in your workspaces. Tell me the board name or ID, or run the `workspace-builder` skill to create one.\"*\n\n---\n\n## Step 3: Audit — surface the gaps (Gather + Synthesize)\n\n**Goal:** Compute a gap report grouped by fix-type, with a concrete count per type.\n\nPull all items via `get_board_items_page` (paginated up to 5000, then sample with disclosure). For each item, check:\n\n| Gap type | Audit rule | Fixable here? |\n|---|---|---|\n| Missing email | `email` column empty AND contact name + company present | ✅ (heuristic guess from company domain — flagged with `Source = guessed`) |\n| Missing phone | `phone` column empty | ❌ (no source — flag only) |\n| Phone format | Phone present but no country code, or contains letters / >2 separators | ✅ (E.164 normalization) |\n| Country code missing | `country` column empty AND phone has parseable country code OR email domain has CCTLD | ✅ |\n| Email case | Mixed-case (`John.Doe@Acme.COM`) | ✅ (lowercase) |\n| Stale last-touch | `date` last-touch >90d AND stage active | Flag unless an actual interaction date is supported by accessible evidence or explicitly confirmed by the user; selection alone does not establish contact |\n| Owner missing | `people` owner empty AND stage active | ✅ (bulk-assign within a user-picked group/scope) |\n| Stage drift | items in non-canonical groups; user explicitly wants normalization | ✅ (bulk-set within user-picked scope) |\n| Amount missing | `numbers` value empty AND stage past \"qualifying\" | ❌ (flag only — forecast integrity rail) |\n| Source missing | `status` source column empty | ✅ (bulk-set within user-picked scope) |\n\nResolve columns by **type**, not English name (same rule as morning-briefing). Skip gap types where the matching column doesn't exist.\n\nProduce the audit summary in chat:\n\n```markdown\n## Audit — <board name>\n\n**Items scanned:** N\n**Gaps found:**\n- ✅ Fixable (column normalization): <N> phone formats · <N> mixed-case emails · <N> missing country codes · <N> guessable missing emails\n- ✅ Fixable (bulk-set within picked scope): <N> missing owners · <N> stale last-touch · <N> missing sources · <N> stage drift\n- ⚠️ Flag-only: <N> missing phones (no source) · <N> missing amounts (forecast integrity rail)\n\nPick which fixable gaps to run, or \"all\". Flag-only items list in the summary doc — no writes.\n```\n\n---\n\n## Step 4: User picks (Synthesize)\n\nFirst present the complete audit and a reviewable plan of proposed fixes and limitations. Ask whether to publish the audit; independently ask which record fixes to apply only if the user wants fixes. Publishing the report does not approve record edits.\n\na short question to the user with numbered options, multiSelect:\n\n- \"Phone formats (N items)\"\n- \"Mixed-case emails (N items)\"\n- \"Country codes from phone/email (N items)\"\n- \"Guess missing emails from company domain (N items) — *heuristic, may miss*\"\n- \"Bulk-assign owner (N items) — *requires picking a scope (group / status filter) and a target owner*\"\n- \"Correct last-touch from verified interaction dates (N items) — *requires evidence or a user-confirmed actual interaction; never replaces a more-recent date*\"\n- \"Bulk-set source (N items) — *requires picking a scope and a source value*\"\n- \"Normalize stage drift (N items) — *requires picking a scope and target stage*\"\n- \"All column-normalization fixes\" (the first four — bulk-set ones still need their per-fix scope picker)\n\nFor each bulk-set option, after Step 4 selection, run a **scope picker** sub-prompt: *\"Which group / status filter should this apply to? Pick a target value.\"*. Never bulk-set across the entire board without scoping.\n\nIf user picks \"Guess missing emails\", explain: *\"These would be unverified guesses from a verified company domain. The plugin has no validated accuracy estimate. A guessed address is not a verified recipient and must not be used for sending until independently verified. Review the exact candidates and their guessed-source flags before approving.\"* If the domain or a source flag is unavailable, leave the email blank.\n\nHard rule: **no fix runs without an explicit Step 4 selection** (and a scope-picker confirm for bulk-set options). Empty selection → stop with summary-only.\n\n---\n\n## Step 5: Plan + execute (Publish)\n\n**Goal:** Bounded, idempotent loop. Every write is undoable.\n\nFor each picked gap type, build a write plan: list of `(itemId, columnId, oldValue, newValue, reason)`.\n\n### Normalization recipes\n\n- **Phone format → E.164:**\n  - Strip non-digit/`+` characters.\n  - If no leading `+` AND country column is populated → prepend dial code per country.\n  - If no leading `+` AND country empty AND phone starts with a known country prefix (e.g., `1` for US, `44` for UK) → prepend `+`.\n  - Edge case: phone is too short (<7 digits) or too long (>15 digits) → skip with reason \"unparseable\".\n- **Email lowercase:** `value.toLowerCase()`. Only write if changed.\n- **Country code from phone:** parse leading dial code → ISO-2 country code → write to country column.\n- **Country code from email CCTLD:** if email ends in `.uk` / `.de` / `.fr` etc. and country empty → infer.\n- **Email guess:** for items with name + company-domain present, try `<firstname>.<lastname>@<domain>` lowercase. **Add a `Source = guessed` status flag** on the item so the user can filter and verify later. If a source/status column is missing, prompt the user once to add one — never write the email without a verifiability flag.\n- **Bulk-set owner / source / stage:** within the user-confirmed scope (group / status filter from Step 4 sub-prompt), apply the chosen target value via `change_item_column_values`. Never extend beyond the picked scope.\n- **Correct last-touch:** use the date of an actual interaction from accessible evidence or the user's explicit confirmation. Show the source and proposed date in the approval plan. Merely running the cleanup or selecting a stale-date gap never justifies setting it to today. Without an evidenced date, flag the gap and skip the write. Never overwrite a more-recent date.\n\n### Bounded execution\n\n1. Cap total writes at **500 per run**. Above that, prompt: *\"<N> writes planned, capped at 500 per run. Run first 500 now and re-trigger for the rest, or narrow your selection?\"*\n2. Execute via `change_item_column_values`, batched by item (one call per item updates multiple columns at once). Concurrency: **5 in flight max** to avoid rate limits.\n3. Confirm before write in every mode — single concrete batched plan:\n   - *\"Plan: <N> phone normalizations · <M> email lowercase · <K> country fills · <O> owner assignments (scope: <group>) · <S> source backfills (scope: <filter>) · <T> stale-last-touch fills · <D> stage drift fixes (scope: <group> → <target>). Proceed? (yes / show 3 examples per fix-type / no).\"*\n4. After each batch of 50 writes, print progress: `Updated 50/500. Continuing...`.\n\n### Idempotency\n\nCompute `oldValue == newValue` per cell — never write a no-op (avoids audit-log noise).\n\n### Failure handling\n\n- **429 rate limit:** backoff 3x (1s, 2s, 4s). On third fail, halt the loop and emit a partial summary with \"<X>/<N> done; <Y> failed; re-run to continue.\"\n- **Per-item permission error:** skip that item, log to summary, continue.\n- **Per-column write error (e.g., column type changed mid-run):** skip that column on that item, log, continue.\n- **User halts mid-run:** keep writes done so far, show a partial summary draft, and ask before publishing it. Print the **last item ID written** so the user can pick up from there.\n\n---\n\n## Step 6: Publish summary doc (Publish)\n\nShow the full summary before asking whether to publish it. Only on approval, `create_doc` (idempotent — search same-day docs by title + skill-id marker, update in place if found), title `Hygiene fix — <Mon DD>`. Body:\n\n```markdown\n# Hygiene fix — <board name> · <Mon DD>\n\nmonday-crm-skill-id: bulk-data-hygiene\n\n## Writes executed (<N>)\n| Item | Column | Old → New | Scope / reason |\n|---|---|---|---|\n| ... | ... | ... | ... |\n\n## Skipped (<N>)\n| Item | Column | Reason |\n|---|---|---|\n\n## Flag-only (no writes)\n- N items missing phone (no source — manual entry required)\n- N items missing amount (forecast integrity rail — review manually)\n\n## Undo\nEvery write here is reversible. The \"Old\" column above shows the prior value. To revert, copy the rows you want undone into a follow-up run with `--undo` (coming in v0.2; for v0.1, restore manually using the Old values).\n\n---\nGenerated by monday CRM plugin · <ISO timestamp>\n```\n\nOptional β: `create_update` on a \"snapshot\" item with the pre-fix state of every touched item, for 30-day rollback window. Only runs if user opted in at Step 4.\n\n---\n\n## Step 7: Close the loop\n\nOne-line chat summary: `Cleaned <N> cells across <M> items on <board>. <K> skipped. Doc: <url>.`\n\nIf any failures: `<N> failed (rate limits / permissions). Re-run to retry — already-fixed cells are idempotent.`\n\n---\n\n## Shared patterns\n\n- **No prefix on titles.** Findability via `Generated by monday CRM plugin · <ISO timestamp>` footer + `monday-crm-skill-id: bulk-data-hygiene` in summary/snapshot doc bodies.\n- **Idempotency at the cell level** — never write `oldValue == newValue`.\n- **Safety rail.** No deletes, no amount-column writes, no cross-workspace moves, no normalization on columns the user didn't pick. Owner / source / stage / last-touch edits require an explicit Step 4 selection AND a scope-picker confirm.\n- **Bounded run** — 500 writes max per execution. Anything bigger gets split.\n- **Concurrency cap** — 5 in flight max.\n- **Type-based column resolution** for non-English boards.\n- **Confirm in batches, not per-item.**\n\n---\n\n## Error handling reference\n\n| Failure | Behavior |\n|---|---|\n| Connector missing | Step 0 stops; print install link. |\n| No board found | Step 2 stops; suggest `workspace-builder`. |\n| Empty selection at Step 4 | Skip fixes; show the audit draft and publish it only if explicitly approved. |\n| 0 fixable gaps | Print \"Board's already clean — N flag-only items in summary doc.\" |\n| 429 rate limit | Backoff 3x; halt on third with partial summary. |\n| Per-item permission error | Skip, log, continue. |\n| Column type changed mid-run | Skip that column on that item, continue. |\n| User halt | Keep writes done; emit partial summary with last-written item ID. |\n| Email-guess accuracy concern | Strong warning at Step 4; `Source = guessed` flag on every guessed write. |\n\n---\n\n## Completion criteria\n\n- [ ] Step 0 (connector check) passed.\n- [ ] User explicitly selected at least one gap type at Step 4 before any write.\n- [ ] For bulk-set gap types (owner / source / stage / last-touch), a scope-picker confirm ran before write.\n- [ ] Every write was non-no-op (idempotency).\n- [ ] Total writes ≤ 500 (or user explicitly approved a multi-run plan).\n- [ ] Summary doc lists every write (item, column, old→new, scope/reason) and every skip (with reason).\n- [ ] Summary doc body carries `monday-crm-skill-id: bulk-data-hygiene` + `Generated by monday CRM plugin` footer.\n- [ ] Safety rail held: no deletes, no amount-column writes, no cross-workspace moves, no writes outside the user-selected gap types or picked scope.\n- [ ] Guessed emails carry a `Source = guessed` flag (or were withheld if no flag column existed); they are never treated as verified recipients.\n- [ ] Every last-touch correction has an evidenced or user-confirmed actual interaction date; stale dates were not reset just because cleanup ran.\n"
}

SHA-256 of public snapshot: ad6c81ad5a452c38faeb079d0e65ca058d528a14e56c174c55303530f51c4f8d