@synity/bitrix-skills 1.3.1 → 1.3.2

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.
@@ -1,32 +1,12 @@
1
1
  # bx:crm — Onboard (Write SOP)
2
2
 
3
- Covers: contact, company (VN), deal, lead creation.
3
+ Covers: contact, company (VN), deal, lead creation, and safe updates.
4
4
 
5
- ---
6
-
7
- ## Phone Normalization (CRITICAL)
8
-
9
- `upsertContact` auto-normalizes — **pass raw phone, never pre-format**.
10
-
11
- | Input | Stored as |
12
- |-------|-----------|
13
- | `0977680550` | `+84977680550` |
14
- | `0977 68 0550` | `+84977680550` |
15
- | `84977680550` | `+84977680550` |
16
- | `+84977680550` | `+84977680550` (idempotent) |
17
- | `+6591234567` | `+6591234567` (non-VN: keep) |
5
+ ## Phone + VN Normalization
18
6
 
19
- ---
20
-
21
- ## HONORIFIC
7
+ See [vn-norms.md](./vn-norms.md) for phone `+84`, HONORIFIC, address, and MST rules.
22
8
 
23
- | Input | Value |
24
- |-------|-------|
25
- | Anh / Mr | `Anh` |
26
- | Chị / Ms / Mrs | `Chị` |
27
- | Ông (senior male) | `Ông` |
28
- | Bà (senior female) | `Bà` |
29
- | Unknown | omit field |
9
+ `upsertContact` auto-normalizes phone numbers. Pass raw phone, never pre-format.
30
10
 
31
11
  ---
32
12
 
@@ -35,93 +15,131 @@ Covers: contact, company (VN), deal, lead creation.
35
15
  ```js
36
16
  upsertContact({
37
17
  name: "Nguyen Van A",
38
- phone: "0977680550", // raw — helper normalizes
18
+ phone: "0977680550",
39
19
  email: "a@example.com",
40
20
  HONORIFIC: "Anh",
41
- match: { phone } // dedup check before create
21
+ match: { phone }
42
22
  })
43
23
  ```
44
24
 
45
- Minimum: `name` + (`phone` OR `email`) + `HONORIFIC` (if determinable).
46
-
47
- ---
25
+ Minimum: `name` + (`phone` OR `email`) + `HONORIFIC` if determinable. See [vn-norms.md](./vn-norms.md) for the HONORIFIC table.
48
26
 
49
27
  ## Company — VN Rule (CRITICAL)
50
28
 
51
- **NEVER `crm.company.add` directly for VN companies with MST.**
29
+ **Never call `crm.company.add` directly for VN companies with MST.**
52
30
 
53
31
  `upsertCompanyByTaxCode` auto-does:
54
- 1. VietQR lookup → legal name + address from GDT tax registry
55
- 2. Sets `RQ_INN` = `RQ_VAT_ID` = taxCode on requisite
56
- 3. Creates address entity (needed for `{CompanyAddressLegal}` in document templates)
32
+ 1. VietQR lookup → legal name + address from GDT tax registry.
33
+ 2. Sets `RQ_INN` = `RQ_VAT_ID` = taxCode on requisite.
34
+ 3. Creates address entity for document templates.
57
35
 
58
36
  ```js
59
37
  upsertCompanyByTaxCode({
60
- taxCode: "0315427733", // MST — REQUIRED
61
- title: "Pegasus Realty", // optional, VietQR fills if missing
38
+ taxCode: "0315427733",
39
+ title: "Pegasus Realty",
62
40
  phone: "...",
63
- email: "...",
41
+ email: "..."
64
42
  })
65
43
  ```
66
44
 
67
- **Verify checklist for company:**
68
- - `RQ_INN` populated
69
- - `RQ_VAT_ID` populated (same value)
70
- - `RQ_COMPANY_FULL_NAME` populated (legal name from registry)
71
- - Address entity exists (ENTITY_TYPE_ID=8)
72
-
73
- ---
45
+ **Verify company:**
46
+ - `RQ_INN` and `RQ_VAT_ID` populated.
47
+ - `RQ_COMPANY_FULL_NAME` populated from registry.
48
+ - Address entity exists (`ENTITY_TYPE_ID=8`).
74
49
 
75
50
  ## Deal — Required Fields
