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.
Files changed (63) hide show
  1. package/.claude/hooks/post-write.sh +64 -58
  2. package/.claude/hooks/pre-commit-gate.sh +96 -90
  3. package/.claude/hooks/privacy-block.sh +99 -94
  4. package/.claude/hooks/progress-ping.sh +53 -47
  5. package/.claude/hooks/safety-guard.sh +60 -54
  6. package/.claude/hooks/scout-block.sh +88 -82
  7. package/.claude/hooks/session-init.sh +91 -74
  8. package/.claude/hooks/stop-check.sh +88 -36
  9. package/.claude/hooks/telemetry-log.sh +34 -0
  10. package/.claude/rules/code-style.md +37 -37
  11. package/.claude/rules/commit-standards.md +37 -37
  12. package/.claude/rules/dw.md +136 -0
  13. package/.claude/settings.json +120 -99
  14. package/.claude/skills/dw-arch-review/SKILL.md +119 -119
  15. package/.claude/skills/dw-archive/SKILL.md +81 -81
  16. package/.claude/skills/dw-commit/SKILL.md +81 -81
  17. package/.claude/skills/dw-config-init/SKILL.md +91 -91
  18. package/.claude/skills/dw-config-validate/SKILL.md +75 -75
  19. package/.claude/skills/dw-dashboard/SKILL.md +209 -209
  20. package/.claude/skills/dw-debug/SKILL.md +97 -97
  21. package/.claude/skills/dw-decision/SKILL.md +116 -0
  22. package/.claude/skills/dw-docs-update/SKILL.md +125 -125
  23. package/.claude/skills/dw-estimate/SKILL.md +90 -90
  24. package/.claude/skills/dw-execute/SKILL.md +98 -98
  25. package/.claude/skills/dw-flow/SKILL.md +274 -274
  26. package/.claude/skills/dw-handoff/SKILL.md +81 -81
  27. package/.claude/skills/dw-kit-report/SKILL.md +152 -152
  28. package/.claude/skills/dw-log-work/SKILL.md +69 -69
  29. package/.claude/skills/dw-onboard/SKILL.md +201 -201
  30. package/.claude/skills/dw-plan/SKILL.md +125 -125
  31. package/.claude/skills/dw-prompt/SKILL.md +62 -62
  32. package/.claude/skills/dw-requirements/SKILL.md +98 -98
  33. package/.claude/skills/dw-research/SKILL.md +114 -114
  34. package/.claude/skills/dw-retroactive/SKILL.md +311 -311
  35. package/.claude/skills/dw-review/SKILL.md +66 -66
  36. package/.claude/skills/dw-rollback/SKILL.md +90 -90
  37. package/.claude/skills/dw-sprint-review/SKILL.md +99 -99
  38. package/.claude/skills/dw-task-init/SKILL.md +59 -59
  39. package/.claude/skills/dw-test-plan/SKILL.md +113 -113
  40. package/.claude/skills/dw-thinking/SKILL.md +70 -70
  41. package/.claude/skills/dw-upgrade/SKILL.md +72 -72
  42. package/.dw/config/dw.config.yml +82 -82
  43. package/.dw/core/PILLARS.md +122 -0
  44. package/.dw/core/templates/v2/spec.md +68 -0
  45. package/.dw/core/templates/v2/tracking.md +62 -0
  46. package/.dw/core/v14-evaluation-protocol.md +118 -0
  47. package/CLAUDE.md +42 -39
  48. package/MIGRATION-v1.3.md +201 -0
  49. package/README.md +43 -6
  50. package/package.json +86 -84
  51. package/src/cli.mjs +45 -9
  52. package/src/commands/dashboard.mjs +116 -0
  53. package/src/commands/doctor.mjs +165 -149
  54. package/src/commands/init.mjs +339 -332
  55. package/src/commands/metrics.mjs +165 -0
  56. package/src/commands/upgrade.mjs +297 -262
  57. package/src/lib/active-index.mjs +87 -0
  58. package/src/lib/copy.mjs +118 -110
  59. package/src/lib/cut-analysis.mjs +161 -0
  60. package/src/lib/telemetry.mjs +80 -0
  61. package/.claude/rules/dw-core.md +0 -100
  62. package/.claude/rules/dw-skills.md +0 -53
  63. 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
