reference/cleanup-checklist.md
# Cleanup checklist
The fields the `crm-maintenance` skill checks during a deal cleanup, plus the evidence needed to flag each. This runs when the user says "clean up HubSpot" or "is this deal up to date" scoped to a specific deal.
Work through every item. Present findings as a review list; write only what the user approves.
## 1. Last activity date
**Check:** Is the deal's `hs_lastactivitydate` older than the most recent real interaction (email or meeting) involving its associated contacts?
**Evidence to flag:** An email thread or calendar event in the last 14 days with one of the deal's contacts, newer than `hs_lastactivitydate`.
**Proposed action:** Offer to log the missing activity. Don't mark the deal itself; logging the activity updates `hs_lastactivitydate` automatically.
## 2. Next-step field (`hs_next_step`)
**Check:** Does the current `hs_next_step` still reflect what was agreed in the latest email or meeting?
**Evidence to flag:** The field says "send pricing" and a subsequent email shows pricing was sent. Or the field is blank and a recent meeting explicitly set a next action.
**Proposed action:** Propose a new value (or blank if the step is completed). Show current → proposed side-by-side.
## 3. Deal stage (`dealstage`)
**Check:** Do recent interactions suggest the deal has moved stage — e.g., proposal sent, contract signed, lost to a competitor?
**Evidence to flag:** Explicit language in email/meeting notes like "we're moving forward," "we signed," "we went with [competitor]," or a change in meeting cadence.
**Proposed action:** **Flag only — never change stage automatically.** Surface the evidence and let the user decide.
## 4. Close date (`closedate`)
**Check:** Has the latest email/meeting referenced a revised timeline that conflicts with the current `closedate`?
**Evidence to flag:** Phrases like "pushing to Q3," "we'll sign by end of month," or "delayed until next quarter."
**Proposed action:** Propose an updated date. Show current → proposed.
## 5. Amount (`amount`)
**Check:** Has the latest email/meeting mentioned a different deal size?
**Evidence to flag:** Explicit revised pricing in a pricing email, expanded scope in a meeting, or reduced scope the customer accepted.
**Proposed action:** Propose an updated amount. Show current → proposed.
## 6. Associated contacts
**Check:** Are there people on recent emails or meetings with the deal's contacts who aren't themselves associated to the deal?
**Evidence to flag:** An email CC or meeting attendee whose domain matches an existing deal contact's domain but who isn't on the deal.
**Proposed action:** Propose adding the missing contact(s) to the deal. Create the contact in HubSpot first if they don't exist yet.
## 7. Notes hygiene
**Check:** Are there notes or activities older than 90 days that explicitly contradict the current deal state?
**Evidence to flag:** A note saying "customer is lost" on a deal still in an open stage, or a note with an outdated contract amount.
**Proposed action:** Add a new note clarifying the current state — **never edit or delete** the old note. Append, don't rewrite history.
## Output format
Present findings as a numbered list, each item showing:
- What was flagged
- The evidence (email subject + date, or meeting title + date)
- The proposed action (or "flag only" for stage changes)
Then ask for approval item-by-item or bulk-approve. Write only what's approved.
reference/examples/cleanup-deal.md
# Example: clean up a stale deal
Worked scenario for the cleanup path — user asks the skill to audit one specific deal, skill surfaces findings and waits for approval.
## Setup
- Deal: "Acme Q2 Expansion" — stage "Proposal Sent," amount $18K, close date May 15, next step "send pricing," `hs_lastactivitydate` 22 days ago
- Deal has two associated contacts: Sarah Lin and Ben Rivera
- Last 14 days of email with Sarah include an Apr 18 thread titled "Re: Acme pricing" where Sarah confirmed the pricing and said "we'll sign once our legal review wraps, should be mid-June"
- A calendar event on Apr 20 titled "Acme — legal review walkthrough" included a new person, `maria.chen@acme.com`, who is not in HubSpot and not on the deal
- No email, meeting, or note suggests the deal has moved stage
## User says
> "Clean up the Acme Q2 Expansion deal."
## Skill behavior
**Step 1 — Identify intent.**
Cleanup path, scoped to one named deal.
**Step 2 — Gather context.**
Pulls the deal record. Pulls email threads from the last 14 days involving Sarah and Ben. Pulls calendar events from the same window.
**Step 3 — Resolve.**
Deal explicitly named — no resolution needed. Contacts on recent emails/meetings: Sarah (on deal), Ben (on deal), Maria Chen (not on deal, not in HubSpot).
**Step 4 — Walk the cleanup checklist.**
Skill does NOT write anything yet. Produces this review list:
> **Cleanup review — Acme Q2 Expansion**
>
> **1. Last activity date.** Deal shows 22 days old. Newer activity found: Apr 18 email from Sarah ("Re: Acme pricing"), Apr 20 meeting ("Acme — legal review walkthrough"). Neither is logged to HubSpot.
> **→ Propose:** Log the Apr 18 email and the Apr 20 meeting as activities on this deal.
>
> **2. Next-step field.** Currently "send pricing." The Apr 18 email shows pricing was sent and confirmed by Sarah.
> **→ Propose:** `hs_next_step`: "send pricing" → "wait for legal review sign-off."
>
> **3. Deal stage.** Currently "Proposal Sent." No evidence of stage movement — Sarah said they'll sign *after* legal review. Stage is consistent with reality.
> **→ Flag only — no change.**
>
> **4. Close date.** Currently May 15. Sarah's Apr 18 email says "should be mid-June."
> **→ Propose:** `closedate`: May 15 → June 15.
>
> **5. Amount.** Currently $18K. Apr 18 email confirms 50 seats at $360 = $18K. No change needed.
> **→ No action.**
>
> **6. Associated contacts.** Maria Chen (`maria.chen@acme.com`) attended the Apr 20 legal review meeting. Not on the deal, not in HubSpot.
> **→ Propose:** Create the contact and associate to the deal.
>
> **7. Notes hygiene.** No conflicting notes found in the last 90 days.
> **→ No action.**
>
> Approve items 1, 2, 4, 6 to write? Or specify which ones.
**Step 5 — Approval gate.**
Waits for the user. User replies: *"Yes to all except 4 — leave close date alone, she might be optimistic."*
**Step 6 — Execute approved items.**
- Logs the Apr 18 email as an email activity
- Logs the Apr 20 meeting as a call activity with attendees Sarah, Ben, and (newly created) Maria
- Updates `hs_next_step` to "wait for legal review sign-off"
- Does NOT update `closedate`
- Creates Maria Chen contact and associates to the deal
**Report.**
> Wrote changes to Acme Q2 Expansion:
> - Logged Apr 18 email and Apr 20 call activities
> - Updated next step to "wait for legal review sign-off"
> - Created contact Maria Chen (maria.chen@acme.com) and added to deal
> Close date unchanged per your call.
> Deal stage was flagged as consistent with reality — no change needed.
> [HubSpot: Acme Q2 Expansion]
## What this example demonstrates
- Every field check from `reference/cleanup-checklist.md` applied to one deal
- Side-by-side current → proposed framing for every proposed change
- Stage change correctly held as flag-only
- User selectively approves items — skill honors the exact selection
- Contact creation folded into the same approval cycle
reference/examples/log-call-happy-path.md
# Example: log a call with a missing contact
Worked scenario for the call path — calendar event has three attendees, one of them isn't in HubSpot yet.
## Setup
- User just finished a 30-minute Google Meet titled "Acme — technical deep dive" at 10:00am
- Attendees on the calendar invite: the user, `sarah.lin@acme.com`, `ben.rivera@acme.com`
- Sarah Lin is an existing HubSpot contact on the "Acme Q2 Expansion" deal
- Ben Rivera is NOT in HubSpot
- The Acme Q2 Expansion deal is the only open deal for Sarah
## User says
> "Log this call."
## Skill behavior
**Step 1 — Identify intent.**
Call path. No specific event named — falls back to the most recent completed meeting in the last 24 hours.
**Step 2 — Gather context.**
Finds the "Acme — technical deep dive" event from 10:00am. Confirms with the user before proceeding:
> *"Logging the 'Acme — technical deep dive' call from 10:00am this morning — correct?"*
User confirms.
Reads event details: 30 minutes, external attendees `sarah.lin@acme.com` and `ben.rivera@acme.com`. No description on the event.
**Step 3 — Resolve contact + deal.**
- Looks up `sarah.lin@acme.com` → exists (Sarah Lin, on Acme Q2 Expansion).
- Looks up `ben.rivera@acme.com` → does not exist.
- Announces contact creation:
> *"Ben Rivera (ben.rivera@acme.com) isn't in HubSpot. Creating the contact and associating to the Acme Q2 Expansion deal."*
Creates the contact with `email`, `firstname: Ben`, `lastname: Rivera`, `company: Acme` (inferred from email domain). Associates to the deal.
Deal resolution: Sarah's only open deal is Acme Q2 Expansion — unambiguous match.
**Step 4 — Execute.**
Writes a call activity on Acme Q2 Expansion:
- `hs_call_title`: Acme — technical deep dive
- `hs_call_body`: "Meeting held; notes to be added." (No description on the calendar event; placeholder surfaced to user so they know to add detail.)
- `hs_call_duration`: 30 min
- `hs_timestamp`: 10:00am today
- Associated contacts: Sarah Lin, Ben Rivera
- Associated deal: Acme Q2 Expansion
**Step 5 — Approval gate.**
Announced the contact creation before writing. Activity write is inline per the user's explicit "log this call" request.
**Step 6 — Report.**
> Logged call activity to Acme Q2 Expansion:
> *"Acme — technical deep dive" (30 min, 10:00am)*
> Created new contact: Ben Rivera (ben.rivera@acme.com) — associated to the deal.
> Call body is a placeholder — add your notes if you want them on the deal timeline.
> [HubSpot: Acme Q2 Expansion]
## What this example demonstrates
- Fallback to most-recent-meeting when the user doesn't specify an event
- Confirmation before acting on inferred context
- Contact creation with inferred company from email domain
- Placeholder body with user notification when no meeting notes exist
- Associating multiple contacts (existing + newly created) to the same activity
reference/examples/log-email-happy-path.md
# Example: log email to an existing deal (happy path)
A worked scenario for the email path — contact exists, deal resolves unambiguously, activity logs cleanly.
## Setup
- User is viewing an email thread with subject "Acme Q2 pricing follow-up"
- Thread has three messages between the user and `sarah.lin@acme.com` over the last two days
- `sarah.lin@acme.com` is an existing contact in HubSpot
- She is associated to exactly one open deal: "Acme Q2 Expansion" (proposal sent, $18K, close date May 15)
## User says
> "Add this thread to the deal."
## Skill behavior
**Step 1 — Identify intent.**
Email path (user said "this thread").
**Step 2 — Gather context.**
Reads the thread. Subject: "Acme Q2 pricing follow-up." Latest message (yesterday, 4:12pm) from Sarah: *"Confirmed, 50 seats at $360/seat works. Sending the signed SOW Friday."* Primary external contact: `sarah.lin@acme.com`.
**Step 3 — Resolve contact + deal.**
Looks up `sarah.lin@acme.com` → contact exists (Sarah Lin, Acme Corp). Finds open deals associated to her: exactly one, "Acme Q2 Expansion." Match is unambiguous.
**Step 4 — Execute.**
Writes an email activity on Acme Q2 Expansion:
- `hs_email_subject`: Acme Q2 pricing follow-up
- `hs_email_text`: "Sarah confirmed 50 seats at $360/seat ($18K). Signed SOW coming Friday."
- `hs_timestamp`: yesterday, 4:12pm
- Associated contact: Sarah Lin
- Associated deal: Acme Q2 Expansion
**Step 5 — Approval gate.**
Contact already existed, no creation announcement needed. Activity write happens inline — no separate gate because the user explicitly asked.
**Step 6 — Report.**
> Logged email activity to Acme Q2 Expansion:
> *"Sarah confirmed 50 seats at $360/seat ($18K). Signed SOW coming Friday."*
> Timestamped yesterday 4:12pm. No contact created — Sarah was already on the deal.
> [HubSpot: Acme Q2 Expansion]
## What makes this the happy path
- Single unambiguous deal association — no fuzzy match needed
- Contact already existed — no creation flow
- Thread subject is descriptive — usable as-is for activity title
- Summary focuses on decisions and numbers, not full thread transcript
reference/gotchas.md
# Gotchas
Edge cases where the `crm-maintenance` skill is most likely to produce bad CRM data. Each entry uses the Good / Bad pattern.
---
## Gotcha: Creating a duplicate contact when the email is already in HubSpot under a different case
**Why it matters:** HubSpot dedupes on exact email match. `Sarah.Lin@acme.com` and `sarah.lin@acme.com` are treated as distinct, but an owner who already created one will see a silent duplicate appear. That destroys the owner's trust in the skill.
### ✗ Bad
> Searched HubSpot for `Sarah.Lin@acme.com` — no match. Creating new contact: Sarah Lin (Sarah.Lin@acme.com).
New contact is created; owner's existing `sarah.lin@acme.com` contact now has a duplicate.
### ✓ Good
> Searched HubSpot for `sarah.lin@acme.com` (case-insensitive) — no match. Creating new contact: Sarah Lin (sarah.lin@acme.com). Announcing before write.
Always normalize email to lowercase before lookup. Announce the creation so the owner can catch misses.
---
## Gotcha: Writing the full email thread as the activity body
**Why it matters:** HubSpot activities are signal surfaces for fast scanning, not a transcript archive. A 12-message thread pasted into the activity body is useless; a three-sentence summary of what was agreed is actionable.
### ✗ Bad
> **Activity body:**
> *From: Sarah Lin — Thu Apr 9…*
> *Hi! Following up on our call, here are the specs we discussed…*
> *[1,200 more words]*
Owner scrolls past it every time.
### ✓ Good
> **Activity body:** Sarah confirmed scope for Q2 expansion — 50 seats, $18K ACV, start date June 1. She'll send the signed SOW by Friday. No open questions.
Summary names the decisions, the numbers, and the next step.
---
## Gotcha: Auto-creating a deal when the right one can't be resolved
**Why it matters:** Duplicate deals are very hard to untangle in HubSpot — they split activity history, confuse reporting, and the owner usually notices weeks later when a forecast looks wrong.
### ✗ Bad
> No open deal found for Sarah Lin at Acme. Creating new deal: "Acme — new opportunity" and logging the call to it.
Now there are two Acme deals and the owner has no idea which one is real.
### ✓ Good
> No open deal found for Sarah Lin at Acme. Which deal should I log this to? Options: (1) Acme Q2 Expansion (closed-won Feb), (2) Acme Renewal 2027 (open). Or do you want to skip logging for now?
Ask. Never create a deal on behalf of the owner.
---
## Gotcha: Proposing a deal stage change based on email language
**Why it matters:** "We're moving forward" in an email doesn't always mean "advance to closed-won." Customers say it when they're moving forward with evaluation, or with a procurement step. Owners have context the skill doesn't. Auto-advancing stage is destructive to the pipeline.
### ✗ Bad
> Email from Sarah says "we're moving forward." Proposing `dealstage` → Closed Won. Writing now.
### ✓ Good
> Flagging: the Apr 18 email from Sarah ("we're moving forward with the Anthropic evaluation") may indicate stage movement. Current stage is "Proposal Sent." Do you want to advance it? I won't change stage without your explicit go-ahead.
Surface the evidence, hold the write.
---
## Gotcha: Overwriting an owner-set next-step during cleanup
**Why it matters:** The owner may have set a next step that reflects context the skill can't see — a phone call, a Slack thread, a private note. Overwriting it because a recent email suggests something different erases real work.
### ✗ Bad
> `hs_next_step` currently says "wait for legal review." Latest email from Sarah says "let's schedule pricing review." Updating `hs_next_step` to "schedule pricing review."
### ✓ Good
> `hs_next_step` currently reads "wait for legal review" (set by you 3 days ago). The Apr 19 email from Sarah suggests a pricing review is also needed. Propose appending or replacing? Current → Proposed: "wait for legal review" → "wait for legal review; then schedule pricing review." Approve?
Show the current value, propose the change explicitly, wait for approval.
reference/hubspot-fields.md
# HubSpot fields and activity types
The specific HubSpot objects, properties, and activity types the `crm-maintenance` skill reads from and writes to. Only the fields listed here are in-scope for this skill — everything else in HubSpot is untouched.
## Contacts — write
| Field | Usage |
|---------------|----------------------------------------------------------------------------------------|
| `email` | Primary identifier for lookup + dedupe. Always set on creation. |
| `firstname` | Set from email signature or calendar invite if available. Leave blank if unknown. |
| `lastname` | Set from email signature or calendar invite if available. Leave blank if unknown. |
| `company` | Set from email signature domain or calendar organization if available. |
Do not write any other contact properties. Owner, lifecycle stage, and lead source are user-managed fields — never overwrite.
## Contacts — read (for lookup)
| Field | Usage |
|---------------|--------------------------------------------------------------------|
| `email` | Search key. Case-insensitive exact match. |
| `firstname` · `lastname` · `company` | Displayed to the user during ambiguity resolution. |
| `hs_object_id`| Used for association with deals and activities. |
## Deals — read (all cleanup + resolution paths)
| Field | Usage |
|-----------------------------|----------------------------------------------------------|
| `dealname` | Displayed to the user; used for fuzzy match against email/meeting topic |
| `dealstage` | Read-only during cleanup — flag discrepancies, never change |
| `amount` | Read during cleanup; flag if recent email/meeting implies change |
| `closedate` | Read during cleanup; flag if outdated |
| `hs_next_step` | Read + propose updates during cleanup |
| `hubspot_owner_id` | Displayed to the user; never changed |
| `hs_lastactivitydate` | Used to detect stale deals |
| Associated contacts | Used to determine whether recent email/meeting participants are on the deal |
## Deals — write (cleanup path only, with approval)
| Field | Rule |
|-----------------|------------------------------------------------------------------|
| `hs_next_step` | Propose updates; write only with explicit user approval |
| `closedate` | Propose updates; write only with explicit user approval |
| `amount` | Propose updates; write only with explicit user approval |
| Contact assoc. | Propose adding missing deal participants; write only with approval |
**Never write** `dealstage`, `pipeline`, `hubspot_owner_id`, or any custom property during cleanup. Those are owner-managed.
## Activities — write
| Activity type | Used by | Fields set |
|--------------------------------|----------------|--------------------------------------------------------------------------|
| Email engagement (`EMAIL`) | Email path | `hs_email_subject` (thread subject), `hs_email_text` (summary, not full thread), `hs_timestamp` (latest message time), associated contact(s) + deal |
| Call engagement (`CALL`) | Call path | `hs_call_title` (event title), `hs_call_body` (summary), `hs_call_duration` (from calendar), `hs_timestamp` (event start), associated contact(s) + deal |
| Note (`NOTE`) | Cleanup path | `hs_note_body` (when flagging something for future review that doesn't fit a field update) |
Use HubSpot's standard engagement vocabulary. Do not invent custom activity types.
## Association rules
- Every activity must associate to the deal AND to at least one contact.
- If a contact is created on-the-fly during activity logging, associate it to the deal in the same operation so the activity shows up on both the contact and deal timelines.
- Do not associate a contact to a deal during an activity-logging flow unless the contact is actually a participant in that email thread or meeting.