76
51
 
77
- Use `createDealWithParties` for atomic contact + company + deal in one call.
52
+ Use `createDealWithParties` for atomic contact + company + deal.
78
53
 
79
54
  | Field | How to get | Example |
80
- |-------|-----------|---------|
81
- | `STAGE_ID` | `codemode.entityIds()` → dealStageSemanticId | **never hardcode** |
82
- | `CURRENCY_ID` | from user input | `USD`, `VND` |
83
- | `OPPORTUNITY` | from user input (number) | `588` |
84
- | `SOURCE_ID` | see mapping below | `OTHER` |
85
-
86
- **SOURCE_ID mapping:**
87
-
88
- | Context | Value |
89
- |---------|-------|
90
- | Event / offline meeting | `OTHER` |
91
- | Website / form | `WEB` |
92
- | Referral / partner | `PARTNER` |
93
- | Cold call / outreach | `CALL` |
94
- | Social / ad | `ADVERTISING` |
55
+ |---|---|---|
56
+ | `STAGE_ID` | `codemode.entityIds()` | never hardcode |
57
+ | `CURRENCY_ID` | user input | `USD`, `VND` |
58
+ | `OPPORTUNITY` | user input number | `588` |
59
+ | `SOURCE_ID` | mapping below | `OTHER` |
60
+
61
+ **SOURCE_ID mapping:** Event/offline=`OTHER`, website/form=`WEB`, partner=`PARTNER`, call=`CALL`, social/ad=`ADVERTISING`.
95
62
 
96
63
  **COMMENTS — BANT template:**
64
+ ```text
65
+ Budget: <amount/range/unknown>
66
+ Authority: <decision maker>
67
+ Need: <problem or goal>
68
+ Timeline: <start date>
69
+ Notes: <context>
97
70
  ```
98
- Budget: <amount or range, or "unknown">
99
- Authority: <who makes the decision>
100
- Need: <specific problem / goal>
101
- Timeline: <when they want to start>
102
- Notes: <additional context>
71
+
72
+ ## Lead Create + Products
73
+
74
+ Use lead when the prospect is unqualified or missing deal-level budget/timeline.
75
+
76
+ ```js
77
+ createLeadWithParties({
78
+ title: "Nguyen Van A - CRM training",
79
+ name: "Nguyen Van A",
80
+ phone: "0977680550",
81
+ email: "a@example.com",
82
+ companyTitle: "ABC Training",
83
+ sourceId: "WEB",
84
+ comments: "Need: CRM training\nTimeline: Q2"
85
+ })
86
+
87
+ setLeadProducts({
88
+ leadId,
89
+ products: [{ name: "Workshop", price: 5000000, quantity: 1, currency: "VND" }]
90
+ })
103
91
  ```
104
92
 
105
- > **Omit `idempotencyKey`** causes D1 table error at runtime.
93
+ For qualification, convert via [convert.md](./convert.md).
94
+
95
+ ## Update Existing Entity
96
+
97
+ 1. Search before update: `codemode.search({ keywords, entities: ["contact","company","deal","lead"], intent: "read" })`.
98
+ 2. Read current entity by ID and validate target ownership.
99
+ 3. Discover allowed fields with `crm.{entity}.fields` when field names are uncertain.
100
+ 4. Show before/after field diff and confirm every update, even for a single match.
101
+ 5. Send only changed fields.
102
+
103
+ ```js
104
+ // MCP gap fallback when no typed update helper exists.
105
+ codemode.request({
106
+ method: "POST",
107
+ path: "/crm.deal.update/",
108
+ body: { id: dealId, fields: { OPPORTUNITY: 9000000, ASSIGNED_BY_ID: 12 } }
109
+ })
110
+ ```
111
+
112
+ Skip this search rule for `upsertContact` and `upsertCompanyByTaxCode`; they dedup internally.
113
+
114
+ ## Custom Fields & Routing
115
+
116
+ - `UF_CRM_*`: discover with `codemode.entityIds()` or `crm.{entity}.fields`.
117
+ - `categoryId`: route deal to the correct pipeline.
118
+ - `assignedById`: responsible user. Resolve user IDs before writing.
119
+ - Do not echo full custom-field PII unless user explicitly asks.
120
+
121
+ ## Idempotency
122
+
123
+ Omit `idempotencyKey`. **Verified 2026-05-15:** D1 table `idempotency_keys` missing; passing the key throws `SQLITE_ERROR` before write. Re-verify when MCP version changes. Escalation: MCP team.
106
124
 
