@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,51 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# SessionStart hook — warns if TASK_ID missing (Hard Rule #17); probes task status;
|
|
3
|
+
# auto-enables time tracking; writes session start ISO for digest.
|
|
4
|
+
# Non-blocking: always exit 0 so Claude session continues.
|
|
5
|
+
|
|
6
|
+
source "$(dirname "$0")/bitrix-lib.sh"
|
|
7
|
+
|
|
8
|
+
_b24_preflight || exit 0
|
|
9
|
+
|
|
10
|
+
PAYLOAD=$(cat)
|
|
11
|
+
SESSION_ID=$(echo "$PAYLOAD" | jq -r '.session_id // "unknown"')
|
|
12
|
+
|
|
13
|
+
# Write session start timestamp for Stop hook digest (Phase 4)
|
|
14
|
+
SESSION_ENV_FILE="$SESSION_DIR/session-env-${SESSION_ID}"
|
|
15
|
+
BTS_SESSION_START_ISO=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
16
|
+
echo "BTS_SESSION_START_ISO=${BTS_SESSION_START_ISO}" > "$SESSION_ENV_FILE"
|
|
17
|
+
|
|
18
|
+
TASK_ID=$(find_task_id) || {
|
|
19
|
+
cat >&2 <<'EOF'
|
|
20
|
+
⚠️ Hard Rule #17 violation: TASK_ID chưa set trong CLAUDE.md
|
|
21
|
+
|
|
22
|
+
Bitrix Task Sync sẽ KHÔNG hoạt động trong session này.
|
|
23
|
+
Hooks (PreToolUse/PostToolUse/Stop) sẽ silent skip.
|
|
24
|
+
|
|
25
|
+
Cách fix:
|
|
26
|
+
1. Mở CLAUDE.md root hoặc CLAUDE.md của feature đang làm
|
|
27
|
+
2. Thêm block:
|
|
28
|
+
## Bitrix Task
|
|
29
|
+
TASK_ID: <id-từ-Bitrix>
|
|
30
|
+
3. Reload session
|
|
31
|
+
|
|
32
|
+
Setup chi tiết: docs/bitrix-task-sync.md
|
|
33
|
+
EOF
|
|
34
|
+
exit 0
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Wipe stale skill counter from previous session
|
|
38
|
+
rm -f "$SESSION_DIR/skill-counter-${SESSION_ID}" \
|
|
39
|
+
"$SESSION_DIR/skill-counter-${SESSION_ID}.lock" 2>/dev/null || true
|
|
40
|
+
|
|
41
|
+
# Probe task status — skip lifecycle if task is closed (STATUS=5)
|
|
42
|
+
TASK_STATUS=$(b24_task_status "$TASK_ID") || true
|
|
43
|
+
if [[ "$TASK_STATUS" == "5" ]]; then
|
|
44
|
+
echo "BTS_TASK_CLOSED=1" >> "$SESSION_ENV_FILE"
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Auto-enable time tracking if not already enabled (Phase 2)
|
|
49
|
+
b24_ensure_timetracking "$TASK_ID" || true
|
|
50
|
+
|
|
51
|
+
exit 0
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Stop hook — fires when Claude finishes a turn.
|
|
3
|
+
# Posts full session digest: skills + time + tokens + files + commits + plan progress.
|
|
4
|
+
|
|
5
|
+
source "$(dirname "$0")/bitrix-lib.sh"
|
|
6
|
+
_b24_preflight || exit 0
|
|
7
|
+
|
|
8
|
+
PAYLOAD=$(cat)
|
|
9
|
+
SESSION_ID=$(echo "$PAYLOAD" | jq -r '.session_id // "unknown"')
|
|
10
|
+
TRANSCRIPT_PATH=$(echo "$PAYLOAD" | jq -r '.transcript_path // ""')
|
|
11
|
+
|
|
12
|
+
STATE_FILE="$SESSION_DIR/${SESSION_ID}.json"
|
|
13
|
+
[[ ! -f "$STATE_FILE" ]] && exit 0
|
|
14
|
+
|
|
15
|
+
STATE=$(cat "$STATE_FILE")
|
|
16
|
+
TASK_ID=$(echo "$STATE" | jq -r '.task_meta.task_id // ""')
|
|
17
|
+
[[ -z "$TASK_ID" ]] && exit 0
|
|
18
|
+
|
|
19
|
+
SKILL_COUNT=$(echo "$STATE" | jq '.skills_run | length')
|
|
20
|
+
[[ "$SKILL_COUNT" -eq 0 ]] && exit 0
|
|
21
|
+
|
|
22
|
+
SESSION_ENV_FILE="$SESSION_DIR/session-env-${SESSION_ID}"
|
|
23
|
+
SESSION_START=""
|
|
24
|
+
if [[ -f "$SESSION_ENV_FILE" ]]; then
|
|
25
|
+
source "$SESSION_ENV_FILE" 2>/dev/null || true
|
|
26
|
+
SESSION_START="${BTS_SESSION_START_ISO:-}"
|
|
27
|
+
fi
|
|
28
|
+
[[ -z "$SESSION_START" ]] && SESSION_START=$(echo "$STATE" | jq -r '.session_start')
|
|
29
|
+
|
|
30
|
+
# Stop timer crash-safety: if skill crashed mid-run, timer may still be running
|
|
31
|
+
if [[ "${BTS_TASK_CLOSED:-}" != "1" ]]; then
|
|
32
|
+
b24_timer_stop "$TASK_ID" || true
|
|
33
|
+
fi
|
|
34
|
+
# Wipe counter
|
|
35
|
+
rm -f "$SESSION_DIR/skill-counter-${SESSION_ID}" \
|
|
36
|
+
"$SESSION_DIR/skill-counter-${SESSION_ID}.lock" 2>/dev/null || true
|
|
37
|
+
|
|
38
|
+
# Write skills log to tmp file for renderer
|
|
39
|
+
SKILLS_LOG_FILE="$SESSION_DIR/skills-log-${SESSION_ID}.json"
|
|
40
|
+
echo "$STATE" | jq '.skills_run' > "$SKILLS_LOG_FILE"
|
|
41
|
+
|
|
42
|
+
# Phase 1: accumulate session tokens
|
|
43
|
+
TOKEN_LINE=""
|
|
44
|
+
if [[ -n "$TRANSCRIPT_PATH" && -f "$TRANSCRIPT_PATH" ]]; then
|
|
45
|
+
TOKEN_LINE=$(b24_finalize_session_tokens "$TASK_ID" "$TRANSCRIPT_PATH") || true
|
|
46
|
+
fi
|
|
47
|
+
# Fallback: probe recent JSONL if transcript_path not in payload.
|
|
48
|
+
# Exclude subagents/ dirs; sort newest-first to pick the right session.
|
|
49
|
+
if [[ -z "$TOKEN_LINE" ]]; then
|
|
50
|
+
CANDIDATE=$(find "${HOME}/.claude/projects" -maxdepth 2 -name "*.jsonl" \
|
|
51
|
+
-not -path "*/subagents/*" -newer "$STATE_FILE" 2>/dev/null \
|
|
52
|
+
| xargs ls -t 2>/dev/null | head -1 || true)
|
|
53
|
+
if [[ -n "$CANDIDATE" ]]; then
|
|
54
|
+
TOKEN_LINE=$(b24_finalize_session_tokens "$TASK_ID" "$CANDIDATE") || true
|
|
55
|
+
fi
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Resolve active plan dir from state or .claude/active-plan.txt
|
|
59
|
+
PLAN_DIR=""
|
|
60
|
+
ACTIVE_PLAN_FILE="${HOME}/.claude/active-plan.txt"
|
|
61
|
+
if [[ -f "$ACTIVE_PLAN_FILE" ]]; then
|
|
62
|
+
PLAN_DIR=$(cat "$ACTIVE_PLAN_FILE" | tr -d '[:space:]')
|
|
63
|
+
fi
|
|
64
|
+
[[ -n "$PLAN_DIR" && ! -d "$PLAN_DIR" ]] && PLAN_DIR=""
|
|
65
|
+
|
|
66
|
+
# Resolve repo root for commit log
|
|
67
|
+
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
|
|
68
|
+
|
|
69
|
+
# Attached files registry (from Phase 3 hook)
|
|
70
|
+
ATTACHED_LOG="$SESSION_DIR/attached-${SESSION_ID}.json"
|
|
71
|
+
|
|
72
|
+
# Render digest via pure renderer (use bash explicitly — script may not have +x bit)
|
|
73
|
+
SCRIPT_DIR="$(dirname "$0")"
|
|
74
|
+
MSG=$(bash "${SCRIPT_DIR}/bitrix-render-digest.sh" \
|
|
75
|
+
--session-start "$SESSION_START" \
|
|
76
|
+
--skills-log "$SKILLS_LOG_FILE" \
|
|
77
|
+
${PLAN_DIR:+--plan-dir "$PLAN_DIR"} \
|
|
78
|
+
${TOKEN_LINE:+--token-line "$TOKEN_LINE"} \
|
|
79
|
+
${ATTACHED_LOG:+--attached-log "$ATTACHED_LOG"} \
|
|
80
|
+
${REPO_ROOT:+--repo-root "$REPO_ROOT"})
|
|
81
|
+
|
|
82
|
+
[[ -z "$MSG" ]] && { rm -f "$STATE_FILE" "$SKILLS_LOG_FILE" "$SESSION_ENV_FILE" "$ATTACHED_LOG" 2>/dev/null; exit 0; }
|
|
83
|
+
b24_comment "$TASK_ID" "$MSG" || true
|
|
84
|
+
|
|
85
|
+
# Clean up session tmp files
|
|
86
|
+
rm -f "$STATE_FILE" "$SKILLS_LOG_FILE" "$SESSION_ENV_FILE" \
|
|
87
|
+
"$ATTACHED_LOG" "$SESSION_DIR/disk-root-${SESSION_ID}" 2>/dev/null || true
|
|
88
|
+
|
|
89
|
+
exit 0
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PostToolUse hook — fires after Skill tool returns result
|
|
3
|
+
# Posts result preview + logs elapsed time; decrements skill counter → pauses
|
|
4
|
+
# task timer on last skill (Phase 2); attaches plan/brainstorm artifacts (Phase 3).
|
|
5
|
+
|
|
6
|
+
source "$(dirname "$0")/bitrix-lib.sh"
|
|
7
|
+
_b24_preflight || exit 0
|
|
8
|
+
|
|
9
|
+
PAYLOAD=$(cat)
|
|
10
|
+
SESSION_ID=$(echo "$PAYLOAD" | jq -r '.session_id // "unknown"')
|
|
11
|
+
SKILL_NAME=$(echo "$PAYLOAD" | jq -r '.tool_input.skill // "unknown"')
|
|
12
|
+
|
|
13
|
+
STATE_FILE="$SESSION_DIR/${SESSION_ID}.json"
|
|
14
|
+
[[ ! -f "$STATE_FILE" ]] && exit 0
|
|
15
|
+
|
|
16
|
+
STATE=$(cat "$STATE_FILE")
|
|
17
|
+
TASK_ID=$(echo "$STATE" | jq -r '.task_meta.task_id // ""')
|
|
18
|
+
[[ -z "$TASK_ID" ]] && exit 0
|
|
19
|
+
|
|
20
|
+
START_TIME=$(echo "$STATE" | jq -r '.current_skill.start // 0')
|
|
21
|
+
END_TIME=$(date -u +%s)
|
|
22
|
+
ELAPSED_SECONDS=$((END_TIME - START_TIME))
|
|
23
|
+
ELAPSED_MINUTES=$(( (ELAPSED_SECONDS + 59) / 60 ))
|
|
24
|
+
|
|
25
|
+
# Take first 1500 chars of result as preview
|
|
26
|
+
RESULT=$(echo "$PAYLOAD" | jq -r '.tool_response // ""' | head -c 1500)
|
|
27
|
+
[[ ${#RESULT} -ge 1500 ]] && RESULT="${RESULT}
|
|
28
|
+
..."
|
|
29
|
+
|
|
30
|
+
# Update session state
|
|
31
|
+
STATE=$(echo "$STATE" | jq \
|
|
32
|
+
--arg skill "$SKILL_NAME" \
|
|
33
|
+
--argjson elapsed "$ELAPSED_SECONDS" \
|
|
34
|
+
--arg result "$RESULT" \
|
|
35
|
+
'.skills_run += [{skill: $skill, elapsed_seconds: $elapsed, result_preview: $result}]
|
|
36
|
+
| del(.current_skill)')
|
|
37
|
+
echo "$STATE" > "$STATE_FILE"
|
|
38
|
+
|
|
39
|
+
# Decrement skill counter; stop native timer on every skill end (records duration automatically)
|
|
40
|
+
_skill_counter_decr "$SESSION_ID" >/dev/null
|
|
41
|
+
SESSION_ENV_FILE="$SESSION_DIR/session-env-${SESSION_ID}"
|
|
42
|
+
if [[ -f "$SESSION_ENV_FILE" ]]; then
|
|
43
|
+
source "$SESSION_ENV_FILE" 2>/dev/null || true
|
|
44
|
+
fi
|
|
45
|
+
if [[ "${BTS_TASK_CLOSED:-}" != "1" ]]; then
|
|
46
|
+
b24_timer_stop "$TASK_ID"
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Phase 3a: sync bitrix-checklist fenced block → Bitrix task checklist (plan/planner only)
|
|
50
|
+
case "$SKILL_NAME" in
|
|
51
|
+
plan|planner)
|
|
52
|
+
TOOL_RESPONSE_FULL=$(echo "$PAYLOAD" | jq -r '.tool_response // ""')
|
|
53
|
+
CHECKLIST_JSON=$(printf '%s' "$TOOL_RESPONSE_FULL" | \
|
|
54
|
+
awk '/```bitrix-checklist/{flag=1;next}/```/{flag=0}flag' | \
|
|
55
|
+
jq -e '.' 2>/dev/null || true)
|
|
56
|
+
if [[ -n "$CHECKLIST_JSON" ]]; then
|
|
57
|
+
b24_sync_checklist "$TASK_ID" "$CHECKLIST_JSON" >/dev/null || true
|
|
58
|
+
fi
|
|
59
|
+
;;
|
|
60
|
+
esac
|
|
61
|
+
|
|
62
|
+
# Phase 3b: attach artifact files first so disk links can be included in comment
|
|
63
|
+
DISK_IDS=""
|
|
64
|
+
case "$SKILL_NAME" in
|
|
65
|
+
plan|planner|brainstorm|cook|research|journal)
|
|
66
|
+
DISK_IDS=$(_attach_skill_artifacts "$TASK_ID" "$SESSION_ID" "$PAYLOAD" "$SKILL_NAME")
|
|
67
|
+
;;
|
|
68
|
+
esac
|
|
69
|
+
|
|
70
|
+
# Only post "xong" when tool_response has human-readable content (not empty/pure JSON)
|
|
71
|
+
if _has_useful_content "$RESULT"; then
|
|
72
|
+
EMOJI=$(skill_emoji "$SKILL_NAME")
|
|
73
|
+
MSG="${EMOJI} [Claude] ${SKILL_NAME} xong (${ELAPSED_MINUTES} phút)
|
|
74
|
+
|
|
75
|
+
${RESULT}"
|
|
76
|
+
# Append [disk=ID] links for uploaded artifacts
|
|
77
|
+
if [[ -n "$DISK_IDS" ]]; then
|
|
78
|
+
while IFS= read -r did; do
|
|
79
|
+
[[ -n "$did" ]] && MSG="${MSG}[br]📎 [disk=${did}]"
|
|
80
|
+
done <<< "$DISK_IDS"
|
|
81
|
+
fi
|
|
82
|
+
b24_comment "$TASK_ID" "$MSG" || true
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
exit 0
|
|
86
|
+
|
|
87
|
+
# ── Artifact attach helper ───────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
_attach_skill_artifacts() {
|
|
90
|
+
local task_id="$1"
|
|
91
|
+
local session_id="$2"
|
|
92
|
+
local payload="$3"
|
|
93
|
+
local skill_name="$4"
|
|
94
|
+
|
|
95
|
+
local attached_files=()
|
|
96
|
+
local disk_ids=()
|
|
97
|
+
|
|
98
|
+
# Path 1: parse ```bitrix-artifact``` fenced block from tool_response
|
|
99
|
+
local tool_response
|
|
100
|
+
tool_response=$(echo "$payload" | jq -r '.tool_response // ""')
|
|
101
|
+
local artifact_path
|
|
102
|
+
artifact_path=$(printf '%s' "$tool_response" | \
|
|
103
|
+
awk '/```bitrix-artifact/{flag=1;next}/```/{flag=0}flag' | \
|
|
104
|
+
jq -r '.path // empty' 2>/dev/null || true)
|
|
105
|
+
|
|
106
|
+
if [[ -n "$artifact_path" && -f "$artifact_path" ]]; then
|
|
107
|
+
local rel_dir; rel_dir=$(dirname "$artifact_path")
|
|
108
|
+
local drive_id
|
|
109
|
+
drive_id=$(b24_attach_artifact "$task_id" "$artifact_path" "$session_id" "$rel_dir") || true
|
|
110
|
+
if [[ -n "$drive_id" ]]; then
|
|
111
|
+
attached_files+=("$artifact_path")
|
|
112
|
+
disk_ids+=("$drive_id")
|
|
113
|
+
fi
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# Path 2: fallback — glob .md files in plans/ modified in last 60s
|
|
117
|
+
if [[ ${#attached_files[@]} -eq 0 ]]; then
|
|
118
|
+
local glob_pattern=""
|
|
119
|
+
case "$skill_name" in
|
|
120
|
+
plan|planner) glob_pattern="plans/*/plan.md plans/*/phase-*.md" ;;
|
|
121
|
+
brainstorm) glob_pattern="plans/reports/brainstorm-*.md plans/*/brainstorm-*.md" ;;
|
|
122
|
+
cook) glob_pattern="plans/*/plan.md plans/*/phase-*.md plans/reports/*.md" ;;
|
|
123
|
+
research) glob_pattern="plans/reports/research-*.md plans/reports/researcher-*.md" ;;
|
|
124
|
+
journal) glob_pattern="docs/journal/*.md" ;;
|
|
125
|
+
esac
|
|
126
|
+
|
|
127
|
+
if [[ -n "$glob_pattern" ]]; then
|
|
128
|
+
local f
|
|
129
|
+
for f in $glob_pattern; do
|
|
130
|
+
[[ ! -f "$f" ]] && continue
|
|
131
|
+
# Only attach recently modified files (within last 60 seconds)
|
|
132
|
+
local age=$(( $(date -u +%s) - $(date -r "$f" +%s 2>/dev/null || echo 0) ))
|
|
133
|
+
[[ "$age" -gt 60 ]] && continue
|
|
134
|
+
local rel_dir; rel_dir=$(dirname "$f")
|
|
135
|
+
local drive_id
|
|
136
|
+
drive_id=$(b24_attach_artifact "$task_id" "$f" "$session_id" "$rel_dir") || true
|
|
137
|
+
if [[ -n "$drive_id" ]]; then
|
|
138
|
+
attached_files+=("$f")
|
|
139
|
+
disk_ids+=("$drive_id")
|
|
140
|
+
fi
|
|
141
|
+
done
|
|
142
|
+
fi
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
# Record attached files to session registry for Stop hook digest (Phase 4)
|
|
146
|
+
if [[ ${#attached_files[@]} -gt 0 ]]; then
|
|
147
|
+
local attached_json
|
|
148
|
+
attached_json=$(printf '%s\n' "${attached_files[@]}" | jq -R . | jq -s .)
|
|
149
|
+
local attach_log="$SESSION_DIR/attached-${session_id}.json"
|
|
150
|
+
if [[ -f "$attach_log" ]]; then
|
|
151
|
+
# Merge with existing list
|
|
152
|
+
local existing
|
|
153
|
+
existing=$(cat "$attach_log")
|
|
154
|
+
echo "$existing $attached_json" | jq -s '.[0] + .[1] | unique' > "$attach_log"
|
|
155
|
+
else
|
|
156
|
+
echo "$attached_json" > "$attach_log"
|
|
157
|
+
fi
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
# Echo drive IDs to stdout so caller can build [disk=ID] links
|
|
161
|
+
local did
|
|
162
|
+
for did in "${disk_ids[@]}"; do
|
|
163
|
+
echo "$did"
|
|
164
|
+
done
|
|
165
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PreToolUse hook — fires before Skill tool executes
|
|
3
|
+
# Captures full task meta into session state; posts "started" comment;
|
|
4
|
+
# increments skill counter → starts task timer on first skill (Phase 2).
|
|
5
|
+
|
|
6
|
+
source "$(dirname "$0")/bitrix-lib.sh"
|
|
7
|
+
_b24_preflight || exit 0
|
|
8
|
+
|
|
9
|
+
PAYLOAD=$(cat)
|
|
10
|
+
SESSION_ID=$(echo "$PAYLOAD" | jq -r '.session_id // "unknown"')
|
|
11
|
+
SKILL_NAME=$(echo "$PAYLOAD" | jq -r '.tool_input.skill // "unknown"')
|
|
12
|
+
SKILL_ARGS=$(echo "$PAYLOAD" | jq -r '.tool_input.args // ""')
|
|
13
|
+
|
|
14
|
+
STATE_FILE="$SESSION_DIR/${SESSION_ID}.json"
|
|
15
|
+
START_TIME=$(date -u +%s)
|
|
16
|
+
|
|
17
|
+
# Reuse existing state if present (multiple skills in one session); else bootstrap.
|
|
18
|
+
if [[ -f "$STATE_FILE" ]]; then
|
|
19
|
+
STATE=$(cat "$STATE_FILE")
|
|
20
|
+
TASK_META=$(echo "$STATE" | jq -c '.task_meta // empty')
|
|
21
|
+
else
|
|
22
|
+
TASK_META=$(find_task_meta) || exit 0
|
|
23
|
+
STATE=$(jq -n \
|
|
24
|
+
--arg sid "$SESSION_ID" \
|
|
25
|
+
--argjson meta "$TASK_META" \
|
|
26
|
+
--arg st "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
27
|
+
'{session_id: $sid, task_meta: $meta, session_start: $st, skills_run: []}')
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
[[ -z "$TASK_META" ]] && exit 0
|
|
31
|
+
TASK_ID=$(echo "$TASK_META" | jq -r '.task_id // ""')
|
|
32
|
+
[[ -z "$TASK_ID" ]] && exit 0
|
|
33
|
+
|
|
34
|
+
# Skip lifecycle if task was closed at SessionStart
|
|
35
|
+
SESSION_ENV_FILE="$SESSION_DIR/session-env-${SESSION_ID}"
|
|
36
|
+
if [[ -f "$SESSION_ENV_FILE" ]]; then
|
|
37
|
+
source "$SESSION_ENV_FILE" 2>/dev/null || true
|
|
38
|
+
[[ "${BTS_TASK_CLOSED:-}" == "1" ]] && exit 0
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
STATE=$(echo "$STATE" | jq \
|
|
42
|
+
--arg skill "$SKILL_NAME" \
|
|
43
|
+
--arg args "$SKILL_ARGS" \
|
|
44
|
+
--argjson start "$START_TIME" \
|
|
45
|
+
'.current_skill = {skill: $skill, args: $args, start: $start}')
|
|
46
|
+
|
|
47
|
+
echo "$STATE" > "$STATE_FILE"
|
|
48
|
+
|
|
49
|
+
# Increment skill counter; activate native timer on every skill start
|
|
50
|
+
_skill_counter_incr "$SESSION_ID" >/dev/null
|
|
51
|
+
b24_timer_start "$TASK_ID"
|
|
52
|
+
|
|
53
|
+
# Only post "bắt đầu" when args carry meaningful context (≥15 chars)
|
|
54
|
+
if [[ ${#SKILL_ARGS} -ge 15 ]]; then
|
|
55
|
+
EMOJI=$(skill_emoji "$SKILL_NAME")
|
|
56
|
+
b24_comment "$TASK_ID" "${EMOJI} [Claude] ${SKILL_NAME} bắt đầu: ${SKILL_ARGS}" || true
|
|
57
|
+
fi
|
|
58
|
+
exit 0
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# BB code rendering helpers for Bitrix task chat output.
|
|
3
|
+
# Bitrix renders BB code, NOT markdown. Source this file to use the helpers.
|
|
4
|
+
#
|
|
5
|
+
# Extracted from bitrix-render-digest.sh logic.
|
|
6
|
+
# Depends on: _format_duration from bitrix-lib.sh (source that first).
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# source "$(dirname "$0")/bitrix-lib.sh"
|
|
10
|
+
# source "$(dirname "$0")/bb-formatter.sh"
|
|
11
|
+
|
|
12
|
+
# Wrap text in [b]...[/b]
|
|
13
|
+
bb_bold() {
|
|
14
|
+
printf '[b]%s[/b]' "$1"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
# Wrap text in [i]...[/i]
|
|
18
|
+
bb_italic() {
|
|
19
|
+
printf '[i]%s[/i]' "$1"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# Wrap text in [code]...[/code]
|
|
23
|
+
bb_code() {
|
|
24
|
+
printf '[code]%s[/code]' "$1"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Wrap text in [quote]...[/quote]
|
|
28
|
+
bb_quote() {
|
|
29
|
+
printf '[quote]%s[/quote]' "$1"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Render a URL link: bb_link <url> <label>
|
|
33
|
+
bb_link() {
|
|
34
|
+
printf '[url=%s]%s[/url]' "$1" "$2"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Render a Bitrix Drive file attachment tag: bb_disk <drive_file_id>
|
|
38
|
+
bb_disk() {
|
|
39
|
+
printf '[disk=%s]' "$1"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Render a bullet list item with Unicode bullet
|
|
43
|
+
bb_bullet() {
|
|
44
|
+
printf '• %s' "$1"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Render the session summary header line
|
|
48
|
+
# bb_session_header <session_start_iso>
|
|
49
|
+
bb_session_header() {
|
|
50
|
+
local start="${1:-?}"
|
|
51
|
+
printf '[b][Claude] Session summary[/b] (%s)' "$start"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Render skills section given a skills_log JSON file path.
|
|
55
|
+
# Output: per-skill bullet lines + total line.
|
|
56
|
+
# Requires _format_duration from bitrix-lib.sh.
|
|
57
|
+
# bb_render_skills_section <skills_log_file>
|
|
58
|
+
bb_render_skills_section() {
|
|
59
|
+
local log_file="$1"
|
|
60
|
+
[[ ! -f "$log_file" ]] && return 0
|
|
61
|
+
|
|
62
|
+
local total_seconds
|
|
63
|
+
total_seconds=$(jq '[.[].elapsed_seconds // 0] | add // 0' "$log_file")
|
|
64
|
+
local count
|
|
65
|
+
count=$(jq 'length' "$log_file")
|
|
66
|
+
|
|
67
|
+
local summary=""
|
|
68
|
+
for ((idx = 0; idx < count; idx++)); do
|
|
69
|
+
local s_name s_sec s_fmt
|
|
70
|
+
s_name=$(jq -r ".[$idx].skill // \"?\"" "$log_file")
|
|
71
|
+
s_sec=$(jq -r ".[$idx].elapsed_seconds // 0" "$log_file")
|
|
72
|
+
s_fmt=$(_format_duration "$s_sec")
|
|
73
|
+
summary="${summary}• ${s_name} (${s_fmt})
|
|
74
|
+
"
|
|
75
|
+
done
|
|
76
|
+
# Trim trailing newline
|
|
77
|
+
summary="${summary%$'\n'}"
|
|
78
|
+
|
|
79
|
+
local total_fmt
|
|
80
|
+
total_fmt=$(_format_duration "$total_seconds")
|
|
81
|
+
|
|
82
|
+
if [[ -n "$summary" ]]; then
|
|
83
|
+
printf '%s\n\n[b]Tổng thời gian:[/b] %s' "$summary" "$total_fmt"
|
|
84
|
+
fi
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# Render plan progress section given a plan directory.
|
|
88
|
+
# bb_render_plan_section <plan_dir>
|
|
89
|
+
bb_render_plan_section() {
|
|
90
|
+
local plan_dir="$1"
|
|
91
|
+
[[ -z "$plan_dir" || ! -f "${plan_dir}/plan.md" ]] && return 0
|
|
92
|
+
|
|
93
|
+
local plan_title plan_status
|
|
94
|
+
plan_title=$(grep -m1 '^# Plan:' "${plan_dir}/plan.md" | sed 's/^# Plan: *//' || true)
|
|
95
|
+
plan_status=$(awk '/^---/{c++;next} c==1 && /^status:/{print $2;exit}' "${plan_dir}/plan.md" || true)
|
|
96
|
+
|
|
97
|
+
local checked=0 total_boxes=0
|
|
98
|
+
for f in "${plan_dir}"/phase-*.md; do
|
|
99
|
+
[[ ! -f "$f" ]] && continue
|
|
100
|
+
checked=$(( checked + $(grep -c '^\- \[x\]' "$f" 2>/dev/null || echo 0) ))
|
|
101
|
+
total_boxes=$(( total_boxes + $(grep -c '^\- \[.\]' "$f" 2>/dev/null || echo 0) ))
|
|
102
|
+
done
|
|
103
|
+
|
|
104
|
+
if [[ -n "$plan_title" ]]; then
|
|
105
|
+
local line="📋 [b]Plan:[/b] ${plan_title}"
|
|
106
|
+
[[ -n "$plan_status" ]] && line="${line} [${plan_status}]"
|
|
107
|
+
[[ "$total_boxes" -gt 0 ]] && line="${line} — ${checked}/${total_boxes} tasks done"
|
|
108
|
+
printf '%s' "$line"
|
|
109
|
+
fi
|
|
110
|
+
}
|