- INPUT=$(cat)
9
-
10
- COMMAND=$(echo "$INPUT" | grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*"' \
11
- | sed 's/"command"[[:space:]]*:[[:space:]]*"//;s/"$//' | head -1)
12
-
13
- [ -z "$COMMAND" ] && exit 0
14
-
15
- # ── Pattern 1: rm -rf với path quá rộng ──────────────────────────────────────
16
- # Block: rm -rf / | rm -rf * | rm -rf . (nguy hiểm)
17
- # Allow: rm -rf ./specific/path | rm -rf /tmp/specific-file
18
- if echo "$COMMAND" | grep -qE 'rm\s+-rf?\s+(\/\s*$|\*|\.(\s|$))'; then
19
- echo "🚨 BLOCKED: rm -rf với path nguy hiểm ($COMMAND)" >&2
20
- echo " Chỉ định path cụ thể hơn để proceed." >&2
21
- exit 2
22
- fi
23
-
24
- # ── Pattern 2: git push --force lên main/master ───────────────────────────────
25
- if echo "$COMMAND" | grep -qE 'git\s+push\s+.*--force'; then
26
- BRANCH=$(echo "$COMMAND" | grep -oE '(main|master|develop|dev)' | head -1)
27
- if [ -n "$BRANCH" ]; then
28
- echo "🚨 BLOCKED: git push --force lên $BRANCH" >&2
29
- echo " Force push lên protected branch không được phép." >&2
30
- exit 2
31
- fi
32
- # Force push lên non-protected branch: warn nhưng allow
33
- echo "⚠ Warning: git push --force (non-protected branch)" >&2
34
- echo " Proceed nếu intentional." >&2
35
- exit 0
36
- fi
37
-
38
- # ── Pattern 3: Destructive SQL không WHERE ─────────────────────────────────
39
- if echo "$COMMAND" | grep -qiE '(DELETE\s+FROM|UPDATE\s+\w+\s+SET)' \
40
- && ! echo "$COMMAND" | grep -qi 'WHERE'; then
41
- echo "🚨 BLOCKED: Destructive SQL không có WHERE clause" >&2
42
- echo " Thêm WHERE clause hoặc confirm intentional." >&2
43
- exit 2
44
- fi
45
-
46
- # ── Pattern 4: DROP TABLE / DROP DATABASE ────────────────────────────────────
47
- if echo "$COMMAND" | grep -qiE 'DROP\s+(TABLE|DATABASE|SCHEMA)'; then
48
- echo "⚠ Warning: DROP statement detected ($COMMAND)" >&2
49
- echo " Đây có phải migration script đã được review không?" >&2
50
- # Warn but allow — user có thể đang chạy migration
51
- exit 0
52
- fi
53
-
54
- exit 0
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
- INPUT=$(cat)
10
-
11
- # Extract tool name file path từ JSON input
12
- TOOL_NAME=$(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).tool_name||''); }catch(e){}
16
- });
17
- " 2>/dev/null || true)
18
-
19
- # Extract path tùy theo tool
20
- if [ "$TOOL_NAME" = "Read" ]; then
21
- TARGET=$(echo "$INPUT" | node -e "
22
- let d='';
23
- process.stdin.on('data',c=>d+=c).on('end',()=>{
24
- try{ const p=JSON.parse(d); process.stdout.write((p.tool_input&&p.tool_input.file_path)||''); }catch(e){}
25
- });
26
- " 2>/dev/null || true)
27
- elif [ "$TOOL_NAME" = "Glob" ]; then
28
- TARGET=$(echo "$INPUT" | node -e "
29
- let d='';
30
- process.stdin.on('data',c=>d+=c).on('end',()=>{
31
- try{ const p=JSON.parse(d); const ti=p.tool_input||{}; process.stdout.write(ti.path||ti.pattern||''); }catch(e){}
32
- });
33
- " 2>/dev/null || true)
34
- else
35
- exit 0
36
- fi
37
-
38
- [ -z "$TARGET" ] && exit 0
39
-
40
- # Normalize: lowercase, forward slashes
41
- NORM=$(echo "$TARGET" | tr '\\' '/')
42
-
43
- # ── Danh sách heavy/irrelevant directories ─────────────────────────────────────
44
- BLOCKED_PATTERNS=(
45
- "node_modules/"
46
- "/node_modules"
47
- "dist/"
48
- "/dist/"
49
- "build/"
50
- "/build/"
51
- ".git/"
52
- "/__pycache__/"
53
- "/.pytest_cache/"
54
- "/.next/"
55
- "/.nuxt/"
56
- "/vendor/"
57
- "/coverage/"
58
- "/.tmp/"
59
- "/tmp/"
60
- "/.cache/"
61
- )
62
-
63
- for pattern in "${BLOCKED_PATTERNS[@]}"; do
64
- if echo "$NORM" | grep -q "$pattern"; then
65
- echo "⚡ scout-block: Skipped heavy directory [${TOOL_NAME}] $(basename "$TARGET")/" >&2
66
- echo " Path: $TARGET" >&2
67
- echo " Dùng path cụ thể hơn nếu thực sự cần đọc file này." >&2
68
- exit 2
69
- fi
70
- done
71
-
72
- # Block pattern: kết thúc bằng thư mục blocked (không có trailing slash)
73
- BLOCKED_EXACT=("node_modules" ".git" "dist" "build" "__pycache__" ".next" ".nuxt" "vendor" "coverage")
74
- BASENAME=$(basename "$NORM")
75
- for exact in "${BLOCKED_EXACT[@]}"; do
76
- if [ "$BASENAME" = "$exact" ]; then
77
- echo "⚡ scout-block: Skipped heavy directory [${TOOL_NAME}] $BASENAME/" >&2
78
- exit 2
79
- fi
80
- done
81
-
82
- exit 0
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
- [ -z "$SESSION_ID" ] && exit 0
20
-
21
- # ── Track session: chỉ chạy một lần mỗi session ───────────────────────────────
22
- SESSION_MARKER="/tmp/dw-session-${SESSION_ID}"
23
- if [ -f "$SESSION_MARKER" ]; then
24
- exit 0
25
- fi
26
- touch "$SESSION_MARKER" 2>/dev/null || true
27
-
28
- # ── Scan .dw/tasks/ tìm tasks In Progress ─────────────────────────────────────
29
- PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
30
- TASKS_DIR="$PROJECT_DIR/.dw/tasks"
31
-
32
- [ ! -d "$TASKS_DIR" ] && exit 0
33
-
34
- ACTIVE_TASKS=()
35
- ACTIVE_SUMMARIES=()
36
-
37
- while IFS= read -r progress_file; do
38
- if grep -q "Trạng thái: In Progress" "$progress_file" 2>/dev/null; then
39
- task_name=$(basename "$(dirname "$progress_file")")
40
-
41
- # Extract current subtask (dòng "In Progress" trong table)
42
- current_st=$(grep -m1 "In Progress" "$progress_file" 2>/dev/null \
43
- | grep -oP '\| ST-\d+ \| [^|]+' | sed 's/|//g' | xargs 2>/dev/null || echo "")
44
-
45
- # Extract last handoff note nếu
46
- last_handoff=$(awk '/## Handoff Notes/{found=1} found{print}' "$progress_file" 2>/dev/null \
47
- | grep -m1 "Bước tiếp theo:" | sed 's/.*Bước tiếp theo://' | xargs 2>/dev/null || echo "")
48
-
49
- ACTIVE_TASKS+=("$task_name")
50
- summary="$task_name"
51
- [ -n "$current_st" ] && summary="$summary — $current_st"
52
- [ -n "$last_handoff" ] && summary="$summary | Next: $last_handoff"
53
- ACTIVE_SUMMARIES+=("$summary")
54
- fi
55
- done < <(find "$TASKS_DIR" -name "*-progress.md" 2>/dev/null)
56
-
57
- [ ${#ACTIVE_TASKS[@]} -eq 0 ] && exit 0
58
-
59
- # ── Output context vào stdout (injected vào conversation) ─────────────────────
60
- echo ""
61
- echo "---"
62
- echo "[dw-kit session-init] Task đang in-progress:"
63
- for summary in "${ACTIVE_SUMMARIES[@]}"; do
64
- echo " $summary"
65
- done
66
- if [ ${#ACTIVE_TASKS[@]} -eq 1 ]; then
67
- echo "Context: .dw/tasks/${ACTIVE_TASKS[0]}/"
68
- else
69
- echo "Nhiều tasks đang active hỏi user task nào cần tiếp tục."
70
- fi
71
- echo "---"
72
- echo ""
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 nếu uncommitted changes hoặc task in-progress chưa update
3
- # Output ra stderr để hiện thị cho user, exit 0 để không block
4
-
5
- PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
6
- WARNINGS=()
7
-
8
- # Check uncommitted changes
9
- if git -C "$PROJECT_DIR" diff --quiet && git -C "$PROJECT_DIR" diff --cached --quiet; then
10
- : # clean
11
- else
12
- CHANGED=$(git -C "$PROJECT_DIR" diff --stat --cached && git -C "$PROJECT_DIR" diff --stat)
13
- WARNINGS+=("Uncommitted changes:"$'\n'"$CHANGED")
14
- fi
15
-
16
- # Check in-progress tasks
17
- TASKS_DIR="$PROJECT_DIR/.dw/tasks"
18
- if [ -d "$TASKS_DIR" ]; then
19
- while IFS= read -r progress_file; do
20
- if grep -q "Trạng thái: In Progress" "$progress_file" 2>/dev/null; then
21
- task_name=$(basename "$(dirname "$progress_file")")
22
- WARNINGS+=("Task in-progress chưa được update: $task_name")
23
- fi
24
- done < <(find "$TASKS_DIR" -name "*-progress.md" 2>/dev/null)
25
- fi
26
-
27
- # Print warnings
28
- if [ ${#WARNINGS[@]} -gt 0 ]; then
29
- echo "--- dw stop-check ---" >&2
30
- for w in "${WARNINGS[@]}"; do
31
- printf "⚠ %b\n" "$w" >&2
32
- done
33
- echo "---------------------" >&2
34
- fi
35
-
36
- exit 0
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