107
125
  ---
108
126
 
109
127
  ## Verify Checklist (mandatory after every write op)
110
128
 
111
- - [ ] Contact: `PHONE[0].VALUE` starts with `+84` for VN numbers
112
- - [ ] Company: `RQ_VAT_ID` populated, address entity exists
113
- - [ ] Deal: `STAGE_ID` set, `OPPORTUNITY > 0`, payer linked
114
- - [ ] Lead: `SOURCE_ID` set, phone/email present
129
+ - [ ] Contact: VN phone stored with `+84`; HONORIFIC matches [vn-norms.md](./vn-norms.md).
130
+ - [ ] Company: `RQ_VAT_ID` populated, address entity exists, MST format valid.
131
+ - [ ] Deal: `STAGE_ID` set, `OPPORTUNITY > 0`, payer linked.
132
+ - [ ] Lead: `SOURCE_ID` set, phone/email present, products set when user requested products.
133
+ - [ ] Update: only intended fields changed.
115
134
 
116
135
  ---
117
136
 
118
137
  ## Known Pitfalls
119
138
 
120
139
  | Mistake | Symptom | Fix |
121
- |---------|---------|-----|
122
- | Used `bx-task` for CRM | Creates project task, not CRM record | Use `/bx:crm` |
140
+ |---|---|---|
141
+ | Used `bx:task` for CRM | Creates project task, not CRM record | Use `/bx:crm` |
123
142
  | `crm.contact.add` directly | No dedup, phone not normalized | `upsertContact` |
124
- | `crm.company.add` directly | Requisite missing RQ_VAT_ID + no address | `upsertCompanyByTaxCode` |
143
+ | `crm.company.add` directly | Requisite missing tax fields + address | `upsertCompanyByTaxCode` |
125
144
  | Passed `idempotencyKey` | D1 table error at runtime | Omit the field |
126
- | Pre-formatted phone `+84977...` | Helper double-normalizes (idempotent, fine) | Pass raw anyway |
127
145
  | Skipped verify step | Bugs discovered sessions later | Always verify |
@@ -4,10 +4,16 @@ Covers: dealForecast, stuckInStage, overdueActivities, arReport, teamReport.
4
4
 
5
5
  ---
6
6
 
7
+ ## Timezone
8
+
9
+ MCP may return UTC ISO timestamps. Convert display times to Asia/Saigon (UTC+7). Date-only summaries use the Asia/Saigon date.
10
+
11
+ ---
12
+
7
13
  ## Helper Selection
8
14
 
9
15
  | Report | Helper | Typical Frequency |
10
- |--------|--------|-------------------|
16
+ |---|---|---|
11
17
  | Pipeline forecast month/quarter | `dealForecast` | Weekly |
12
18
  | Deals stuck too long in stage | `stuckInStage` | Weekly |
13
19
  | Overdue activities | `overdueActivities` | Daily |
@@ -19,61 +25,71 @@ Covers: dealForecast, stuckInStage, overdueActivities, arReport, teamReport.
19
25
 
20
26
  ## Output Template — dealForecast
21
27
 
22
- ```
23
- | Month | Predicted | Won | At Risk | # Deals |
24
- |--------|-----------|-----|---------|---------|
25
- | T5/26 | $X | $Y | $Z | N |
28
+ ```text
29
+ | Month | Predicted | Won | At Risk | # Deals |
30
+ |---|---:|---:|---:|---:|
31
+ | T5/26 | $X | $Y | $Z | N |
26
32
  ```
27
33
 
28
- Red flags: deals in same stage > 30 days with no activity → highlight with 🔴.
34
+ Red flags: deals in same stage > 30 days with no activity.
29
35
 
30
36
  ---
31
37
 
32
38
  ## Output Template — stuckInStage
33
39
 
34
- Group by pipeline stage sort by days stuck DESC within each group.
40
+ Group by pipeline stage. Sort by days stuck DESC within each group.
35
41
 
36
- **Top 5 per stage:**
37
- ```
42
+ ```text
38
43
  Stage: [Stage Name]
39
- 1. [dealId] "[title]" — [owner] — [N] days — last activity: [date]
40
- 2. ...
44
+ 1. [dealId] "[title]" — [owner] — [N] days — last activity: [date]
41
45
  ```
