@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.
Files changed (77) hide show
  1. package/CHANGELOG.md +169 -0
  2. package/LICENSE +21 -0
  3. package/README.md +83 -0
  4. package/bin/bitrix-skills.js +3 -0
  5. package/dist/cli.js +1510 -0
  6. package/dist/features/bx-task/install.js +111 -0
  7. package/dist/features/task-sync/index.js +1053 -0
  8. package/package.json +69 -0
  9. package/src/features/bx/assets/SKILL.md +34 -0
  10. package/src/features/bx/feature.json +8 -0
  11. package/src/features/bx-calendar/assets/SKILL.md +61 -0
  12. package/src/features/bx-calendar/assets/availability.md +65 -0
  13. package/src/features/bx-calendar/assets/meeting.md +87 -0
  14. package/src/features/bx-calendar/assets/reminder.md +71 -0
  15. package/src/features/bx-calendar/assets/sync.md +70 -0
  16. package/src/features/bx-calendar/feature.json +10 -0
  17. package/src/features/bx-crm/assets/SKILL.md +59 -0
  18. package/src/features/bx-crm/assets/commerce.md +96 -0
  19. package/src/features/bx-crm/assets/onboard.md +127 -0
  20. package/src/features/bx-crm/assets/report.md +98 -0
  21. package/src/features/bx-crm/assets/research.md +71 -0
  22. package/src/features/bx-crm/feature.json +10 -0
  23. package/src/features/bx-task/assets/SKILL.md +148 -0
  24. package/src/features/bx-task/assets/lib/bx-api.sh +39 -0
  25. package/src/features/bx-task/assets/lib/bx-checklist.sh +127 -0
  26. package/src/features/bx-task/assets/lib/bx-resolve-task.sh +41 -0
  27. package/src/features/bx-task/assets/lib/bx-state.sh +131 -0
  28. package/src/features/bx-task/assets/lib/bx-tasks.sh +109 -0
  29. package/src/features/bx-task/assets/references/bootstrap.md +184 -0
  30. package/src/features/bx-task/assets/references/feature.md +97 -0
  31. package/src/features/bx-task/assets/references/init-templates/cli-tool.md +47 -0
  32. package/src/features/bx-task/assets/references/init-templates/generic.md +31 -0
  33. package/src/features/bx-task/assets/references/init-templates/library.md +45 -0
  34. package/src/features/bx-task/assets/references/init-templates/monorepo.md +38 -0
  35. package/src/features/bx-task/assets/references/init-templates/npm-package.md +40 -0
  36. package/src/features/bx-task/assets/references/init-templates/web-app.md +46 -0
  37. package/src/features/bx-task/assets/references/init.md +107 -0
  38. package/src/features/bx-task/assets/references/roadmap.md +93 -0
  39. package/src/features/bx-task/assets/references/summary.md +269 -0
  40. package/src/features/bx-task/assets/references/sync.md +104 -0
  41. package/src/features/bx-task/assets/references/time-log.md +214 -0
  42. package/src/features/bx-task/feature.json +10 -0
  43. package/src/features/bx-task/install.ts +117 -0
  44. package/src/features/task-sync/assets/docs/bitrix-task-reference.md +318 -0
  45. package/src/features/task-sync/assets/docs/bitrix-task-sync.md +254 -0
  46. package/src/features/task-sync/assets/githooks/commit-msg +44 -0
  47. package/src/features/task-sync/assets/githooks/install.sh +15 -0
  48. package/src/features/task-sync/assets/manifest.json +108 -0
  49. package/src/features/task-sync/assets/rules/00-bitrix-task-sync.md +161 -0
  50. package/src/features/task-sync/assets/scripts/bitrix-attach-files.sh +55 -0
  51. package/src/features/task-sync/assets/scripts/bitrix-lib.sh +540 -0
  52. package/src/features/task-sync/assets/scripts/bitrix-render-digest.sh +116 -0
  53. package/src/features/task-sync/assets/scripts/bitrix-session-check.sh +51 -0
  54. package/src/features/task-sync/assets/scripts/bitrix-session-sync.sh +89 -0
  55. package/src/features/task-sync/assets/scripts/bitrix-skill-end.sh +165 -0
  56. package/src/features/task-sync/assets/scripts/bitrix-skill-start.sh +58 -0
  57. package/src/features/task-sync/assets/scripts/lib/bb-formatter.sh +110 -0
  58. package/src/features/task-sync/assets/scripts/lib/bitrix-lib.sh +540 -0
  59. package/src/features/task-sync/assets/scripts/lib/time-helpers.sh +57 -0
  60. package/src/features/task-sync/assets/workflows/bitrix-sync.yml +85 -0
  61. package/src/features/task-sync/commands/install.ts +296 -0
  62. package/src/features/task-sync/commands/uninstall.ts +189 -0
  63. package/src/features/task-sync/commands/update.ts +11 -0
  64. package/src/features/task-sync/commands/verify.ts +141 -0
  65. package/src/features/task-sync/feature.json +12 -0
  66. package/src/features/task-sync/index.ts +121 -0
  67. package/src/features/task-sync/lib/dest-map.ts +96 -0
  68. package/src/features/task-sync/lib/drift-check.ts +47 -0
  69. package/src/features/task-sync/lib/file-ops.ts +36 -0
  70. package/src/features/task-sync/lib/manifest.ts +66 -0
  71. package/src/features/task-sync/lib/project-root.ts +38 -0
  72. package/src/features/task-sync/lib/settings-merge.ts +112 -0
  73. package/src/features/task-sync/lib/skill-refs.ts +106 -0
  74. package/src/features/task-sync/lib/task-id-finder.ts +31 -0
  75. package/src/features/task-sync/lib/token-extractor.ts +52 -0
  76. package/src/features/task-sync/lib/version.ts +36 -0
  77. 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
+ }