@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,214 @@
|
|
|
1
|
+
# /bx:task time-log — Manual Time Entry
|
|
2
|
+
|
|
3
|
+
Log time manually to a Bitrix task via `task.elapseditem.add`. Use when the auto time-tracker (`bitrix-task-sync` hook) didn't catch the work — offline session, different machine, forgot to start, or filling in past sessions.
|
|
4
|
+
|
|
5
|
+
Bitrix auto-emits a system message *"<user> activated time tracking. Duration: N seconds"* on every successful add — your time entry is visible in the task chat and time tab.
|
|
6
|
+
|
|
7
|
+
## Args
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
/bx:task time-log <duration> [comment] [--task-id N]
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
| Arg | Required | Notes |
|
|
14
|
+
|-----|----------|-------|
|
|
15
|
+
| `<duration>` | yes | One of 4 formats. See "Duration formats" below. |
|
|
16
|
+
| `[comment]` | optional | Free text. Default: `"Manual log via /bx:task time-log"`. |
|
|
17
|
+
| `--task-id N` | optional | Override CLAUDE.md walk-up. |
|
|
18
|
+
|
|
19
|
+
## Duration formats
|
|
20
|
+
|
|
21
|
+
| Pattern | Example | Meaning | Seconds |
|
|
22
|
+
|---------|---------|---------|--------:|
|
|
23
|
+
| `^\d+$` | `30` | minutes | 1800 |
|
|
24
|
+
| `^\d+h\d+m?$` | `1h30m` / `1h30` | hours + minutes | 5400 |
|
|
25
|
+
| `^\d+h$` | `2h` | whole hours | 7200 |
|
|
26
|
+
| `^\d+\.\d+h$` | `1.5h` | decimal hours (truncated to int seconds) | 5400 |
|
|
27
|
+
| `^\d+:\d+(:\d+)?$` | `0:30:00` / `1:00:00` | HH:MM or HH:MM:SS | 1800 / 3600 |
|
|
28
|
+
|
|
29
|
+
Anything else → error listing all 4 formats + exit 1.
|
|
30
|
+
|
|
31
|
+
## Algorithm (Claude follows step by step)
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
1. Source helpers + pre-flight:
|
|
35
|
+
source ~/.claude/skills/bx-task/lib/bx-resolve-task.sh
|
|
36
|
+
source ~/.claude/skills/bx-task/lib/bx-api.sh
|
|
37
|
+
bx_preflight || abort
|
|
38
|
+
|
|
39
|
+
2. Parse args:
|
|
40
|
+
- extract --task-id N if present, store in EXPLICIT_TASK_ID
|
|
41
|
+
- first positional = duration (REQUIRED, abort if empty)
|
|
42
|
+
- second positional = comment (optional)
|
|
43
|
+
|
|
44
|
+
3. Resolve TASK_ID:
|
|
45
|
+
TASK_ID=$(bx_resolve_task_id "$EXPLICIT_TASK_ID") || abort
|
|
46
|
+
|
|
47
|
+
4. Parse duration:
|
|
48
|
+
SECONDS_VAL=$(bx_parse_duration "$duration") || abort
|
|
49
|
+
# bx_parse_duration body — inline in this skill:
|
|
50
|
+
|
|
51
|
+
bx_parse_duration() {
|
|
52
|
+
# IMPORTANT: every arithmetic expansion uses 10# prefix to force decimal
|
|
53
|
+
# base. Without it, leading-zero minutes ('08', '1h08m', '0:09:30') trigger
|
|
54
|
+
# bash octal interpretation → 'value too great for base' error.
|
|
55
|
+
local input="$1"
|
|
56
|
+
if [[ "$input" =~ ^([0-9]+)$ ]]; then
|
|
57
|
+
echo $(( 10#${BASH_REMATCH[1]} * 60 ))
|
|
58
|
+
elif [[ "$input" =~ ^([0-9]+)h([0-9]+)m?$ ]]; then
|
|
59
|
+
echo $(( 10#${BASH_REMATCH[1]} * 3600 + 10#${BASH_REMATCH[2]} * 60 ))
|
|
60
|
+
elif [[ "$input" =~ ^([0-9]+)h$ ]]; then
|
|
61
|
+
echo $(( 10#${BASH_REMATCH[1]} * 3600 ))
|
|
62
|
+
elif [[ "$input" =~ ^([0-9]+)\.([0-9]+)h$ ]]; then
|
|
63
|
+
awk -v h="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}" 'BEGIN{printf "%d", h*3600}'
|
|
64
|
+
elif [[ "$input" =~ ^([0-9]+):([0-9]+)(:([0-9]+))?$ ]]; then
|
|
65
|
+
local h="${BASH_REMATCH[1]}" m="${BASH_REMATCH[2]}" s="${BASH_REMATCH[4]:-0}"
|
|
66
|
+
echo $(( 10#$h * 3600 + 10#$m * 60 + 10#$s ))
|
|
67
|
+
else
|
|
68
|
+
echo "ERROR: invalid duration. Use: '30' (min), '1h30m', '1.5h', '0:30:00'." >&2
|
|
69
|
+
return 1
|
|
70
|
+
fi
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
5. If SECONDS_VAL <= 0 → abort with error "duration must be > 0".
|
|
74
|
+
|
|
75
|
+
6. COMMENT="${comment:-Manual log via /bx:task time-log}"
|
|
76
|
+
|
|
77
|
+
7. POST:
|
|
78
|
+
PAYLOAD=$(jq -n \
|
|
79
|
+
--argjson tid "$TASK_ID" \
|
|
80
|
+
--argjson sec "$SECONDS_VAL" \
|
|
81
|
+
--arg c "$COMMENT" \
|
|
82
|
+
'{TASKID:$tid, ARFIELDS:{SECONDS:$sec, COMMENT_TEXT:$c}}')
|
|
83
|
+
RESP=$(bx_call_v1 "task.elapseditem.add" "$PAYLOAD")
|
|
84
|
+
|
|
85
|
+
8. Extract entry id (response is `{"result": NNN}` — usually clean JSON, but
|
|
86
|
+
we still use grep for parity with summary.md):
|
|
87
|
+
ENTRY_ID=$(echo "$RESP" | grep -oE '"result":[[:space:]]*[0-9]+' | grep -oE '[0-9]+')
|
|
88
|
+
|
|
89
|
+
9. If ENTRY_ID non-empty:
|
|
90
|
+
echo "⏱️ Logged $(bx_format_duration $SECONDS_VAL) to task #${TASK_ID}"
|
|
91
|
+
Else:
|
|
92
|
+
echo "❌ POST failed: $RESP" >&2
|
|
93
|
+
exit 1
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Output formatter
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
bx_format_duration() {
|
|
100
|
+
local s="$1"
|
|
101
|
+
if (( s < 60 )); then echo "${s}s"
|
|
102
|
+
elif (( s < 3600 )); then echo "$((s/60))m"
|
|
103
|
+
else
|
|
104
|
+
local h=$((s/3600)) m=$(( (s%3600)/60 ))
|
|
105
|
+
if (( m == 0 )); then echo "${h}h"
|
|
106
|
+
else echo "${h}h ${m}m"
|
|
107
|
+
fi
|
|
108
|
+
fi
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Examples:
|
|
113
|
+
- 30 sec → `30s`
|
|
114
|
+
- 1800 sec → `30m`
|
|
115
|
+
- 3600 sec → `1h`
|
|
116
|
+
- 5400 sec → `1h 30m`
|
|
117
|
+
- 7200 sec → `2h`
|
|
118
|
+
|
|
119
|
+
## API payload shape
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"TASKID": 2776,
|
|
124
|
+
"ARFIELDS": {
|
|
125
|
+
"SECONDS": 1800,
|
|
126
|
+
"COMMENT_TEXT": "Manual log via /bx:task time-log"
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Endpoint: `task.elapseditem.add` on the v1 path (`/rest/<user>/<token>/`). `bx_call_v1` does NOT rewrite the URL.
|
|
132
|
+
|
|
133
|
+
Response (success):
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{ "result": 12345, "time": {...} }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`12345` = the new elapsed-item ID.
|
|
140
|
+
|
|
141
|
+
## Error handling
|
|
142
|
+
|
|
143
|
+
| Failure | Behaviour |
|
|
144
|
+
|---------|-----------|
|
|
145
|
+
| `bx_preflight` fails | Exit 1, print env hint |
|
|
146
|
+
| `bx_resolve_task_id` fails | Exit 1, print "TASK_ID not found" hint |
|
|
147
|
+
| Duration arg missing | Exit 1, print usage |
|
|
148
|
+
| Duration parse fails | Exit 1, list 4 valid formats |
|
|
149
|
+
| Duration ≤ 0 | Exit 1, "duration must be > 0" |
|
|
150
|
+
| Network/HTTP error | Print raw response to stderr, exit 1 (NO retry — fail-fast) |
|
|
151
|
+
|
|
152
|
+
## Test matrix (parser + formatter)
|
|
153
|
+
|
|
154
|
+
| Input | Seconds | Output |
|
|
155
|
+
|-------|--------:|--------|
|
|
156
|
+
| `30` | 1800 | `⏱️ Logged 30m to task #N` |
|
|
157
|
+
| `1` | 60 | `⏱️ Logged 1m to task #N` |
|
|
158
|
+
| `1h30m` | 5400 | `⏱️ Logged 1h 30m to task #N` |
|
|
159
|
+
| `1h30` | 5400 | `⏱️ Logged 1h 30m to task #N` |
|
|
160
|
+
| `1.5h` | 5400 | `⏱️ Logged 1h 30m to task #N` |
|
|
161
|
+
| `2h` | 7200 | `⏱️ Logged 2h to task #N` |
|
|
162
|
+
| `0:30:00` | 1800 | `⏱️ Logged 30m to task #N` |
|
|
163
|
+
| `1:00:00` | 3600 | `⏱️ Logged 1h to task #N` |
|
|
164
|
+
| `0:00:30` | 30 | `⏱️ Logged 30s to task #N` |
|
|
165
|
+
| `08` | 480 | `⏱️ Logged 8m to task #N` (leading-zero — `10#` prefix prevents octal trap) |
|
|
166
|
+
| `1h08m` | 4080 | `⏱️ Logged 1h 8m to task #N` (leading-zero minutes) |
|
|
167
|
+
| `0:09:30` | 570 | `⏱️ Logged 9m to task #N` (leading-zero in HH:MM:SS) |
|
|
168
|
+
| `abc` | — | error + exit 1 |
|
|
169
|
+
| `30s` | — | error + exit 1 (raw seconds not supported v1) |
|
|
170
|
+
| `-30` | — | error + exit 1 (regex rejects sign) |
|
|
171
|
+
|
|
172
|
+
## Examples
|
|
173
|
+
|
|
174
|
+
### Quick log
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
/bx:task time-log 30
|
|
178
|
+
```
|
|
179
|
+
→ Logs 1800s with default comment `"Manual log via /bx:task time-log"`.
|
|
180
|
+
|
|
181
|
+
### With comment
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
/bx:task time-log 1h30m "Code review for crm-deal-sync"
|
|
185
|
+
```
|
|
186
|
+
→ Logs 5400s with custom comment.
|
|
187
|
+
|
|
188
|
+
### Decimal hours
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
/bx:task time-log 1.5h "Architecture brainstorm"
|
|
192
|
+
```
|
|
193
|
+
→ Logs 5400s.
|
|
194
|
+
|
|
195
|
+
### HH:MM:SS
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
/bx:task time-log 0:30:00
|
|
199
|
+
```
|
|
200
|
+
→ Logs 1800s.
|
|
201
|
+
|
|
202
|
+
### Override task
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
/bx:task time-log 30 --task-id 9999
|
|
206
|
+
```
|
|
207
|
+
→ Logs 30 minutes against task 9999.
|
|
208
|
+
|
|
209
|
+
## Notes
|
|
210
|
+
|
|
211
|
+
- Bitrix auto-emits *"<user> activated time tracking. Duration: N seconds"* on success — that's why even short entries surface in the task chat.
|
|
212
|
+
- Time entries are removable via `task.elapseditem.delete {TASKID, ITEMID}` if you need to undo. Skill does NOT auto-delete (audit value).
|
|
213
|
+
- Decimal hour math truncates to int seconds via `awk` — `1.5h = 5400s exact`, `1.7h ≈ 6120s` (not `6120.0`).
|
|
214
|
+
- If the task has time-tracking disabled, the API returns an error — surface it via the fail-fast path.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { resolve, join, dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { mkdir, copyFile, readdir, stat, rm, access } from 'node:fs/promises';
|
|
5
|
+
import { assertNotSymlink, assertContainedIn } from '../../lib/fs-safety.js';
|
|
6
|
+
|
|
7
|
+
export interface InstallOpts {
|
|
8
|
+
projectRoot: string;
|
|
9
|
+
onConflict?: 'overwrite' | 'skip';
|
|
10
|
+
force?: boolean;
|
|
11
|
+
/** @internal Override destination base for testing only — bypasses path-containment guard. Not wired to any CLI flag. */
|
|
12
|
+
_destOverride?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface InstallResult {
|
|
16
|
+
installedFiles: string[];
|
|
17
|
+
skippedFiles: string[];
|
|
18
|
+
warnings: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Resolve assets directory. Code is inlined into dist/cli.js (__dirname = dist/) AND
|
|
22
|
+
// compiled to dist/features/bx-task/install.js (__dirname = dist/features/bx-task/).
|
|
23
|
+
// assets/ lives in src/features/bx-task/assets/ (included in package.json "files").
|
|
24
|
+
async function getAssetsDir(): Promise<string> {
|
|
25
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const candidates = [
|
|
27
|
+
// dist/ context (inlined in cli.js): ../src/features/bx-task/assets
|
|
28
|
+
resolve(here, '../src/features/bx-task/assets'),
|
|
29
|
+
// dist/features/bx-task/ context: ../../../src/features/bx-task/assets
|
|
30
|
+
resolve(here, '../../../src/features/bx-task/assets'),
|
|
31
|
+
// src/features/bx-task/ (dev)
|
|
32
|
+
resolve(here, 'assets'),
|
|
33
|
+
];
|
|
34
|
+
for (const c of candidates) {
|
|
35
|
+
try {
|
|
36
|
+
await access(c);
|
|
37
|
+
return c;
|
|
38
|
+
} catch { /* continue */ }
|
|
39
|
+
}
|
|
40
|
+
return candidates[0]!;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getSkillBase(): string {
|
|
44
|
+
return resolve(homedir(), '.claude', 'skills');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function install(opts: InstallOpts): Promise<InstallResult> {
|
|
48
|
+
const skillBase = getSkillBase();
|
|
49
|
+
const dest = opts._destOverride ?? resolve(skillBase, 'bx-task');
|
|
50
|
+
// Path traversal guard: dest must be absolute and not escape via ../
|
|
51
|
+
const resolvedDest = resolve(dest);
|
|
52
|
+
if (!opts._destOverride && !resolvedDest.startsWith(skillBase)) {
|
|
53
|
+
throw new Error(`Path traversal detected: ${resolvedDest}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const result: InstallResult = {
|
|
57
|
+
installedFiles: [],
|
|
58
|
+
skippedFiles: [],
|
|
59
|
+
warnings: [],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
await installDir(await getAssetsDir(), resolvedDest, result, opts);
|
|
63
|
+
|
|
64
|
+
result.warnings.push(
|
|
65
|
+
'[bx-task] Requires MCP server "bitrix-synity-mcp" — verify it is configured in Claude Code settings.',
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function installDir(
|
|
72
|
+
srcDir: string,
|
|
73
|
+
destDir: string,
|
|
74
|
+
result: InstallResult,
|
|
75
|
+
opts: InstallOpts,
|
|
76
|
+
): Promise<void> {
|
|
77
|
+
await mkdir(destDir, { recursive: true });
|
|
78
|
+
const entries = await readdir(srcDir, { withFileTypes: true });
|
|
79
|
+
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
const srcPath = join(srcDir, entry.name);
|
|
82
|
+
const destPath = join(destDir, entry.name);
|
|
83
|
+
|
|
84
|
+
if (entry.isDirectory()) {
|
|
85
|
+
await installDir(srcPath, destPath, result, opts);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
await assertNotSymlink(destPath);
|
|
90
|
+
const exists = await fileExists(destPath);
|
|
91
|
+
if (exists && !opts.force && opts.onConflict === 'skip') {
|
|
92
|
+
result.skippedFiles.push(destPath);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
await copyFile(srcPath, destPath);
|
|
97
|
+
result.installedFiles.push(destPath);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function fileExists(filePath: string): Promise<boolean> {
|
|
102
|
+
try {
|
|
103
|
+
await stat(filePath);
|
|
104
|
+
return true;
|
|
105
|
+
} catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function uninstall(opts?: { _destOverride?: string }): Promise<void> {
|
|
111
|
+
const dest = resolve(opts?._destOverride ?? resolve(homedir(), '.claude', 'skills', 'bx-task'));
|
|
112
|
+
// Guard only applies to production paths; _destOverride is an internal test escape hatch.
|
|
113
|
+
if (!opts?._destOverride) {
|
|
114
|
+
assertContainedIn(dest, getSkillBase(), dest);
|
|
115
|
+
}
|
|
116
|
+
await rm(dest, { recursive: true, force: true });
|
|
117
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# Bitrix Task — Agent Reference
|
|
2
|
+
|
|
3
|
+
> Reference cho agent khi query task hoặc lập plan structure trong Bitrix24.
|
|
4
|
+
> Setup hooks: [bitrix-task-sync.md](bitrix-task-sync.md)
|
|
5
|
+
> Quy trình bắt buộc: [.claude/rules/00-bitrix-task-sync.md](../.claude/rules/00-bitrix-task-sync.md)
|
|
6
|
+
|
|
7
|
+
> **⚠️ Hard Rule #13:** Mọi query field/structure portal PHẢI qua `bitrix-synity-mcp` (KHÔNG curl/fetch trong agent runtime). Code derived từ MCP cần annotation: `// MCP-DERIVED: <source> @ <ISO-timestamp>`.
|
|
8
|
+
|
|
9
|
+
Các tra cứu trong file này dành cho agent runtime (đọc/query qua `bitrix-synity-mcp`, sync checklist qua `bitrix-lib.sh`). Không dùng cho dev setup — nội dung setup ở file `bitrix-task-sync.md`.
|
|
10
|
+
|
|
11
|
+
## 1. Bitrix Object Hierarchy
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Portal (tamgiac.bitrix24.com)
|
|
15
|
+
└── Project (groupId) ← "App" trong ngữ cảnh Synity
|
|
16
|
+
├── Epic (epicId) ← nhóm tính năng (Scrum only)
|
|
17
|
+
│ ├── Task (id)
|
|
18
|
+
│ │ ├── Subtask (parentId → Task.id)
|
|
19
|
+
│ │ └── Checklist items (checklist[])
|
|
20
|
+
│ └── Task ...
|
|
21
|
+
└── Task (không thuộc Epic)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Workgroup = "App" trong Synity
|
|
25
|
+
|
|
26
|
+
Mỗi Bitrix24 app của Synity tương ứng với một **Project** trên portal:
|
|
27
|
+
|
|
28
|
+
| Bitrix Field | Giá trị ví dụ | Ý nghĩa |
|
|
29
|
+
|-------------|--------------|---------|
|
|
30
|
+
| `groupId` | `12` | ID Project chứa task này |
|
|
31
|
+
|
|
32
|
+
Agent đọc `groupId` → biết task thuộc app nào → không nhầm task giữa các dự án.
|
|
33
|
+
|
|
34
|
+
### Mapping: App → Workgroup
|
|
35
|
+
|
|
36
|
+
Agent xác định Project tương ứng với app theo 1 trong 2 flow:
|
|
37
|
+
|
|
38
|
+
#### Greenfield — app mới, chưa có Project trên Bitrix
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
1. Agent hỏi user: "App này đã có Project trên portal chưa?"
|
|
42
|
+
2. User: chưa
|
|
43
|
+
3. Agent hỏi: "Tên Project là gì?" (gợi ý theo tên app, vd: "SynFinance")
|
|
44
|
+
4. Agent → bitrix-synity-mcp: socintranet.workgroup.add
|
|
45
|
+
→ input: NAME, DESCRIPTION (optional), VISIBLE=Y
|
|
46
|
+
→ output: workgroup.id
|
|
47
|
+
5. (Optional) Tạo Epic per nhóm tính năng — xem [Section 2](#2-epic--nhóm-tính-năng)
|
|
48
|
+
6. Agent ghi vào CLAUDE.md root:
|
|
49
|
+
## Bitrix Project
|
|
50
|
+
GROUP_ID: <workgroup.id>
|
|
51
|
+
GROUP_NAME: <name> # tham chiếu nhanh, optional
|
|
52
|
+
7. Cho mỗi feature task tiếp theo → tạo task với groupId = GROUP_ID, lưu TASK_ID
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### Brownfield — app đã có code + Project sẵn trên Bitrix
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
1. Agent đọc CLAUDE.md root → check GROUP_ID có chưa
|
|
59
|
+
2. Nếu CHƯA có:
|
|
60
|
+
a. Agent hỏi: "Project ID trên Bitrix là gì?" hoặc
|
|
61
|
+
b. Agent → bitrix-synity-mcp: socintranet.workgroup.list (filter NAME)
|
|
62
|
+
→ user xác nhận đúng project
|
|
63
|
+
c. Ghi GROUP_ID vào CLAUDE.md root
|
|
64
|
+
3. Nếu ĐÃ có → dùng luôn, không hỏi
|
|
65
|
+
4. Agent → bitrix-synity-mcp: tasks.api.scrum.epic.list (groupId)
|
|
66
|
+
→ list Epic hiện có → user chọn hoặc tạo mới
|
|
67
|
+
5. TASK_ID per feature đặt vào CLAUDE.md feature folder (override root)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
#### CLAUDE.md keys reference
|
|
71
|
+
|
|
72
|
+
| Key | Required | Owner | Mục đích |
|
|
73
|
+
|-----|:--------:|-------|---------|
|
|
74
|
+
| `TASK_ID` | ✅ | feature folder (preferred) hoặc root | Bắt buộc — hooks parse |
|
|
75
|
+
| `PORTAL` | optional | root | Build task link trong session summary |
|
|
76
|
+
| `GROUP_ID` | optional | root | Project ID — agent lookup epic/task list |
|
|
77
|
+
| `GROUP_NAME` | optional | root | Tham chiếu nhanh, không dùng cho API call |
|
|
78
|
+
| `EPIC_ID` | optional | feature folder | Pre-set epic cho feature task mới |
|
|
79
|
+
| `CREATOR_ID` | optional | root | Default user khi agent tạo task mới |
|
|
80
|
+
| `RESPONSIBLE_ID` | optional | feature folder | Assign default — agent fallback nếu user không chỉ định |
|
|
81
|
+
|
|
82
|
+
## 2. Epic — Nhóm Tính Năng
|
|
83
|
+
|
|
84
|
+
Epic là **nhóm tính năng** trong một Workgroup Scrum. Mỗi task có thể thuộc một Epic (`epicId`).
|
|
85
|
+
|
|
86
|
+
### Khi nào dùng Epic
|
|
87
|
+
|
|
88
|
+
- App có nhiều tính năng độc lập → tạo Epic per tính năng
|
|
89
|
+
- Giúp filter task theo nhóm chức năng trên Bitrix board
|
|
90
|
+
- `epicId = null` → task chưa được gắn vào Epic nào
|
|
91
|
+
|
|
92
|
+
### Gợi ý đặt tên Epic theo app
|
|
93
|
+
|
|
94
|
+
Agent gợi ý user đặt Epic dựa trên các tính năng của app:
|
|
95
|
+
|
|
96
|
+
| App | Epic gợi ý |
|
|
97
|
+
|-----|-----------|
|
|
98
|
+
| SynFinance | Payment Integration / Expense Bot / Admin Dashboard |
|
|
99
|
+
| SynCRM | Deal Pipeline / Contact Sync / Activity Log |
|
|
100
|
+
| SynProject | Task Automation / Report Generator / Notification Engine |
|
|
101
|
+
|
|
102
|
+
### Agent Flow với Epic
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
Đọc task → epicId = 42
|
|
106
|
+
→ bitrix-synity-mcp: tasks.api.scrum.epic.get (id: 42)
|
|
107
|
+
→ đọc epic.name → bổ sung context: "Feature group: Payment Integration"
|
|
108
|
+
→ nếu user tạo task mới trong cùng nhóm → set epicId = 42
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Nếu `epicId = null` và task có scope lớn → hỏi user: "Task này thuộc nhóm tính năng nào? Tôi sẽ tạo hoặc gắn Epic."
|
|
112
|
+
|
|
113
|
+
## 3. Subtask vs Checklist — Decision Guide
|
|
114
|
+
|
|
115
|
+
Cả hai đều dùng để chia nhỏ công việc, nhưng phục vụ mục đích khác nhau.
|
|
116
|
+
|
|
117
|
+
### So sánh
|
|
118
|
+
|
|
119
|
+
| Tiêu chí | Subtask | Checklist item |
|
|
120
|
+
|---------|---------|---------------|
|
|
121
|
+
| Có assignee riêng | ✅ | ❌ |
|
|
122
|
+
| Có deadline riêng | ✅ | ❌ |
|
|
123
|
+
| Có status riêng (pending/done) | ✅ | ✅ (checked/unchecked) |
|
|
124
|
+
| Tạo git branch riêng | ✅ | ❌ |
|
|
125
|
+
| Track trong Bitrix board | ✅ | ❌ |
|
|
126
|
+
| Phù hợp cho | Work độc lập, dev khác nhau | Todo list trong 1 task |
|
|
127
|
+
|
|
128
|
+
### Convention cho AI coding workflow
|
|
129
|
+
|
|
130
|
+
**Dùng Subtask khi:**
|
|
131
|
+
- Phase cần branch + PR riêng (ví dụ: backend vs frontend)
|
|
132
|
+
- Developer khác nhau phụ trách
|
|
133
|
+
- Có deadline hoặc estimate riêng
|
|
134
|
+
- Cần track status độc lập trên Bitrix board
|
|
135
|
+
|
|
136
|
+
**Dùng Checklist khi:**
|
|
137
|
+
- Đây là plan steps của AI session (phases do AI thực hiện trong 1 branch)
|
|
138
|
+
- Không cần assignee/deadline riêng
|
|
139
|
+
- Muốn tick từng bước trong khi làm việc
|
|
140
|
+
- Ví dụ: `/ck:plan` output → sync thành checklists qua `b24_sync_checklist()` (Section 5)
|
|
141
|
+
|
|
142
|
+
### Quy tắc thực tế
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
1 feature task
|
|
146
|
+
├── 📋 Checklist: Phase 1 Backend ← AI làm trong 1 session
|
|
147
|
+
│ ├── [x] DB schema migration
|
|
148
|
+
│ └── [ ] Hono route handler
|
|
149
|
+
├── 📋 Checklist: Phase 2 Frontend ← AI làm trong session tiếp
|
|
150
|
+
│ └── [ ] ListShell component
|
|
151
|
+
└── Subtask: "QA Testing" ← Tester khác review/test
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## 4. Task Fields Reference
|
|
155
|
+
|
|
156
|
+
Các trường quan trọng agent cần biết khi query task qua `bitrix-synity-mcp`.
|
|
157
|
+
|
|
158
|
+
### Định danh & Navigation
|
|
159
|
+
|
|
160
|
+
| Field | Type | Dùng khi |
|
|
161
|
+
|-------|------|----------|
|
|
162
|
+
| `id` | integer | Mọi API call |
|
|
163
|
+
| `title` | string | Hiểu context task đang làm |
|
|
164
|
+
| `description` | string | Đọc scope, yêu cầu đầy đủ |
|
|
165
|
+
| `parentId` | integer | Xác nhận đây là subtask của task nào |
|
|
166
|
+
| `link` | string | Mention trong commit/PR message |
|
|
167
|
+
| `groupId` | integer | Biết task thuộc app/workgroup nào — xem [Section 1](#1-bitrix-object-hierarchy) |
|
|
168
|
+
| `epicId` | integer | Thuộc nhóm tính năng nào (Scrum only) — xem [Section 2](#2-epic--nhóm-tính-năng) |
|
|
169
|
+
|
|
170
|
+
### Trạng thái & Ưu tiên
|
|
171
|
+
|
|
172
|
+
| Field | Giá trị |
|
|
173
|
+
|-------|---------|
|
|
174
|
+
| `status` | `pending` / `in_progress` / `supposedly_completed` / `completed` / `deferred` / `declined` |
|
|
175
|
+
| `priority` | `high` / `average` / `low` |
|
|
176
|
+
| `deadline` | ISO-8601 — agent cảnh báo nếu sắp hết |
|
|
177
|
+
|
|
178
|
+
### Con người
|
|
179
|
+
|
|
180
|
+
| Field | Dùng khi |
|
|
181
|
+
|-------|----------|
|
|
182
|
+
| `responsibleId` | Developer phụ trách — lưu trong CLAUDE.md `RESPONSIBLE_ID` |
|
|
183
|
+
| `creatorId` | Phải = Claude user ID (`CREATOR_ID`) để được phép ghi task |
|
|
184
|
+
| `accomplices` | Những người liên quan — agent thêm vào context |
|
|
185
|
+
|
|
186
|
+
### Cấu trúc & Hierarchy
|
|
187
|
+
|
|
188
|
+
| Field | Dùng khi |
|
|
189
|
+
|-------|----------|
|
|
190
|
+
| `parentId` | Subtask của task nào — null nếu là task gốc |
|
|
191
|
+
| `containsSubTasks` | Check trước khi tạo subtask — tránh duplicate |
|
|
192
|
+
| `containsRelatedTasks` | Xem flow bên dưới |
|
|
193
|
+
| `checklist` | IDs checklist items — dùng để check/tick trạng thái |
|
|
194
|
+
| `chatId` | Chat của task — `tasks.task.chat.message.send` dùng field này nội bộ |
|
|
195
|
+
| `xmlId` | Agent ghi git branch name khi bắt đầu session → trace task↔branch |
|
|
196
|
+
|
|
197
|
+
#### containsRelatedTasks — Agent Flow
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
Đọc task → containsRelatedTasks = true
|
|
201
|
+
→ tasks.task.relation.get (taskId) → list [{taskId, relatedTaskId, relationType}]
|
|
202
|
+
→ đọc title/status related tasks
|
|
203
|
+
→ bổ sung vào context: "Task liên quan #456 (blocks) và #789 (related)"
|
|
204
|
+
|
|
205
|
+
User yêu cầu "thêm related task #999":
|
|
206
|
+
→ tasks.task.relation.add (taskId, relatedTaskId, relationType)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`relationType`: `"blocked_by"` / `"blocking"` / `"related"`
|
|
210
|
+
|
|
211
|
+
### Time tracking
|
|
212
|
+
|
|
213
|
+
| Field | Dùng khi |
|
|
214
|
+
|-------|----------|
|
|
215
|
+
| `elapsedTime` | Tổng thời gian đã log — agent tránh log trùng |
|
|
216
|
+
| `actualDuration` | Thời gian thực tế tính toán bởi Bitrix |
|
|
217
|
+
| `UF_AI_TOKENS_TOTAL` | Custom field (v0.2+) — cumulative AI token usage (input + output). Auto-created on first session. Read to check token budget. |
|
|
218
|
+
|
|
219
|
+
### CRM links
|
|
220
|
+
|
|
221
|
+
| Field | Dùng khi |
|
|
222
|
+
|-------|----------|
|
|
223
|
+
| `crmItemIds` | Task liên kết CRM object nào (`D_XX` deal, `C_XX` contact...) |
|
|
224
|
+
|
|
225
|
+
## 5. Checklist Sync API — Plan Phases → Bitrix
|
|
226
|
+
|
|
227
|
+
Khi agent lập plan, có thể sync từng phase thành một **named checklist** trong Bitrix task qua batch API.
|
|
228
|
+
|
|
229
|
+
### Cơ chế batch API
|
|
230
|
+
|
|
231
|
+
Batch API cho phép gọi tối đa 50 cmds trong 1 request và chain kết quả qua `$result[cmd_name]`:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
# Tạo 2 named checklists + items trong 1 batch call
|
|
235
|
+
POST /batch
|
|
236
|
+
{
|
|
237
|
+
"halt": 1,
|
|
238
|
+
"cmd": {
|
|
239
|
+
"cl_0": "task.checklistitem.add?TASKID=123&FIELDS[TITLE]=Phase+1+Backend&FIELDS[PARENT_ID]=0",
|
|
240
|
+
"item_0_0": "task.checklistitem.add?TASKID=123&FIELDS[TITLE]=DB+schema&FIELDS[PARENT_ID]=$result[cl_0]",
|
|
241
|
+
"item_0_1": "task.checklistitem.add?TASKID=123&FIELDS[TITLE]=Route+handler&FIELDS[PARENT_ID]=$result[cl_0]",
|
|
242
|
+
"cl_1": "task.checklistitem.add?TASKID=123&FIELDS[TITLE]=Phase+2+Frontend&FIELDS[PARENT_ID]=0",
|
|
243
|
+
"item_1_0": "task.checklistitem.add?TASKID=123&FIELDS[TITLE]=ListShell+component&FIELDS[PARENT_ID]=$result[cl_1]"
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
- `PARENT_ID=0` → tạo named checklist mới (tên = `TITLE`)
|
|
249
|
+
- `PARENT_ID=$result[cl_0]` → thêm item vào checklist vừa tạo
|
|
250
|
+
- `halt=1` → dừng nếu 1 cmd lỗi
|
|
251
|
+
|
|
252
|
+
### Kết quả trong Bitrix UI
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
Task: "Implement CRM Sync"
|
|
256
|
+
├── 📋 Phase 1 Backend
|
|
257
|
+
│ ├── [ ] DB schema
|
|
258
|
+
│ └── [ ] Route handler
|
|
259
|
+
└── 📋 Phase 2 Frontend
|
|
260
|
+
└── [ ] ListShell component
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Plan output format — `bitrix-checklist` fenced block
|
|
264
|
+
|
|
265
|
+
Planner agent emit JSON trong fenced code block với info-string `bitrix-checklist` để hook PostToolUse parse và sync tự động:
|
|
266
|
+
|
|
267
|
+
````markdown
|
|
268
|
+
```bitrix-checklist
|
|
269
|
+
[
|
|
270
|
+
{"name":"Phase 1: Backend","items":["DB schema","Hono route","Tests"]},
|
|
271
|
+
{"name":"Phase 2: Frontend","items":["ListShell","Composable"]}
|
|
272
|
+
]
|
|
273
|
+
```
|
|
274
|
+
````
|
|
275
|
+
|
|
276
|
+
**Spec contract:**
|
|
277
|
+
- Info-string PHẢI = `bitrix-checklist` (không phải `json` hoặc khác)
|
|
278
|
+
- Top-level: array của objects, mỗi object có `name` (string) + `items` (array of string)
|
|
279
|
+
- Tổng `phase_count + sum(items.length)` ≤ 50 (batch API limit)
|
|
280
|
+
- Chỉ 1 block per plan output — block thứ 2 trong cùng output sẽ bị bỏ qua
|
|
281
|
+
- Không emit block → no-op (planner muốn skip sync)
|
|
282
|
+
|
|
283
|
+
**Hook behavior (`PostToolUse` cho skill `plan` / `planner`):**
|
|
284
|
+
1. Regex extract block từ `tool_response`
|
|
285
|
+
2. `jq` validate schema → silent skip nếu invalid (không tạo checklist rác)
|
|
286
|
+
3. Đọc `TASK_ID` từ state file → call `b24_sync_checklist`
|
|
287
|
+
4. Errors → stderr only, exit 0
|
|
288
|
+
|
|
289
|
+
### Trigger qua shell script (manual)
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
# Agent gọi khi plan đã được approve
|
|
293
|
+
PHASES='[
|
|
294
|
+
{"name":"Phase 1 Backend","items":["DB schema migration","Hono route handler","Unit tests"]},
|
|
295
|
+
{"name":"Phase 2 Frontend","items":["ListShell component","useAsyncData composable"]}
|
|
296
|
+
]'
|
|
297
|
+
source .claude/scripts/bitrix-lib.sh
|
|
298
|
+
_b24_preflight || exit 0
|
|
299
|
+
TASK_ID=$(find_task_id)
|
|
300
|
+
b24_sync_checklist "$TASK_ID" "$PHASES"
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Limit:** Tối đa 50 cmds/batch. Script tự warn nếu vượt (phases × items + phase count > 50).
|
|
304
|
+
|
|
305
|
+
### `bitrix-lib.sh` exports
|
|
306
|
+
|
|
307
|
+
| Function | Mục đích |
|
|
308
|
+
|----------|---------|
|
|
309
|
+
| `_b24_preflight` | Check awk/jq/curl present; warn stderr nếu thiếu, return 1 (hooks dùng `\|\| exit 0`) |
|
|
310
|
+
| `_b24_md_field <file> <key>` | POSIX awk parse `KEY: value` từ markdown — works trên macOS BSD / Linux / Windows Git Bash |
|
|
311
|
+
| `find_task_id [dir]` | Walk up từ `dir` (default `pwd`), trả về `TASK_ID` đầu tiên trong CLAUDE.md |
|
|
312
|
+
| `find_task_meta [dir]` | Walk up, parse 4 field (`task_id`, `creator_id`, `responsible_id`, `portal`) → JSON |
|
|
313
|
+
| `b24_call <method> [body]` | POST JSON tới webhook (silent on error) |
|
|
314
|
+
| `b24_batch <cmd_obj> [halt=1]` | Gọi `batch` với JSON cmd object, chain `$result[]` |
|
|
315
|
+
| `b24_comment <task_id> <text>` | Post message vào task chat (`tasks.task.chat.message.send`) |
|
|
316
|
+
| `b24_log_time <task_id> <minutes> [comment]` | Ghi elapsed time (`task.elapseditem.add`, params `{TASKID, ARFIELDS:{SECONDS, COMMENT_TEXT}}`) |
|
|
317
|
+
| `b24_sync_checklist <task_id> <phases_json>` | Batch tạo named checklists + items |
|
|
318
|
+
| `skill_emoji <skill_name>` | Map skill → emoji cho comment prefix |
|