42
46
 
43
47
  ---
44
48
 
45
49
  ## Output Template — overdueActivities
46
50
 
47
- ```
51
+ ```text
48
52
  | Deal/Contact | Activity | Assigned | Due | Days Overdue |
49
- |-------------|----------|----------|-----|--------------|
50
- | ... | ... | ... | ... | N |
53
+ |---|---|---|---|---:|
54
+ | ... | ... | ... | ... | N |
51
55
  ```
52
56
 
53
- Sort by days overdue DESC. Group by assignee if > 10 items.
57
+ Sort by days overdue DESC. Group by assignee if more than 10 items.
54
58
 
55
59
  ---
56
60
 
57
- ## Output Template — arReport (Aging Buckets)
61
+ ## Output Template — arReport
58
62
 
63
+ ```text
64
+ | Bucket | Amount | % of Total | # Invoices |
65
+ |---|---:|---:|---:|
66
+ | Current | $X | X% | N |
67
+ | 1-30d | $X | X% | N |
68
+ | 31-60d | $X | X% | N |
69
+ | 61-90d | $X | X% | N |
70
+ | 90d+ | $X | X% | N |
59
71
  ```
60
- | Bucket | Amount | % of Total | # Invoices |
61
- |----------|--------|------------|------------|
62
- | Current | $X | X% | N |
63
- | 1–30d | $X | X% | N |
64
- | 31–60d | $X | X% | N |
65
- | 61–90d | $X | X% | N |
66
- | 90d+ | $X | X% | N | ← highlight if > 20%
72
+
73
+ For 90d+ bucket: list each debtor below the table.
74
+
75
+ ---
76
+
77
+ ## Output Template teamReport
78
+
79
+ ```text
80
+ | Rep | # Deals | Total Value | Avg Response | Overdue |
81
+ |---|---:|---:|---:|---:|
82
+ | ... | N | $X | Nh | N |
67
83
  ```
68
84
 
69
- For 90d+ bucket: list each debtor individually below the table.
85
+ Sort by Total Value DESC. Flag response time and overdue counts using the thresholds below.
70
86
 
71
87
  ---
72
88
 
73
89
  ## Interpretation Thresholds
74
90
 
75
- | Metric | Yellow ⚠️ | Red 🔴 | Action |
76
- |--------|----------|--------|--------|
91
+ | Metric | Yellow | Red | Action |
92
+ |---|---|---|---|
77
93
  | stuckInStage | > 14 days | > 30 days | Assign follow-up activity |
78
94
  | AR 90d+ bucket | > 10% total AR | > 20% total AR | Escalate to account manager |
79
95
  | teamReport response_time | > 12h | > 24h | Flag for coaching |
@@ -84,15 +100,30 @@ For 90d+ bucket: list each debtor individually below the table.
84
100
  ## Common Report Calls
85
101
 
86
102
  ```js
87
- // Weekly pipeline review
88
103
  dealForecast({ period: "month", includeAtRisk: true })
