dw-kit 1.2.0 → 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/.claude/hooks/post-write.sh +64 -58
- package/.claude/hooks/pre-commit-gate.sh +96 -90
- package/.claude/hooks/privacy-block.sh +99 -94
- package/.claude/hooks/progress-ping.sh +53 -47
- package/.claude/hooks/safety-guard.sh +60 -54
- package/.claude/hooks/scout-block.sh +88 -82
- package/.claude/hooks/session-init.sh +91 -74
- package/.claude/hooks/stop-check.sh +88 -36
- package/.claude/hooks/telemetry-log.sh +34 -0
- package/.claude/rules/code-style.md +37 -37
- package/.claude/rules/commit-standards.md +37 -37
- package/.claude/rules/dw.md +136 -0
- package/.claude/settings.json +120 -99
- package/.claude/skills/dw-arch-review/SKILL.md +119 -119
- package/.claude/skills/dw-archive/SKILL.md +81 -81
- package/.claude/skills/dw-commit/SKILL.md +81 -81
- package/.claude/skills/dw-config-init/SKILL.md +91 -91
- package/.claude/skills/dw-config-validate/SKILL.md +75 -75
- package/.claude/skills/dw-dashboard/SKILL.md +209 -209
- package/.claude/skills/dw-debug/SKILL.md +97 -97
- package/.claude/skills/dw-decision/SKILL.md +116 -0
- package/.claude/skills/dw-docs-update/SKILL.md +125 -125
- package/.claude/skills/dw-estimate/SKILL.md +90 -90
- package/.claude/skills/dw-execute/SKILL.md +98 -98
- package/.claude/skills/dw-flow/SKILL.md +274 -274
- package/.claude/skills/dw-handoff/SKILL.md +81 -81
- package/.claude/skills/dw-kit-report/SKILL.md +152 -152
- package/.claude/skills/dw-log-work/SKILL.md +69 -69
- package/.claude/skills/dw-onboard/SKILL.md +201 -201
- package/.claude/skills/dw-plan/SKILL.md +125 -125
- package/.claude/skills/dw-prompt/SKILL.md +62 -62
- package/.claude/skills/dw-requirements/SKILL.md +98 -98
- package/.claude/skills/dw-research/SKILL.md +114 -114
- package/.claude/skills/dw-retroactive/SKILL.md +311 -311
- package/.claude/skills/dw-review/SKILL.md +66 -66
- package/.claude/skills/dw-rollback/SKILL.md +90 -90
- package/.claude/skills/dw-sprint-review/SKILL.md +99 -99
- package/.claude/skills/dw-task-init/SKILL.md +59 -59
- package/.claude/skills/dw-test-plan/SKILL.md +113 -113
- package/.claude/skills/dw-thinking/SKILL.md +70 -70
- package/.claude/skills/dw-upgrade/SKILL.md +72 -72
- package/.dw/config/dw.config.yml +82 -82
- package/.dw/core/PILLARS.md +122 -0
- package/.dw/core/templates/v2/spec.md +68 -0
- package/.dw/core/templates/v2/tracking.md +62 -0
- package/.dw/core/v14-evaluation-protocol.md +118 -0
- package/CLAUDE.md +42 -39
- package/MIGRATION-v1.3.md +201 -0
- package/README.md +43 -6
- package/package.json +86 -84
- package/src/cli.mjs +45 -9
- package/src/commands/dashboard.mjs +116 -0
- package/src/commands/doctor.mjs +165 -149
- package/src/commands/init.mjs +339 -332
- package/src/commands/metrics.mjs +165 -0
- package/src/commands/upgrade.mjs +297 -262
- package/src/lib/active-index.mjs +87 -0
- package/src/lib/copy.mjs +118 -110
- package/src/lib/cut-analysis.mjs +161 -0
- package/src/lib/telemetry.mjs +80 -0
- package/.claude/rules/dw-core.md +0 -100
- package/.claude/rules/dw-skills.md +0 -53
- package/.claude/rules/workflow-rules.md +0 -77
|
@@ -1,54 +1,60 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# .claude/hooks/safety-guard.sh
|
|
3
|
-
# Intercept destructive Bash commands trước khi execute.
|
|
4
|
-
# exit 0 = allow, exit 2 = block
|
|
5
|
-
#
|
|
6
|
-
# Được gọi bởi PreToolUse hook cho tất cả Bash commands.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
echo "
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# .claude/hooks/safety-guard.sh
|
|
3
|
+
# Intercept destructive Bash commands trước khi execute.
|
|
4
|
+
# exit 0 = allow, exit 2 = block
|
|
5
|
+
#
|
|
6
|
+
# Được gọi bởi PreToolUse hook cho tất cả Bash commands.
|
|
7
|
+
|
|
8
|
+
# Telemetry (local, fire-and-forget)
|
|
9
|
+
TELEMETRY_SCRIPT="${CLAUDE_PROJECT_DIR:-$(pwd)}/.claude/hooks/telemetry-log.sh"
|
|
10
|
+
if [ -x "$TELEMETRY_SCRIPT" ] && [ "${DW_NO_TELEMETRY:-}" != "1" ]; then
|
|
11
|
+
"$TELEMETRY_SCRIPT" hook safety-guard >/dev/null 2>&1 || true
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
INPUT=$(cat)
|
|
15
|
+
|
|
16
|
+
COMMAND=$(echo "$INPUT" | grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*"' \
|
|
17
|
+
| sed 's/"command"[[:space:]]*:[[:space:]]*"//;s/"$//' | head -1)
|
|
18
|
+
|
|
19
|
+
[ -z "$COMMAND" ] && exit 0
|
|
20
|
+
|
|
21
|
+
# ── Pattern 1: rm -rf với path quá rộng ──────────────────────────────────────
|
|
22
|
+
# Block: rm -rf / | rm -rf * | rm -rf . (nguy hiểm)
|
|
23
|
+
# Allow: rm -rf ./specific/path | rm -rf /tmp/specific-file
|
|
24
|
+
if echo "$COMMAND" | grep -qE 'rm\s+-rf?\s+(\/\s*$|\*|\.(\s|$))'; then
|
|
25
|
+
echo "🚨 BLOCKED: rm -rf với path nguy hiểm ($COMMAND)" >&2
|
|
26
|
+
echo " Chỉ định path cụ thể hơn để proceed." >&2
|
|
27
|
+
exit 2
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# ── Pattern 2: git push --force lên main/master ───────────────────────────────
|
|
31
|
+
if echo "$COMMAND" | grep -qE 'git\s+push\s+.*--force'; then
|
|
32
|
+
BRANCH=$(echo "$COMMAND" | grep -oE '(main|master|develop|dev)' | head -1)
|
|
33
|
+
if [ -n "$BRANCH" ]; then
|
|
34
|
+
echo "🚨 BLOCKED: git push --force lên $BRANCH" >&2
|
|
35
|
+
echo " Force push lên protected branch không được phép." >&2
|
|
36
|
+
exit 2
|
|
37
|
+
fi
|
|
38
|
+
# Force push lên non-protected branch: warn nhưng allow
|
|
39
|
+
echo "⚠ Warning: git push --force (non-protected branch)" >&2
|
|
40
|
+
echo " Proceed nếu intentional." >&2
|
|
41
|
+
exit 0
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# ── Pattern 3: Destructive SQL không có WHERE ─────────────────────────────────
|
|
45
|
+
if echo "$COMMAND" | grep -qiE '(DELETE\s+FROM|UPDATE\s+\w+\s+SET)' \
|
|
46
|
+
&& ! echo "$COMMAND" | grep -qi 'WHERE'; then
|
|
47
|
+
echo "🚨 BLOCKED: Destructive SQL không có WHERE clause" >&2
|
|
48
|
+
echo " Thêm WHERE clause hoặc confirm intentional." >&2
|
|
49
|
+
exit 2
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# ── Pattern 4: DROP TABLE / DROP DATABASE ────────────────────────────────────
|
|
53
|
+
if echo "$COMMAND" | grep -qiE 'DROP\s+(TABLE|DATABASE|SCHEMA)'; then
|
|
54
|
+
echo "⚠ Warning: DROP statement detected ($COMMAND)" >&2
|
|
55
|
+
echo " Đây có phải migration script đã được review không?" >&2
|
|
56
|
+
# Warn but allow — user có thể đang chạy migration
|
|
57
|
+
exit 0
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
exit 0
|
|
@@ -1,82 +1,88 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# .claude/hooks/scout-block.sh — dw-kit v1.2
|
|
3
|
-
# Block agent reads vào heavy/irrelevant directories để tăng performance.
|
|
4
|
-
# Học từ claudekit scout-block pattern.
|
|
5
|
-
#
|
|
6
|
-
# PreToolUse hook cho: Read, Glob
|
|
7
|
-
# exit 0 = allow, exit 2 = block
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"
|
|
52
|
-
"/
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"/
|
|
57
|
-
"/
|
|
58
|
-
"
|
|
59
|
-
"/
|
|
60
|
-
"/.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# .claude/hooks/scout-block.sh — dw-kit v1.2
|
|
3
|
+
# Block agent reads vào heavy/irrelevant directories để tăng performance.
|
|
4
|
+
# Học từ claudekit scout-block pattern.
|
|
5
|
+
#
|
|
6
|
+
# PreToolUse hook cho: Read, Glob
|
|
7
|
+
# exit 0 = allow, exit 2 = block
|
|
8
|
+
|
|
9
|
+
# Telemetry (local, fire-and-forget)
|
|
10
|
+
TELEMETRY_SCRIPT="${CLAUDE_PROJECT_DIR:-$(pwd)}/.claude/hooks/telemetry-log.sh"
|
|
11
|
+
if [ -x "$TELEMETRY_SCRIPT" ] && [ "${DW_NO_TELEMETRY:-}" != "1" ]; then
|
|
12
|
+
"$TELEMETRY_SCRIPT" hook scout-block >/dev/null 2>&1 || true
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
INPUT=$(cat)
|
|
16
|
+
|
|
17
|
+
# Extract tool name và file path từ JSON input
|
|
18
|
+
TOOL_NAME=$(echo "$INPUT" | node -e "
|
|
19
|
+
let d='';
|
|
20
|
+
process.stdin.on('data',c=>d+=c).on('end',()=>{
|
|
21
|
+
try{ process.stdout.write(JSON.parse(d).tool_name||''); }catch(e){}
|
|
22
|
+
});
|
|
23
|
+
" 2>/dev/null || true)
|
|
24
|
+
|
|
25
|
+
# Extract path tùy theo tool
|
|
26
|
+
if [ "$TOOL_NAME" = "Read" ]; then
|
|
27
|
+
TARGET=$(echo "$INPUT" | node -e "
|
|
28
|
+
let d='';
|
|
29
|
+
process.stdin.on('data',c=>d+=c).on('end',()=>{
|
|
30
|
+
try{ const p=JSON.parse(d); process.stdout.write((p.tool_input&&p.tool_input.file_path)||''); }catch(e){}
|
|
31
|
+
});
|
|
32
|
+
" 2>/dev/null || true)
|
|
33
|
+
elif [ "$TOOL_NAME" = "Glob" ]; then
|
|
34
|
+
TARGET=$(echo "$INPUT" | node -e "
|
|
35
|
+
let d='';
|
|
36
|
+
process.stdin.on('data',c=>d+=c).on('end',()=>{
|
|
37
|
+
try{ const p=JSON.parse(d); const ti=p.tool_input||{}; process.stdout.write(ti.path||ti.pattern||''); }catch(e){}
|
|
38
|
+
});
|
|
39
|
+
" 2>/dev/null || true)
|
|
40
|
+
else
|
|
41
|
+
exit 0
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
[ -z "$TARGET" ] && exit 0
|
|
45
|
+
|
|
46
|
+
# Normalize: lowercase, forward slashes
|
|
47
|
+
NORM=$(echo "$TARGET" | tr '\\' '/')
|
|
48
|
+
|
|
49
|
+
# ── Danh sách heavy/irrelevant directories ─────────────────────────────────────
|
|
50
|
+
BLOCKED_PATTERNS=(
|
|
51
|
+
"node_modules/"
|
|
52
|
+
"/node_modules"
|
|
53
|
+
"dist/"
|
|
54
|
+
"/dist/"
|
|
55
|
+
"build/"
|
|
56
|
+
"/build/"
|
|
57
|
+
".git/"
|
|
58
|
+
"/__pycache__/"
|
|
59
|
+
"/.pytest_cache/"
|
|
60
|
+
"/.next/"
|
|
61
|
+
"/.nuxt/"
|
|
62
|
+
"/vendor/"
|
|
63
|
+
"/coverage/"
|
|
64
|
+
"/.tmp/"
|
|
65
|
+
"/tmp/"
|
|
66
|
+
"/.cache/"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
for pattern in "${BLOCKED_PATTERNS[@]}"; do
|
|
70
|
+
if echo "$NORM" | grep -q "$pattern"; then
|
|
71
|
+
echo "⚡ scout-block: Skipped heavy directory [${TOOL_NAME}] $(basename "$TARGET")/" >&2
|
|
72
|
+
echo " Path: $TARGET" >&2
|
|
73
|
+
echo " Dùng path cụ thể hơn nếu thực sự cần đọc file này." >&2
|
|
74
|
+
exit 2
|
|
75
|
+
fi
|
|
76
|
+
done
|
|
77
|
+
|
|
78
|
+
# Block pattern: kết thúc bằng thư mục blocked (không có trailing slash)
|
|
79
|
+
BLOCKED_EXACT=("node_modules" ".git" "dist" "build" "__pycache__" ".next" ".nuxt" "vendor" "coverage")
|
|
80
|
+
BASENAME=$(basename "$NORM")
|
|
81
|
+
for exact in "${BLOCKED_EXACT[@]}"; do
|
|
82
|
+
if [ "$BASENAME" = "$exact" ]; then
|
|
83
|
+
echo "⚡ scout-block: Skipped heavy directory [${TOOL_NAME}] $BASENAME/" >&2
|
|
84
|
+
exit 2
|
|
85
|
+
fi
|
|
86
|
+
done
|
|
87
|
+
|
|
88
|
+
exit 0
|
|
@@ -1,74 +1,91 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# .claude/hooks/session-init.sh — dw-kit v1.2
|
|
3
|
-
# Inject active task context vào đầu session, giải quyết "session amnesia".
|
|
4
|
-
# Chỉ chạy một lần mỗi session (track bằng session_id).
|
|
5
|
-
#
|
|
6
|
-
# UserPromptSubmit hook
|
|
7
|
-
# Stdout output → được inject vào context của user prompt
|
|
8
|
-
# exit 0 = allow
|
|
9
|
-
|
|
10
|
-
INPUT=$(cat)
|
|
11
|
-
|
|
12
|
-
SESSION_ID=$(echo "$INPUT" | node -e "
|
|
13
|
-
let d='';
|
|
14
|
-
process.stdin.on('data',c=>d+=c).on('end',()=>{
|
|
15
|
-
try{ process.stdout.write(JSON.parse(d).session_id||''); }catch(e){}
|
|
16
|
-
});
|
|
17
|
-
" 2>/dev/null || true)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
echo ""
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
exit 0
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# .claude/hooks/session-init.sh — dw-kit v1.2
|
|
3
|
+
# Inject active task context vào đầu session, giải quyết "session amnesia".
|
|
4
|
+
# Chỉ chạy một lần mỗi session (track bằng session_id).
|
|
5
|
+
#
|
|
6
|
+
# UserPromptSubmit hook
|
|
7
|
+
# Stdout output → được inject vào context của user prompt
|
|
8
|
+
# exit 0 = allow
|
|
9
|
+
|
|
10
|
+
INPUT=$(cat)
|
|
11
|
+
|
|
12
|
+
SESSION_ID=$(echo "$INPUT" | node -e "
|
|
13
|
+
let d='';
|
|
14
|
+
process.stdin.on('data',c=>d+=c).on('end',()=>{
|
|
15
|
+
try{ process.stdout.write(JSON.parse(d).session_id||''); }catch(e){}
|
|
16
|
+
});
|
|
17
|
+
" 2>/dev/null || true)
|
|
18
|
+
|
|
19
|
+
# Tier 2: pure-bash grep fallback — works without node (e.g. node absent or CRLF-corrupt shebang)
|
|
20
|
+
if [ -z "$SESSION_ID" ]; then
|
|
21
|
+
SESSION_ID=$(echo "$INPUT" | grep -o '"session_id":"[^"]*"' | cut -d'"' -f4 2>/dev/null || true)
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Tier 3: project-scoped + hour-scoped stable ID
|
|
25
|
+
# cksum is POSIX — available on Linux, macOS, and Git Bash on Windows.
|
|
26
|
+
# Ensures marker is always created so re-injection is suppressed even when tiers 1+2 fail.
|
|
27
|
+
if [ -z "$SESSION_ID" ]; then
|
|
28
|
+
_dir_hash=$(pwd | cksum | cut -d' ' -f1)
|
|
29
|
+
SESSION_ID="fallback-${_dir_hash}-$(date +%Y%m%d-%H)"
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# ── Track session: chỉ chạy một lần mỗi session ───────────────────────────────
|
|
33
|
+
SESSION_MARKER="/tmp/dw-session-${SESSION_ID}"
|
|
34
|
+
if [ -f "$SESSION_MARKER" ]; then
|
|
35
|
+
exit 0
|
|
36
|
+
fi
|
|
37
|
+
touch "$SESSION_MARKER" 2>/dev/null || true
|
|
38
|
+
|
|
39
|
+
# Telemetry (once per session, fire-and-forget)
|
|
40
|
+
TELEMETRY_SCRIPT="${CLAUDE_PROJECT_DIR:-$(pwd)}/.claude/hooks/telemetry-log.sh"
|
|
41
|
+
if [ -x "$TELEMETRY_SCRIPT" ] && [ "${DW_NO_TELEMETRY:-}" != "1" ]; then
|
|
42
|
+
"$TELEMETRY_SCRIPT" hook session-init >/dev/null 2>&1 || true
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# ── Scan .dw/tasks/ tìm tasks In Progress ─────────────────────────────────────
|
|
46
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
47
|
+
TASKS_DIR="$PROJECT_DIR/.dw/tasks"
|
|
48
|
+
|
|
49
|
+
[ ! -d "$TASKS_DIR" ] && exit 0
|
|
50
|
+
|
|
51
|
+
ACTIVE_TASKS=()
|
|
52
|
+
ACTIVE_SUMMARIES=()
|
|
53
|
+
|
|
54
|
+
while IFS= read -r progress_file; do
|
|
55
|
+
if grep -q "Trạng thái: In Progress" "$progress_file" 2>/dev/null; then
|
|
56
|
+
task_name=$(basename "$(dirname "$progress_file")")
|
|
57
|
+
|
|
58
|
+
# Extract current subtask (dòng "In Progress" trong table)
|
|
59
|
+
current_st=$(grep -m1 "In Progress" "$progress_file" 2>/dev/null \
|
|
60
|
+
| grep -oP '\| ST-\d+ \| [^|]+' | sed 's/|//g' | xargs 2>/dev/null || echo "")
|
|
61
|
+
|
|
62
|
+
# Extract last handoff note nếu có
|
|
63
|
+
last_handoff=$(awk '/## Handoff Notes/{found=1} found{print}' "$progress_file" 2>/dev/null \
|
|
64
|
+
| grep -m1 "Bước tiếp theo:" | sed 's/.*Bước tiếp theo://' | xargs 2>/dev/null || echo "")
|
|
65
|
+
|
|
66
|
+
ACTIVE_TASKS+=("$task_name")
|
|
67
|
+
summary="$task_name"
|
|
68
|
+
[ -n "$current_st" ] && summary="$summary — $current_st"
|
|
69
|
+
[ -n "$last_handoff" ] && summary="$summary | Next: $last_handoff"
|
|
70
|
+
ACTIVE_SUMMARIES+=("$summary")
|
|
71
|
+
fi
|
|
72
|
+
done < <(find "$TASKS_DIR" -name "*-progress.md" 2>/dev/null)
|
|
73
|
+
|
|
74
|
+
[ ${#ACTIVE_TASKS[@]} -eq 0 ] && exit 0
|
|
75
|
+
|
|
76
|
+
# ── Output context vào stdout (injected vào conversation) ─────────────────────
|
|
77
|
+
echo ""
|
|
78
|
+
echo "---"
|
|
79
|
+
echo "[dw-kit session-init] Task đang in-progress:"
|
|
80
|
+
for summary in "${ACTIVE_SUMMARIES[@]}"; do
|
|
81
|
+
echo " • $summary"
|
|
82
|
+
done
|
|
83
|
+
if [ ${#ACTIVE_TASKS[@]} -eq 1 ]; then
|
|
84
|
+
echo "Context: .dw/tasks/${ACTIVE_TASKS[0]}/"
|
|
85
|
+
else
|
|
86
|
+
echo "Nhiều tasks đang active — hỏi user task nào cần tiếp tục."
|
|
87
|
+
fi
|
|
88
|
+
echo "---"
|
|
89
|
+
echo ""
|
|
90
|
+
|
|
91
|
+
exit 0
|
|
@@ -1,36 +1,88 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Stop hook: warn
|
|
3
|
-
#
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Stop hook: warn on uncommitted changes + auto-append handoff to active task tracking.md
|
|
3
|
+
# v1.4: auto-handoff (ST-2.5) — append session summary to tracking.md when uncommitted
|
|
4
|
+
# Output via stderr for user visibility. Always exit 0 (non-blocking).
|
|
5
|
+
|
|
6
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
7
|
+
WARNINGS=()
|
|
8
|
+
|
|
9
|
+
# --- Telemetry (local, fire-and-forget) ---
|
|
10
|
+
TELEMETRY_SCRIPT="$PROJECT_DIR/.claude/hooks/telemetry-log.sh"
|
|
11
|
+
if [ -x "$TELEMETRY_SCRIPT" ] && [ "${DW_NO_TELEMETRY:-}" != "1" ]; then
|
|
12
|
+
"$TELEMETRY_SCRIPT" hook stop-check >/dev/null 2>&1 || true
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
# --- Check uncommitted changes ---
|
|
16
|
+
HAS_UNCOMMITTED=0
|
|
17
|
+
if git -C "$PROJECT_DIR" diff --quiet && git -C "$PROJECT_DIR" diff --cached --quiet; then
|
|
18
|
+
: # clean
|
|
19
|
+
else
|
|
20
|
+
HAS_UNCOMMITTED=1
|
|
21
|
+
CHANGED=$(git -C "$PROJECT_DIR" diff --stat --cached && git -C "$PROJECT_DIR" diff --stat)
|
|
22
|
+
WARNINGS+=("Uncommitted changes:"$'\n'"$CHANGED")
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# --- Check in-progress tasks (both v1 3-file and v2 2-file formats) ---
|
|
26
|
+
TASKS_DIR="$PROJECT_DIR/.dw/tasks"
|
|
27
|
+
ACTIVE_TASK=""
|
|
28
|
+
|
|
29
|
+
if [ -d "$TASKS_DIR" ]; then
|
|
30
|
+
# v1 format: {task}-progress.md with "Trạng thái: In Progress"
|
|
31
|
+
while IFS= read -r progress_file; do
|
|
32
|
+
if grep -q "Trạng thái: In Progress" "$progress_file" 2>/dev/null; then
|
|
33
|
+
task_name=$(basename "$(dirname "$progress_file")")
|
|
34
|
+
WARNINGS+=("Task in-progress (v1): $task_name")
|
|
35
|
+
[ -z "$ACTIVE_TASK" ] && ACTIVE_TASK="$task_name"
|
|
36
|
+
fi
|
|
37
|
+
done < <(find "$TASKS_DIR" -maxdepth 3 -name "*-progress.md" -not -path "*/archive/*" 2>/dev/null)
|
|
38
|
+
|
|
39
|
+
# v2 format: tracking.md with frontmatter status In Progress
|
|
40
|
+
while IFS= read -r tracking_file; do
|
|
41
|
+
if grep -qE "^status:.*(In Progress|Code Complete)" "$tracking_file" 2>/dev/null; then
|
|
42
|
+
task_name=$(basename "$(dirname "$tracking_file")")
|
|
43
|
+
WARNINGS+=("Task active (v2): $task_name")
|
|
44
|
+
[ -z "$ACTIVE_TASK" ] && ACTIVE_TASK="$task_name"
|
|
45
|
+
fi
|
|
46
|
+
done < <(find "$TASKS_DIR" -maxdepth 3 -name "tracking.md" -not -path "*/archive/*" 2>/dev/null)
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# --- Auto-handoff: append snippet to active task's tracking.md if uncommitted + active task ---
|
|
50
|
+
if [ "$HAS_UNCOMMITTED" = "1" ] && [ -n "$ACTIVE_TASK" ]; then
|
|
51
|
+
TRACKING_FILE="$TASKS_DIR/$ACTIVE_TASK/tracking.md"
|
|
52
|
+
if [ -f "$TRACKING_FILE" ]; then
|
|
53
|
+
TS=$(date -u +"%Y-%m-%d %H:%M UTC")
|
|
54
|
+
MARKER="<!-- dw-auto-handoff -->"
|
|
55
|
+
|
|
56
|
+
# Only append if no handoff snippet added in last 10 minutes (idempotency via marker + timestamp grep)
|
|
57
|
+
if ! grep -q "$MARKER.*$TS" "$TRACKING_FILE" 2>/dev/null; then
|
|
58
|
+
{
|
|
59
|
+
echo ""
|
|
60
|
+
echo "$MARKER"
|
|
61
|
+
echo "### Auto-handoff — $TS"
|
|
62
|
+
echo ""
|
|
63
|
+
echo "Session ended with uncommitted changes."
|
|
64
|
+
echo ""
|
|
65
|
+
echo "**Files changed:**"
|
|
66
|
+
echo '```'
|
|
67
|
+
git -C "$PROJECT_DIR" diff --stat --cached 2>/dev/null
|
|
68
|
+
git -C "$PROJECT_DIR" diff --stat 2>/dev/null
|
|
69
|
+
echo '```'
|
|
70
|
+
echo ""
|
|
71
|
+
echo "Next session: commit or continue work. Re-read spec.md + this tracking.md first."
|
|
72
|
+
echo ""
|
|
73
|
+
} >> "$TRACKING_FILE" 2>/dev/null
|
|
74
|
+
WARNINGS+=("Auto-handoff appended to: $TRACKING_FILE")
|
|
75
|
+
fi
|
|
76
|
+
fi
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# --- Print warnings ---
|
|
80
|
+
if [ ${#WARNINGS[@]} -gt 0 ]; then
|
|
81
|
+
printf -- "--- dw stop-check ---\n" >&2
|
|
82
|
+
for w in "${WARNINGS[@]}"; do
|
|
83
|
+
printf "⚠ %b\n" "$w" >&2
|
|
84
|
+
done
|
|
85
|
+
printf -- "---------------------\n" >&2
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
exit 0
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Lightweight telemetry logger — invoked by other hooks to record events.
|
|
3
|
+
# Usage: telemetry-log.sh <event-type> <name> [result] [latency_ms]
|
|
4
|
+
# Silently no-ops if DW_NO_TELEMETRY=1 or .dw/metrics unavailable.
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
if [ "${DW_NO_TELEMETRY:-}" = "1" ] || [ "${DW_NO_TELEMETRY:-}" = "true" ]; then
|
|
9
|
+
exit 0
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
EVENT_TYPE="${1:-unknown}"
|
|
13
|
+
NAME="${2:-unknown}"
|
|
14
|
+
RESULT="${3:-}"
|
|
15
|
+
LATENCY="${4:-}"
|
|
16
|
+
|
|
17
|
+
METRICS_DIR=".dw/metrics"
|
|
18
|
+
EVENTS_FILE="$METRICS_DIR/events.jsonl"
|
|
19
|
+
|
|
20
|
+
if [ ! -d "$METRICS_DIR" ]; then
|
|
21
|
+
mkdir -p "$METRICS_DIR" 2>/dev/null || exit 0
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
25
|
+
SESSION_HASH=$(echo "$$:${CLAUDE_SESSION_ID:-default}" | sha256sum 2>/dev/null | cut -c1-8 || echo "unknown")
|
|
26
|
+
|
|
27
|
+
JSON="{\"ts\":\"$TS\",\"session\":\"$SESSION_HASH\",\"event\":\"$EVENT_TYPE\",\"name\":\"$NAME\""
|
|
28
|
+
[ -n "$RESULT" ] && JSON="$JSON,\"result\":\"$RESULT\""
|
|
29
|
+
[ -n "$LATENCY" ] && JSON="$JSON,\"latency_ms\":$LATENCY"
|
|
30
|
+
JSON="$JSON}"
|
|
31
|
+
|
|
32
|
+
echo "$JSON" >> "$EVENTS_FILE" 2>/dev/null || true
|
|
33
|
+
|
|
34
|
+
exit 0
|