@synity/bitrix-skills 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +169 -0
- package/LICENSE +21 -0
- package/README.md +83 -0
- package/bin/bitrix-skills.js +3 -0
- package/dist/cli.js +1510 -0
- package/dist/features/bx-task/install.js +111 -0
- package/dist/features/task-sync/index.js +1053 -0
- package/package.json +69 -0
- package/src/features/bx/assets/SKILL.md +34 -0
- package/src/features/bx/feature.json +8 -0
- package/src/features/bx-calendar/assets/SKILL.md +61 -0
- package/src/features/bx-calendar/assets/availability.md +65 -0
- package/src/features/bx-calendar/assets/meeting.md +87 -0
- package/src/features/bx-calendar/assets/reminder.md +71 -0
- package/src/features/bx-calendar/assets/sync.md +70 -0
- package/src/features/bx-calendar/feature.json +10 -0
- package/src/features/bx-crm/assets/SKILL.md +59 -0
- package/src/features/bx-crm/assets/commerce.md +96 -0
- package/src/features/bx-crm/assets/onboard.md +127 -0
- package/src/features/bx-crm/assets/report.md +98 -0
- package/src/features/bx-crm/assets/research.md +71 -0
- package/src/features/bx-crm/feature.json +10 -0
- package/src/features/bx-task/assets/SKILL.md +148 -0
- package/src/features/bx-task/assets/lib/bx-api.sh +39 -0
- package/src/features/bx-task/assets/lib/bx-checklist.sh +127 -0
- package/src/features/bx-task/assets/lib/bx-resolve-task.sh +41 -0
- package/src/features/bx-task/assets/lib/bx-state.sh +131 -0
- package/src/features/bx-task/assets/lib/bx-tasks.sh +109 -0
- package/src/features/bx-task/assets/references/bootstrap.md +184 -0
- package/src/features/bx-task/assets/references/feature.md +97 -0
- package/src/features/bx-task/assets/references/init-templates/cli-tool.md +47 -0
- package/src/features/bx-task/assets/references/init-templates/generic.md +31 -0
- package/src/features/bx-task/assets/references/init-templates/library.md +45 -0
- package/src/features/bx-task/assets/references/init-templates/monorepo.md +38 -0
- package/src/features/bx-task/assets/references/init-templates/npm-package.md +40 -0
- package/src/features/bx-task/assets/references/init-templates/web-app.md +46 -0
- package/src/features/bx-task/assets/references/init.md +107 -0
- package/src/features/bx-task/assets/references/roadmap.md +93 -0
- package/src/features/bx-task/assets/references/summary.md +269 -0
- package/src/features/bx-task/assets/references/sync.md +104 -0
- package/src/features/bx-task/assets/references/time-log.md +214 -0
- package/src/features/bx-task/feature.json +10 -0
- package/src/features/bx-task/install.ts +117 -0
- package/src/features/task-sync/assets/docs/bitrix-task-reference.md +318 -0
- package/src/features/task-sync/assets/docs/bitrix-task-sync.md +254 -0
- package/src/features/task-sync/assets/githooks/commit-msg +44 -0
- package/src/features/task-sync/assets/githooks/install.sh +15 -0
- package/src/features/task-sync/assets/manifest.json +108 -0
- package/src/features/task-sync/assets/rules/00-bitrix-task-sync.md +161 -0
- package/src/features/task-sync/assets/scripts/bitrix-attach-files.sh +55 -0
- package/src/features/task-sync/assets/scripts/bitrix-lib.sh +540 -0
- package/src/features/task-sync/assets/scripts/bitrix-render-digest.sh +116 -0
- package/src/features/task-sync/assets/scripts/bitrix-session-check.sh +51 -0
- package/src/features/task-sync/assets/scripts/bitrix-session-sync.sh +89 -0
- package/src/features/task-sync/assets/scripts/bitrix-skill-end.sh +165 -0
- package/src/features/task-sync/assets/scripts/bitrix-skill-start.sh +58 -0
- package/src/features/task-sync/assets/scripts/lib/bb-formatter.sh +110 -0
- package/src/features/task-sync/assets/scripts/lib/bitrix-lib.sh +540 -0
- package/src/features/task-sync/assets/scripts/lib/time-helpers.sh +57 -0
- package/src/features/task-sync/assets/workflows/bitrix-sync.yml +85 -0
- package/src/features/task-sync/commands/install.ts +296 -0
- package/src/features/task-sync/commands/uninstall.ts +189 -0
- package/src/features/task-sync/commands/update.ts +11 -0
- package/src/features/task-sync/commands/verify.ts +141 -0
- package/src/features/task-sync/feature.json +12 -0
- package/src/features/task-sync/index.ts +121 -0
- package/src/features/task-sync/lib/dest-map.ts +96 -0
- package/src/features/task-sync/lib/drift-check.ts +47 -0
- package/src/features/task-sync/lib/file-ops.ts +36 -0
- package/src/features/task-sync/lib/manifest.ts +66 -0
- package/src/features/task-sync/lib/project-root.ts +38 -0
- package/src/features/task-sync/lib/settings-merge.ts +112 -0
- package/src/features/task-sync/lib/skill-refs.ts +106 -0
- package/src/features/task-sync/lib/task-id-finder.ts +31 -0
- package/src/features/task-sync/lib/token-extractor.ts +52 -0
- package/src/features/task-sync/lib/version.ts +36 -0
- package/src/features/task-sync/types.ts +40 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# bx:crm — Onboard (Write SOP)
|
|
2
|
+
|
|
3
|
+
Covers: contact, company (VN), deal, lead creation.
|
|
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) |
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## HONORIFIC
|
|
22
|
+
|
|
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 |
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Contact — Minimum Fields + Dedup
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
upsertContact({
|
|
37
|
+
name: "Nguyen Van A",
|
|
38
|
+
phone: "0977680550", // raw — helper normalizes
|
|
39
|
+
email: "a@example.com",
|
|
40
|
+
HONORIFIC: "Anh",
|
|
41
|
+
match: { phone } // dedup check before create
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Minimum: `name` + (`phone` OR `email`) + `HONORIFIC` (if determinable).
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Company — VN Rule (CRITICAL)
|
|
50
|
+
|
|
51
|
+
**NEVER `crm.company.add` directly for VN companies with MST.**
|
|
52
|
+
|
|
53
|
+
`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)
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
upsertCompanyByTaxCode({
|
|
60
|
+
taxCode: "0315427733", // MST — REQUIRED
|
|
61
|
+
title: "Pegasus Realty", // optional, VietQR fills if missing
|
|
62
|
+
phone: "...",
|
|
63
|
+
email: "...",
|
|
64
|
+
})
|
|
65
|
+
```
|
|
66
|
+
|
|
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
|
+
---
|
|
74
|
+
|
|
75
|
+
## Deal — Required Fields
|
|
76
|
+
|
|
77
|
+
Use `createDealWithParties` for atomic contact + company + deal in one call.
|
|
78
|
+
|
|
79
|
+
| 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` |
|
|
95
|
+
|
|
96
|
+
**COMMENTS — BANT template:**
|
|
97
|
+
```
|
|
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>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
> **Omit `idempotencyKey`** — causes D1 table error at runtime.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Verify Checklist (mandatory after every write op)
|
|
110
|
+
|
|
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
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Known Pitfalls
|
|
119
|
+
|
|
120
|
+
| Mistake | Symptom | Fix |
|
|
121
|
+
|---------|---------|-----|
|
|
122
|
+
| Used `bx-task` for CRM | Creates project task, not CRM record | Use `/bx:crm` |
|
|
123
|
+
| `crm.contact.add` directly | No dedup, phone not normalized | `upsertContact` |
|
|
124
|
+
| `crm.company.add` directly | Requisite missing RQ_VAT_ID + no address | `upsertCompanyByTaxCode` |
|
|
125
|
+
| Passed `idempotencyKey` | D1 table error at runtime | Omit the field |
|
|
126
|
+
| Pre-formatted phone `+84977...` | Helper double-normalizes (idempotent, fine) | Pass raw anyway |
|
|
127
|
+
| Skipped verify step | Bugs discovered sessions later | Always verify |
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# bx:crm — Report (Reporting Templates)
|
|
2
|
+
|
|
3
|
+
Covers: dealForecast, stuckInStage, overdueActivities, arReport, teamReport.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Helper Selection
|
|
8
|
+
|
|
9
|
+
| Report | Helper | Typical Frequency |
|
|
10
|
+
|--------|--------|-------------------|
|
|
11
|
+
| Pipeline forecast month/quarter | `dealForecast` | Weekly |
|
|
12
|
+
| Deals stuck too long in stage | `stuckInStage` | Weekly |
|
|
13
|
+
| Overdue activities | `overdueActivities` | Daily |
|
|
14
|
+
| AR aging + receivables | `arReport` | Monthly |
|
|
15
|
+
| Cross-rep performance | `teamReport` | Monthly |
|
|
16
|
+
| Per-entity payment history | `entityPayments` | On demand |
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Output Template — dealForecast
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
| Month | Predicted | Won | At Risk | # Deals |
|
|
24
|
+
|--------|-----------|-----|---------|---------|
|
|
25
|
+
| T5/26 | $X | $Y | $Z | N |
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Red flags: deals in same stage > 30 days with no activity → highlight with 🔴.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Output Template — stuckInStage
|
|
33
|
+
|
|
34
|
+
Group by pipeline stage → sort by days stuck DESC within each group.
|
|
35
|
+
|
|
36
|
+
**Top 5 per stage:**
|
|
37
|
+
```
|
|
38
|
+
Stage: [Stage Name]
|
|
39
|
+
1. [dealId] "[title]" — [owner] — [N] days — last activity: [date]
|
|
40
|
+
2. ...
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Output Template — overdueActivities
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
| Deal/Contact | Activity | Assigned | Due | Days Overdue |
|
|
49
|
+
|-------------|----------|----------|-----|--------------|
|
|
50
|
+
| ... | ... | ... | ... | N |
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Sort by days overdue DESC. Group by assignee if > 10 items.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Output Template — arReport (Aging Buckets)
|
|
58
|
+
|
|
59
|
+
```
|
|
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%
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
For 90d+ bucket: list each debtor individually below the table.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Interpretation Thresholds
|
|
74
|
+
|
|
75
|
+
| Metric | Yellow ⚠️ | Red 🔴 | Action |
|
|
76
|
+
|--------|----------|--------|--------|
|
|
77
|
+
| stuckInStage | > 14 days | > 30 days | Assign follow-up activity |
|
|
78
|
+
| AR 90d+ bucket | > 10% total AR | > 20% total AR | Escalate to account manager |
|
|
79
|
+
| teamReport response_time | > 12h | > 24h | Flag for coaching |
|
|
80
|
+
| overdueActivities per rep | > 5 | > 10 | Manager review |
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Common Report Calls
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
// Weekly pipeline review
|
|
88
|
+
dealForecast({ period: "month", includeAtRisk: true })
|
|
89
|
+
|
|
90
|
+
// Stuck deals
|
|
91
|
+
stuckInStage({ minDays: 14, pipelineId: null }) // null = all pipelines
|
|
92
|
+
|
|
93
|
+
// Daily overdue check
|
|
94
|
+
overdueActivities({ assignedTo: null }) // null = all reps
|
|
95
|
+
|
|
96
|
+
// Monthly AR
|
|
97
|
+
arReport({ groupByBucket: true, listDebtors90Plus: true })
|
|
98
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# bx:crm — Research (Analysis Protocol)
|
|
2
|
+
|
|
3
|
+
Covers: customer360, contactSignals, dealSignals, customerJourney, chatHistory.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Helper Selection
|
|
8
|
+
|
|
9
|
+
| Need | Helper | Returns |
|
|
10
|
+
|------|--------|---------|
|
|
11
|
+
| Full customer overview | `customer360` | profile + deals + comms + invoices |
|
|
12
|
+
| Quick risk signals | `contactSignals` | 8 sections raw context |
|
|
13
|
+
| Deal health check | `dealSignals` | pipeline + activities + AR |
|
|
14
|
+
| Historical narrative | `customerJourney` | stage transitions + communications |
|
|
15
|
+
| Chat/Zalo transcript | `chatHistory` | multi-channel messages |
|
|
16
|
+
| Next best action | `nextAction` | prioritized action recommendation |
|
|
17
|
+
| Behavior pattern | `behaviorWindow` | engagement pattern last N days |
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Output Protocol — customer360
|
|
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
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Output Protocol — contactSignals / dealSignals
|
|
34
|
+
|
|
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
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Output Protocol — customerJourney
|
|
43
|
+
|
|
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)
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## chatHistory Access Pattern
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
chatHistory({
|
|
55
|
+
entityType: "contact", // or "deal"
|
|
56
|
+
entityId: 123,
|
|
57
|
+
channels: ["chat", "zalo", "email"], // optional filter
|
|
58
|
+
limit: 50
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Present as: `[date] [channel]: <1-line summary>` per message block.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Pre-Research Checklist
|
|
67
|
+
|
|
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
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bx-crm",
|
|
3
|
+
"displayName": "Bitrix CRM Skill",
|
|
4
|
+
"version": "2.0.0",
|
|
5
|
+
"target": "global",
|
|
6
|
+
"description": "Claude Code skill for Bitrix24 CRM: contacts, companies, deals, leads, estimates, invoices, customer analysis, pipeline reports",
|
|
7
|
+
"requires": {
|
|
8
|
+
"mcp": ["bitrix-synity-mcp"]
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bx:task
|
|
3
|
+
description: "Synity Bitrix24 task ops — bootstrap project, manage features+roadmap, KB summaries, time-log, session sync, TASK_ID init. Subcommands: bootstrap, feature, roadmap, summary, time-log, sync, init. Self-contained, project-agnostic."
|
|
4
|
+
argument-hint: "<subcommand> [args...]"
|
|
5
|
+
version: "2.0.0"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# /bx:task — Bitrix24 Task Operations (Synity)
|
|
9
|
+
|
|
10
|
+
Global Claude Code skill for Bitrix task ops. v2.0 = 7 subcommands grouped into 2 tiers:
|
|
11
|
+
|
|
12
|
+
**Project lifecycle (state-aware, multi-feature):**
|
|
13
|
+
|
|
14
|
+
| Subcommand | Purpose | Reference |
|
|
15
|
+
|------------|---------|-----------|
|
|
16
|
+
| `bootstrap [--type T] [--dry-run] [--main-task-id N]` | Rich project init: main task + per-phase subtasks + Done/Doing/Plan checklists | `references/bootstrap.md` |
|
|
17
|
+
| `feature <name> [desc] [--no-branch]` | Create subtask + checklist item (Doing) + git branch | `references/feature.md` |
|
|
18
|
+
| `roadmap add\|move <args>` | Manage Done/Doing/Plan checklist groups on main task | `references/roadmap.md` |
|
|
19
|
+
|
|
20
|
+
**Per-task ops (single TASK_ID, project-agnostic):**
|
|
21
|
+
|
|
22
|
+
| Subcommand | Purpose | API |
|
|
23
|
+
|------------|---------|-----|
|
|
24
|
+
| `summary [type]` | Post curated KB entry as Bitrix Task Result (5 templates) | `tasks.task.result.add` (v3) |
|
|
25
|
+
| `time-log <duration> [comment]` | Manual time entry on task | `task.elapseditem.add` (v1) |
|
|
26
|
+
| `sync [--task-id N]` | Post session digest (git log + context) as task comment | `task.commentitem.add` (v1) |
|
|
27
|
+
| `init [--task-id N]` | Detect/insert `TASK_ID:` block in nearest CLAUDE.md | filesystem only |
|
|
28
|
+
|
|
29
|
+
**Triggers:** `/bx:task <subcommand>`, "bootstrap bitrix project", "create feature subtask", "add to roadmap", "post task summary", "log time to bitrix", "sync session to bitrix", "init task id".
|
|
30
|
+
|
|
31
|
+
## Pre-flight (mandatory)
|
|
32
|
+
|
|
33
|
+
Before any subcommand:
|
|
34
|
+
|
|
35
|
+
1. **`BITRIX_WEBHOOK_URL`** env var set (format: `https://<portal>.bitrix24.com/rest/<user>/<token>/`).
|
|
36
|
+
2. **`jq`** + **`curl`** + **`python3`** on PATH (python3 used for safe multiline-JSON payloads).
|
|
37
|
+
3. **TASK_ID** resolution priority (for per-task ops only):
|
|
38
|
+
`--task-id` arg > nearest `CLAUDE.md` with `^TASK_ID: <num>` > error.
|
|
39
|
+
4. **Project-state ops** (`bootstrap`, `feature`, `roadmap`) read state from
|
|
40
|
+
`.claude/bitrix-task-state.json` (project-local, gitignored).
|
|
41
|
+
|
|
42
|
+
Common preamble:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
source ~/.claude/skills/bx-task/lib/bx-api.sh
|
|
46
|
+
source ~/.claude/skills/bx-task/lib/bx-resolve-task.sh
|
|
47
|
+
bx_preflight || exit 1
|
|
48
|
+
# For project-state ops, also:
|
|
49
|
+
source ~/.claude/skills/bx-task/lib/bx-state.sh
|
|
50
|
+
source ~/.claude/skills/bx-task/lib/bx-tasks.sh
|
|
51
|
+
source ~/.claude/skills/bx-task/lib/bx-checklist.sh
|
|
52
|
+
bx_state_migrate # one-shot: strip deprecated 'webhook' field if present
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Routing
|
|
56
|
+
|
|
57
|
+
Parse first positional arg as `subcommand`:
|
|
58
|
+
|
|
59
|
+
| First arg | Action |
|
|
60
|
+
|-----------|--------|
|
|
61
|
+
| _(empty)_ or `help` | Print usage (all 7 subcommands) |
|
|
62
|
+
| `bootstrap` | Load `references/bootstrap.md` → follow its algorithm |
|
|
63
|
+
| `feature` | Load `references/feature.md` → follow its algorithm |
|
|
64
|
+
| `roadmap` | Load `references/roadmap.md` → follow its algorithm |
|
|
65
|
+
| `summary` | Load `references/summary.md` |
|
|
66
|
+
| `time-log` | Load `references/time-log.md` |
|
|
67
|
+
| `sync` | Load `references/sync.md` |
|
|
68
|
+
| `init` | Load `references/init.md` (TASK_ID block in CLAUDE.md — NOT project bootstrap) |
|
|
69
|
+
| anything else | Error: `Unknown subcommand. Use: bootstrap \| feature \| roadmap \| summary \| time-log \| sync \| init` |
|
|
70
|
+
|
|
71
|
+
The skill orchestrates via instructions to Claude — no shell dispatcher in `SKILL.md`. Claude reads the appropriate reference file and follows the algorithm written there.
|
|
72
|
+
|
|
73
|
+
**Naming note:** `init` (here) = lightweight TASK_ID block insertion into CLAUDE.md.
|
|
74
|
+
`bootstrap` = rich project bootstrap (main task + checklists + state). Do not confuse.
|
|
75
|
+
|
|
76
|
+
## State file (project-state subcommands)
|
|
77
|
+
|
|
78
|
+
Path: `.claude/bitrix-task-state.json` (project-local, gitignored)
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"claudeUserId": 614,
|
|
83
|
+
"responsibleId": 614,
|
|
84
|
+
"userMapping": { "github-username": <bitrix-uid> },
|
|
85
|
+
"mainTaskId": <number>,
|
|
86
|
+
"features": { "<name>": { "taskId": N, "status": "active|done|planned" } },
|
|
87
|
+
"checklists": {
|
|
88
|
+
"done": { "parentId": N },
|
|
89
|
+
"doing": { "parentId": N },
|
|
90
|
+
"plan": { "parentId": N }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Migration**: old skill stored `webhook` in this file. New skill REJECTS `webhook` field — use `BITRIX_WEBHOOK_URL` env. `bx_state_migrate()` strips the field on first run, with `.bak` backup.
|
|
96
|
+
|
|
97
|
+
## Hard constraints
|
|
98
|
+
|
|
99
|
+
- **BB code only** in text posted to Bitrix. See `.claude/rules/00-bitrix-task-sync.md` in Synity projects for BB↔markdown table.
|
|
100
|
+
- **Chat/comment messages**: NEVER use `[LIST]`, `[TABLE]`, `[IMG]` — crashes Bitrix24 mobile. Use `\n• ` bullets. (Task DESCRIPTION fields tolerate full BB-code including `[table]`.)
|
|
101
|
+
- **Self-contained**: NO dependency on project-level `.claude/scripts/bitrix-lib.sh`. Only `lib/bx-*.sh` shipped with this skill.
|
|
102
|
+
- **Confirm before POST**: ask user via `AskUserQuestion` with raw BB preview before any write call (KISS — exact text fidelity, no pseudo-render).
|
|
103
|
+
- **Fail-fast**: on network/5xx error → print error, exit 1. User re-runs manually. No automatic retry (avoids dup risk).
|
|
104
|
+
- **Hard rule #11**: any new Bitrix REST method MUST be verified via `bitrix-dev-mcp` before adding.
|
|
105
|
+
- **Prerequisite docs for `bootstrap`**: `docs/{project-overview-pdr,system-architecture,codebase-summary}.md` MUST exist. Missing → hard block + hint to run `/ck:docs init`. NO fallback to thin description.
|
|
106
|
+
|
|
107
|
+
## Errors (UX)
|
|
108
|
+
|
|
109
|
+
- TASK_ID missing (per-task ops) → hint: `Run inside a Bitrix project (CLAUDE.md with 'TASK_ID: N') or pass --task-id N.`
|
|
110
|
+
- Webhook env missing → `ERROR: BITRIX_WEBHOOK_URL not set. Export it (e.g. https://portal.bitrix24.com/rest/USER_ID/TOKEN/).`
|
|
111
|
+
- Missing docs for `bootstrap` → `Run first: /ck:docs init (parallel codebase scan → generates docs/)`
|
|
112
|
+
- State not bootstrapped (for feature/roadmap) → `Run /bx:task bootstrap first.`
|
|
113
|
+
- Unknown subcommand → see routing table above
|
|
114
|
+
|
|
115
|
+
## Out of scope (v3+)
|
|
116
|
+
|
|
117
|
+
- Auto-detect summary type from session context (git log, journal, transcript)
|
|
118
|
+
- Auto-update phase status across plan.md + Bitrix from filesystem state
|
|
119
|
+
- Cross-task batch summary
|
|
120
|
+
- Browser-based Bitrix portal embed
|
|
121
|
+
|
|
122
|
+
## Files
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
~/.claude/skills/bx-task/
|
|
126
|
+
├── SKILL.md — this file (router + intro)
|
|
127
|
+
├── references/
|
|
128
|
+
│ ├── bootstrap.md — /bx:task bootstrap spec
|
|
129
|
+
│ ├── feature.md — /bx:task feature spec
|
|
130
|
+
│ ├── roadmap.md — /bx:task roadmap spec
|
|
131
|
+
│ ├── summary.md — /bx:task summary spec
|
|
132
|
+
│ ├── time-log.md — /bx:task time-log spec
|
|
133
|
+
│ ├── sync.md — /bx:task sync spec
|
|
134
|
+
│ ├── init.md — /bx:task init spec (TASK_ID block, not bootstrap)
|
|
135
|
+
│ └── init-templates/ — per-project-type BB-code templates
|
|
136
|
+
│ ├── npm-package.md
|
|
137
|
+
│ ├── web-app.md
|
|
138
|
+
│ ├── library.md
|
|
139
|
+
│ ├── monorepo.md
|
|
140
|
+
│ ├── cli-tool.md
|
|
141
|
+
│ └── generic.md
|
|
142
|
+
└── lib/
|
|
143
|
+
├── bx-api.sh — bx_preflight, bx_call_v1, bx_call_v3
|
|
144
|
+
├── bx-resolve-task.sh — bx_resolve_task_id()
|
|
145
|
+
├── bx-state.sh — state file CRUD + migration + user mapping
|
|
146
|
+
├── bx-tasks.sh — task CRUD (create/update/complete/comment/notify/elapsed)
|
|
147
|
+
└── bx-checklist.sh — Done/Doing/Plan helpers (init/add/move/toggle)
|
|
148
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# bx-api.sh — Minimal Bitrix24 webhook caller for /bx:task skill.
|
|
3
|
+
#
|
|
4
|
+
# Provides:
|
|
5
|
+
# bx_preflight — verify env + tools (BITRIX_WEBHOOK_URL, curl, jq)
|
|
6
|
+
# bx_call_v1 method body — POST to /rest/{user}/{token}/{method}
|
|
7
|
+
# bx_call_v3 method body — POST to /rest/api/{user}/{token}/{method}
|
|
8
|
+
# (rewrites /rest/ → /rest/api/ for v3 family)
|
|
9
|
+
#
|
|
10
|
+
# Both wrappers: 10s timeout, JSON content-type, raw JSON to stdout.
|
|
11
|
+
|
|
12
|
+
bx_preflight() {
|
|
13
|
+
if [[ -z "${BITRIX_WEBHOOK_URL:-}" ]]; then
|
|
14
|
+
echo "ERROR: BITRIX_WEBHOOK_URL not set. Export it (e.g. https://portal.bitrix24.com/rest/USER_ID/TOKEN/)." >&2
|
|
15
|
+
return 1
|
|
16
|
+
fi
|
|
17
|
+
command -v curl >/dev/null 2>&1 || { echo "ERROR: curl required on PATH" >&2; return 1; }
|
|
18
|
+
command -v jq >/dev/null 2>&1 || { echo "ERROR: jq required on PATH" >&2; return 1; }
|
|
19
|
+
return 0
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
bx_call_v1() {
|
|
23
|
+
local method="$1" body="$2"
|
|
24
|
+
curl -sS -m 10 -X POST "${BITRIX_WEBHOOK_URL%/}/${method}" \
|
|
25
|
+
-H "Content-Type: application/json" \
|
|
26
|
+
-d "$body"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
bx_call_v3() {
|
|
30
|
+
local method="$1" body="$2"
|
|
31
|
+
local url="${BITRIX_WEBHOOK_URL%/}"
|
|
32
|
+
# Rewrite /rest/ → /rest/api/ via split (portable across bash/zsh; \/ escape behaves differently between shells)
|
|
33
|
+
local prefix="${url%%/rest/*}"
|
|
34
|
+
local suffix="${url#*/rest/}"
|
|
35
|
+
url="${prefix}/rest/api/${suffix}"
|
|
36
|
+
curl -sS -m 10 -X POST "${url}/${method}" \
|
|
37
|
+
-H "Content-Type: application/json" \
|
|
38
|
+
-d "$body"
|
|
39
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# bx-checklist.sh — High-level helpers for the Done/Doing/Plan checklist groups.
|
|
3
|
+
#
|
|
4
|
+
# Depends on:
|
|
5
|
+
# bx-state.sh — state read/write for checklist parentIds
|
|
6
|
+
# bx-tasks.sh — _bx_post for raw POST
|
|
7
|
+
# jq, curl, BITRIX_WEBHOOK_URL
|
|
8
|
+
#
|
|
9
|
+
# Public functions:
|
|
10
|
+
# bx_checklist_init <mainTaskId> — create 3 root-level groups Done/Doing/Plan,
|
|
11
|
+
# return parentIds via state file
|
|
12
|
+
# bx_checklist_add <mainTaskId> <group> <title>
|
|
13
|
+
# — group ∈ {done, doing, plan}; print new item ID
|
|
14
|
+
# bx_checklist_toggle <mainTaskId> <itemId> <Y|N>
|
|
15
|
+
# — mark complete/incomplete
|
|
16
|
+
# bx_checklist_find_item <mainTaskId> <group> <title>
|
|
17
|
+
# — print first matching item ID or empty
|
|
18
|
+
# bx_checklist_move <mainTaskId> <title> <from> <to>
|
|
19
|
+
# — close in <from>, add (checked if to=done) in <to>
|
|
20
|
+
#
|
|
21
|
+
# Root group creation uses the "BX_CHECKLIST_1 rename" trick (see SKILL.md docs):
|
|
22
|
+
# Bitrix24 auto-creates BX_CHECKLIST_1 wrapper on first item add. We rename it
|
|
23
|
+
# to "Done", then add Doing/Plan with explicit PARENT_ID: 0.
|
|
24
|
+
|
|
25
|
+
# Add a checklist node (item OR group depending on parent_id presence)
|
|
26
|
+
_checklist_add_raw() {
|
|
27
|
+
local task_id="$1"; local title="$2"; local parent_id="${3:-}"
|
|
28
|
+
local body
|
|
29
|
+
if [[ -n "$parent_id" ]]; then
|
|
30
|
+
body=$(jq -n --arg id "$task_id" --arg t "$title" --arg p "$parent_id" \
|
|
31
|
+
'{TASKID: ($id|tonumber), FIELDS: {TITLE: $t, PARENT_ID: ($p|tonumber)}}')
|
|
32
|
+
else
|
|
33
|
+
body=$(jq -n --arg id "$task_id" --arg t "$title" \
|
|
34
|
+
'{TASKID: ($id|tonumber), FIELDS: {TITLE: $t}}')
|
|
35
|
+
fi
|
|
36
|
+
local resp; resp=$(_bx_post "task.checklistitem.add" "$body")
|
|
37
|
+
echo "$resp" | jq -r '.result'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_checklist_update_title() {
|
|
41
|
+
local task_id="$1"; local item_id="$2"; local title="$3"
|
|
42
|
+
local body; body=$(jq -n --arg id "$task_id" --arg i "$item_id" --arg t "$title" \
|
|
43
|
+
'{TASKID: ($id|tonumber), ITEMID: ($i|tonumber), FIELDS: {TITLE: $t}}')
|
|
44
|
+
_bx_post "task.checklistitem.update" "$body" > /dev/null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_checklist_delete() {
|
|
48
|
+
local task_id="$1"; local item_id="$2"
|
|
49
|
+
local body; body=$(jq -n --arg id "$task_id" --arg i "$item_id" \
|
|
50
|
+
'{TASKID: ($id|tonumber), ITEMID: ($i|tonumber)}')
|
|
51
|
+
_bx_post "task.checklistitem.delete" "$body" > /dev/null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
_checklist_list() {
|
|
55
|
+
local task_id="$1"
|
|
56
|
+
local body; body=$(jq -n --arg id "$task_id" '{TASKID: ($id|tonumber)}')
|
|
57
|
+
_bx_post "task.checklistitem.getlist" "$body"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
bx_checklist_init() {
|
|
61
|
+
local main_id="$1"
|
|
62
|
+
|
|
63
|
+
# Step 1: add placeholder → triggers auto BX_CHECKLIST_1 wrapper
|
|
64
|
+
local placeholder_id; placeholder_id=$(_checklist_add_raw "$main_id" "placeholder")
|
|
65
|
+
|
|
66
|
+
# Find the auto-created root (PARENT_ID == 0)
|
|
67
|
+
local list; list=$(_checklist_list "$main_id")
|
|
68
|
+
local done_root; done_root=$(echo "$list" | jq -r '.result[]? | select(.PARENT_ID == 0) | .ID' | head -1)
|
|
69
|
+
|
|
70
|
+
# Delete placeholder, rename root → "Done"
|
|
71
|
+
_checklist_delete "$main_id" "$placeholder_id"
|
|
72
|
+
_checklist_update_title "$main_id" "$done_root" "Done"
|
|
73
|
+
|
|
74
|
+
# Step 2: add Doing + Plan at root (PARENT_ID: 0 explicit)
|
|
75
|
+
local doing_id; doing_id=$(_checklist_add_raw "$main_id" "Doing" "0")
|
|
76
|
+
local plan_id; plan_id=$(_checklist_add_raw "$main_id" "Plan" "0")
|
|
77
|
+
|
|
78
|
+
# Persist parentIds in state
|
|
79
|
+
bx_state_write "checklists.done.parentId" "$done_root"
|
|
80
|
+
bx_state_write "checklists.doing.parentId" "$doing_id"
|
|
81
|
+
bx_state_write "checklists.plan.parentId" "$plan_id"
|
|
82
|
+
|
|
83
|
+
echo "done=$done_root doing=$doing_id plan=$plan_id"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
bx_checklist_add() {
|
|
87
|
+
local main_id="$1"; local group="$2"; local title="$3"
|
|
88
|
+
local parent_id; parent_id=$(bx_state_get "checklists.${group}.parentId")
|
|
89
|
+
if [[ -z "$parent_id" ]]; then
|
|
90
|
+
echo "ERROR: checklist group '$group' not initialized. Run bx_checklist_init first." >&2
|
|
91
|
+
return 1
|
|
92
|
+
fi
|
|
93
|
+
_checklist_add_raw "$main_id" "$title" "$parent_id"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
bx_checklist_toggle() {
|
|
97
|
+
local main_id="$1"; local item_id="$2"; local complete="$3"
|
|
98
|
+
local body; body=$(jq -n --arg id "$main_id" --arg i "$item_id" --arg c "$complete" \
|
|
99
|
+
'{TASKID: ($id|tonumber), ITEMID: ($i|tonumber), FIELDS: {IS_COMPLETE: $c}}')
|
|
100
|
+
_bx_post "task.checklistitem.update" "$body" > /dev/null
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
bx_checklist_find_item() {
|
|
104
|
+
local main_id="$1"; local group="$2"; local title="$3"
|
|
105
|
+
local parent_id; parent_id=$(bx_state_get "checklists.${group}.parentId")
|
|
106
|
+
[[ -z "$parent_id" ]] && return 0
|
|
107
|
+
local list; list=$(_checklist_list "$main_id")
|
|
108
|
+
echo "$list" | jq -r --arg p "$parent_id" --arg t "$title" \
|
|
109
|
+
'.result[]? | select((.PARENT_ID == ($p|tonumber)) and (.TITLE == $t)) | .ID' | head -1
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
bx_checklist_move() {
|
|
113
|
+
local main_id="$1"; local title="$2"; local from="$3"; local to="$4"
|
|
114
|
+
|
|
115
|
+
# Mark item in <from> as complete (preserves history visibility)
|
|
116
|
+
local from_id; from_id=$(bx_checklist_find_item "$main_id" "$from" "$title")
|
|
117
|
+
if [[ -n "$from_id" ]]; then
|
|
118
|
+
bx_checklist_toggle "$main_id" "$from_id" "Y"
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
# Add to <to>; if destination is "done", also toggle complete
|
|
122
|
+
local new_id; new_id=$(bx_checklist_add "$main_id" "$to" "$title")
|
|
123
|
+
if [[ "$to" == "done" && -n "$new_id" ]]; then
|
|
124
|
+
bx_checklist_toggle "$main_id" "$new_id" "Y"
|
|
125
|
+
fi
|
|
126
|
+
echo "$new_id"
|
|
127
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# bx-resolve-task.sh — TASK_ID resolution helper for /bx:task skill.
|
|
3
|
+
#
|
|
4
|
+
# Resolution priority:
|
|
5
|
+
# 1. Explicit arg (e.g. --task-id 9999)
|
|
6
|
+
# 2. CLAUDE.md walk-up from $PWD (first '^TASK_ID: <num>' wins)
|
|
7
|
+
# 3. Error + exit 1 with friendly hint
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# source ~/.claude/skills/bx-task/lib/bx-resolve-task.sh
|
|
11
|
+
# TASK_ID=$(bx_resolve_task_id "${EXPLICIT_TASK_ID:-}") || exit 1
|
|
12
|
+
|
|
13
|
+
bx_resolve_task_id() {
|
|
14
|
+
local explicit="${1:-}"
|
|
15
|
+
if [[ -n "$explicit" ]]; then
|
|
16
|
+
# Validate explicit --task-id is a positive integer. Without this guard,
|
|
17
|
+
# bx_resolve_task_id "abc" or "2776; rm -rf /" would return the raw string
|
|
18
|
+
# and surface only via cryptic 'jq: invalid JSON text passed to --argjson'.
|
|
19
|
+
if [[ ! "$explicit" =~ ^[0-9]+$ ]]; then
|
|
20
|
+
echo "ERROR: --task-id must be a positive integer (got: '$explicit')." >&2
|
|
21
|
+
return 1
|
|
22
|
+
fi
|
|
23
|
+
echo "$explicit"
|
|
24
|
+
return 0
|
|
25
|
+
fi
|
|
26
|
+
local dir="$PWD"
|
|
27
|
+
while [[ "$dir" != "/" && -n "$dir" ]]; do
|
|
28
|
+
if [[ -f "$dir/CLAUDE.md" ]]; then
|
|
29
|
+
local id
|
|
30
|
+
id=$(grep -m1 -E '^TASK_ID:[[:space:]]+[0-9]+' "$dir/CLAUDE.md" \
|
|
31
|
+
| awk '{print $2}')
|
|
32
|
+
if [[ -n "$id" ]]; then
|
|
33
|
+
echo "$id"
|
|
34
|
+
return 0
|
|
35
|
+
fi
|
|
36
|
+
fi
|
|
37
|
+
dir=$(dirname "$dir")
|
|
38
|
+
done
|
|
39
|
+
echo "ERROR: TASK_ID not found. Run inside a Bitrix project (CLAUDE.md with 'TASK_ID: N') or pass --task-id N." >&2
|
|
40
|
+
return 1
|
|
41
|
+
}
|