104
+ stuckInStage({ minDays: 14, pipelineId: null })
105
+ overdueActivities({ assignedTo: null })
106
+ arReport({ groupByBucket: true, listDebtors90Plus: true })
107
+ teamReport({ period: "month", includeResponseTime: true })
108
+ ```
109
+
110
+ ---
89
111
 
90
- // Stuck deals
91
- stuckInStage({ minDays: 14, pipelineId: null }) // null = all pipelines
112
+ ## Example Dialog — dealForecast
92
113
 
93
- // Daily overdue check
94
- overdueActivities({ assignedTo: null }) // null = all reps
114
+ User: "review pipeline tuần này"
95
115
 
96
- // Monthly AR
97
- arReport({ groupByBucket: true, listDebtors90Plus: true })
116
+ LLM behavior: call `dealForecast({ period: "month", includeAtRisk: true })`, convert dates to Asia/Saigon, then summarize risks.
117
+
118
+ Sample output:
119
+ ```text
120
+ Pipeline T5/26: predicted 1.2B VND, won 420M, at risk 180M across 4 deals.
121
+ Red flag: 3 deals stuck >30 days with no recent activity.
122
+ Next action: assign follow-up activities today for all red-flag deals.
98
123
  ```
124
+
125
+ ## Verify Checklist
126
+
127
+ - [ ] Date/time displayed in Asia/Saigon.
128
+ - [ ] Totals reconcile with helper output.
129
+ - [ ] Red/yellow flags use the threshold table above.
@@ -1,13 +1,13 @@
1
1
  # bx:crm — Research (Analysis Protocol)
2
2
 
3
- Covers: customer360, contactSignals, dealSignals, customerJourney, chatHistory.
3
+ Covers: customer360, contactSignals, dealSignals, customerJourney, chatHistory, nextAction, behaviorWindow.
4
4
 
5
5
  ---
6
6
 
7
7
  ## Helper Selection
8
8
 
9
9
  | Need | Helper | Returns |
10
- |------|--------|---------|
10
+ |---|---|---|
11
11
  | Full customer overview | `customer360` | profile + deals + comms + invoices |
12
12
  | Quick risk signals | `contactSignals` | 8 sections raw context |
13
13
  | Deal health check | `dealSignals` | pipeline + activities + AR |
@@ -20,31 +20,47 @@ Covers: customer360, contactSignals, dealSignals, customerJourney, chatHistory.
20
20
 
21
21
  ## Output Protocol — customer360
22
22
 
23
- Always present in this order:
24
-
25
- 1. **Profile** — name, company, lifecycle stage, assigned rep
26
- 2. **Deals** — active deals (stage, value, days in stage)
27
- 3. **Last contact** — channel + date + 1-line content summary
28
- 4. **Pending actions** — overdue activities, unpaid invoices
29
- 5. **Recommendation** — next action in 1 concrete sentence
23
+ 1. **Profile** name, company, lifecycle stage, assigned rep.
24
+ 2. **Deals** — active deals (stage, value, days in stage).
25
+ 3. **Last contact** — channel + date + 1-line summary.
26
+ 4. **Pending actions** — overdue activities, unpaid invoices.
27
+ 5. **Recommendation** — next action in 1 concrete sentence.
30
28
 
31
29
  ---
32
30
 
33
31
  ## Output Protocol — contactSignals / dealSignals
34
32
 
35
- 1. **Risk level** — `HIGH` / `MEDIUM` / `LOW` + 1-line reason
36
- 2. **Top 3 signals** — bulleted, most critical first
37
- 3. **Next action** — specific, with suggested deadline
38
- 4. **Reference** — contactId / dealId for traceability
33
+ 1. **Risk level** — `HIGH` / `MEDIUM` / `LOW` + 1-line reason.
34
+ 2. **Top 3 signals** — most critical first.
35
+ 3. **Next action** — specific, with suggested deadline.
36
+ 4. **Reference** — contactId / dealId for traceability.
39
37
 
40
38
  ---
41
39
 
42
40
  ## Output Protocol — customerJourney
43
41
 
44
- 1. **Timeline** — chronological: stage changes + key communications
45
- 2. **Turning points** — when did deal get stuck or advance?
46
- 3. **Sentiment trend** — Positive / Neutral / Negative
47
- 4. **Gap analysis** — longest silence period (in days)
42
+ 1. **Timeline** — stage changes + key communications.
43
+ 2. **Turning points** — when deal got stuck or advanced.
44
+ 3. **Sentiment trend** — Positive / Neutral / Negative.
45
+ 4. **Gap analysis** — longest silence period in days.
46
+
47
+ ---
48
+
49
+ ## Output Protocol — nextAction
50
+
51
+ 1. **Action** — one imperative sentence.
52
+ 2. **Reasoning** — 1-2 bullets explaining why now.
53
+ 3. **Deadline** — exact date/time when possible.
54
+ 4. **Reference** — source contactId/dealId.
55
+
56
+ ---
57
+
58
+ ## Output Protocol — behaviorWindow
59
+
60
+ 1. **Window** — date range and interaction count.
61
+ 2. **Trend** — Increasing / Stable / Declining.
62
+ 3. **Last 3 events** — date + channel + 1-line summary.
63
+ 4. **Recommendation** — next action tied to the trend.
48
64
 
49
65
  ---
50
66
 
@@ -52,20 +68,42 @@ Always present in this order:
52
68
 
53
69
  ```js
54
70
  chatHistory({
55
- entityType: "contact", // or "deal"
71
+ entityType: "contact",
56
72
  entityId: 123,
57
- channels: ["chat", "zalo", "email"], // optional filter
73
+ channels: ["chat", "zalo", "email"],
58
74
  limit: 50
59
75
  })
60
76
  ```
61
77
 
62
- Present as: `[date] [channel]: <1-line summary>` per message block.
78
+ Present as: `[date] [channel]: <1-line summary>`.
79
+
80
+ ---
81
+
82
+ ## Example Dialog — customer360
83
+
84
+ User: "phân tích contact #123 trước cuộc họp 2pm"
85
+
86
+ LLM behavior: call `customer360({ contactId: 123 })`, then follow the customer360 output protocol.
87
+
88
+ Sample output:
89
+ ```text
90
+ Profile: Nguyen Van A, ABC Training, assigned to Linh.
91
+ Deals: 2 active; main deal 80M VND stuck 18 days.
92
+ Last contact: Zalo 2026-05-14, asked for contract terms.
93
+ Pending actions: proposal follow-up overdue 1 day.
94
+ Recommendation: Call before 14:00 and confirm decision timeline.
95
+ ```
63
96
 
64
97
  ---
65
98
 
66
99
  ## Pre-Research Checklist
67
100
 
68
- Before calling any intelligence helper:
69
- - [ ] Have contactId or dealId ready
70
- - [ ] Know the goal (overview / risk check / history / next action)
71
- - [ ] Use `codemode.search({ intent: "read" })` if unsure which helper fits
101
+ - [ ] Have contactId or dealId ready.
102
+ - [ ] Know the goal: overview, risk check, history, next action, or behavior.
103
+ - [ ] Use `codemode.search({ intent: "read" })` if unsure which helper fits.
104
+
105
+ ## Verify Checklist
106
+
107
+ - [ ] Output cites source contactId/dealId.
108
+ - [ ] Recommendations are based on returned signals, not invented context.
109
+ - [ ] PII is summarized only as needed for the user's stated purpose.
@@ -0,0 +1,50 @@
1
+ # bx:crm — VN Normalization Reference
2
+
3
+ Vietnam-specific normalization for phone, honorific, address, and MST.
4
+
5
+ ---
6
+
7
+ ## Phone Normalization
8
+
9
+ `upsertContact` auto-normalizes. Pass raw user input.
10
+
11
+ | Input | Stored as |
12
+ |---|---|
13
+ | `0977680550` | `+84977680550` |
14
+ | `0977 68 0550` | `+84977680550` |
15
+ | `84977680550` | `+84977680550` |
16
+ | `+84977680550` | `+84977680550` |
17
+ | `+6591234567` | `+6591234567` |
18
+
19
+ Rules: strip spaces/dashes; VN leading `0` becomes `+84`; existing country code stays; non-VN numbers stay unchanged.
20
+
21
+ ---
22
+
23
+ ## HONORIFIC
24
+
25
+ | Input | Value |
26
+ |---|---|
27
+ | Anh / Mr | `Anh` |
28
+ | Chị / Ms / Mrs | `Chị` |
29
+ | Em | `Em` |
30
+ | Cô | `Cô` |
31
+ | Chú | `Chú` |
32
+ | Bác | `Bác` |
33
+ | Ông (senior male) | `Ông` |
34
+ | Bà (senior female) | `Bà` |
35
+ | Unknown | omit field |
36
+
37
+ ---
38
+
39
+ ## Address + MST
40
+
41
+ VN legal address should preserve: street, ward, district, province/city, country. Do not invent missing parts. Prefer GDT/VietQR registry data from `upsertCompanyByTaxCode`.
42
+
43
+ - Company MST: usually 10 digits.
44
+ - Branch MST: 13 digits, often `10-digit-parent-3-digit-branch`.
45
+ - Store in Bitrix requisites as `RQ_INN` and `RQ_VAT_ID`.
46
+
47
+ ## Verify Checklist
48
+
49
+ - [ ] Raw phone was passed to helper, not preformatted.
50
+ - [ ] Missing address/MST parts were not invented.