dw-kit 1.0.0 → 1.0.2
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 +1 -3
- package/.claude/rules/code-style.md +37 -37
- package/.claude/rules/commit-standards.md +37 -37
- package/.claude/settings.json +1 -1
- package/.claude/settings.local.json +2 -1
- package/.claude/skills/dw-prompt/SKILL.md +62 -0
- package/.claude/skills/dw-upgrade/SKILL.md +20 -30
- package/.dw/adapters/claude-cli/extensions/README.md +2 -2
- package/.dw/adapters/claude-cli/generated/README.md +3 -3
- package/.dw/adapters/claude-cli/overrides/README.md +4 -2
- package/CLAUDE.md +1 -0
- package/README.md +78 -122
- package/package.json +5 -5
- package/scripts/e2e-local-check.sh +1 -2
- package/src/__fixtures__/claude-cli-bug-snippet.js +15 -0
- package/src/cli.mjs +29 -5
- package/src/commands/claude-vn-fix.mjs +267 -0
- package/src/commands/prompt.mjs +125 -0
- package/src/lib/clipboard.mjs +24 -0
- package/src/lib/prompt-suggest.mjs +84 -0
- package/src/lib/update-checker.mjs +73 -0
- package/src/smoke-test.mjs +47 -11
- package/scripts/migrate-v03-to-v1.sh +0 -243
- package/scripts/upgrade.sh +0 -246
- package/setup.sh +0 -382
- package/src/commands/migrate.mjs +0 -215
package/scripts/upgrade.sh
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# scripts/upgrade.sh
|
|
3
|
-
# ⚠ DEPRECATED: Prefer `dw upgrade` from npm CLI.
|
|
4
|
-
# Upgrade dw-kit: update generated/ files, preserve overrides/ và extensions/
|
|
5
|
-
# Usage: bash scripts/upgrade.sh [--dry-run] [--layer core|platform|all]
|
|
6
|
-
|
|
7
|
-
set -euo pipefail
|
|
8
|
-
|
|
9
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
-
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
11
|
-
|
|
12
|
-
# ── Options ──────────────────────────────────────────────────────────────────
|
|
13
|
-
DRY_RUN=false
|
|
14
|
-
LAYER="all"
|
|
15
|
-
|
|
16
|
-
for arg in "$@"; do
|
|
17
|
-
case "$arg" in
|
|
18
|
-
--dry-run) DRY_RUN=true ;;
|
|
19
|
-
--layer) shift; LAYER="${1:-all}" ;;
|
|
20
|
-
--layer=*) LAYER="${arg#*=}" ;;
|
|
21
|
-
esac
|
|
22
|
-
done
|
|
23
|
-
|
|
24
|
-
# ── Helpers ───────────────────────────────────────────────────────────────────
|
|
25
|
-
log() { echo " $*"; }
|
|
26
|
-
info() { echo ""; echo "▶ $*"; }
|
|
27
|
-
warn() { echo " ⚠ $*"; }
|
|
28
|
-
ok() { echo " ✓ $*"; }
|
|
29
|
-
dry() { echo " [dry-run] $*"; }
|
|
30
|
-
|
|
31
|
-
do_copy() {
|
|
32
|
-
local src="$1" dst="$2"
|
|
33
|
-
if [ "$DRY_RUN" = true ]; then
|
|
34
|
-
dry "cp $src → $dst"
|
|
35
|
-
else
|
|
36
|
-
mkdir -p "$(dirname "$dst")"
|
|
37
|
-
cp "$src" "$dst"
|
|
38
|
-
fi
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
do_mkdir() {
|
|
42
|
-
if [ "$DRY_RUN" = true ]; then
|
|
43
|
-
dry "mkdir -p $1"
|
|
44
|
-
else
|
|
45
|
-
mkdir -p "$1"
|
|
46
|
-
fi
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
# ── Read current versions ─────────────────────────────────────────────────────
|
|
50
|
-
CONFIG_FILE="$PROJECT_ROOT/.dw/config/dw.config.yml"
|
|
51
|
-
CURRENT_CORE="unknown"
|
|
52
|
-
CURRENT_PLATFORM="unknown"
|
|
53
|
-
|
|
54
|
-
if [ -f "$CONFIG_FILE" ]; then
|
|
55
|
-
CURRENT_CORE=$(grep -m1 "core_version:" "$CONFIG_FILE" 2>/dev/null \
|
|
56
|
-
| sed 's/.*:[[:space:]]*//' | tr -d '"' | tr -d "'" | tr -d '[:space:]' || echo "unknown")
|
|
57
|
-
CURRENT_PLATFORM=$(grep -m1 "platform_version:" "$CONFIG_FILE" 2>/dev/null \
|
|
58
|
-
| sed 's/.*:[[:space:]]*//' | tr -d '"' | tr -d "'" | tr -d '[:space:]' || echo "unknown")
|
|
59
|
-
fi
|
|
60
|
-
|
|
61
|
-
TOOLKIT_CORE=$(grep -m1 "core-version:" "$PROJECT_ROOT/core/WORKFLOW.md" 2>/dev/null \
|
|
62
|
-
| sed 's/.*core-version:[[:space:]]*//' | tr -d ' -->' | head -1 || echo "1.0")
|
|
63
|
-
|
|
64
|
-
echo ""
|
|
65
|
-
echo "══════════════════════════════════════════"
|
|
66
|
-
echo " dw-kit Upgrade"
|
|
67
|
-
echo " Current: core=$CURRENT_CORE platform=$CURRENT_PLATFORM"
|
|
68
|
-
echo " Toolkit: core=$TOOLKIT_CORE"
|
|
69
|
-
if [ "$DRY_RUN" = true ]; then
|
|
70
|
-
echo " Mode: DRY RUN (no changes)"
|
|
71
|
-
fi
|
|
72
|
-
echo "══════════════════════════════════════════"
|
|
73
|
-
|
|
74
|
-
# ── Phase 1: Update generated/ ───────────────────────────────────────────────
|
|
75
|
-
info "Phase 1: Update generated files"
|
|
76
|
-
|
|
77
|
-
GENERATED="$PROJECT_ROOT/.dw/adapters/claude-cli/generated"
|
|
78
|
-
CLAUDE_DIR="$PROJECT_ROOT/.claude"
|
|
79
|
-
|
|
80
|
-
if [ "$LAYER" = "all" ] || [ "$LAYER" = "platform" ]; then
|
|
81
|
-
# Copy generated skills → .claude/skills (skip if override exists)
|
|
82
|
-
if [ -d "$GENERATED/skills" ]; then
|
|
83
|
-
for skill_dir in "$GENERATED/skills"/*/; do
|
|
84
|
-
skill_name=$(basename "$skill_dir")
|
|
85
|
-
override="$PROJECT_ROOT/.dw/adapters/claude-cli/overrides/skills/$skill_name"
|
|
86
|
-
|
|
87
|
-
if [ -d "$override" ]; then
|
|
88
|
-
warn "Skill '$skill_name': override exists → keeping override"
|
|
89
|
-
# Copy override instead of generated
|
|
90
|
-
for f in "$override"/*; do
|
|
91
|
-
[ -f "$f" ] && do_copy "$f" "$CLAUDE_DIR/skills/$skill_name/$(basename "$f")"
|
|
92
|
-
done
|
|
93
|
-
else
|
|
94
|
-
for f in "$skill_dir"*; do
|
|
95
|
-
[ -f "$f" ] && do_copy "$f" "$CLAUDE_DIR/skills/$skill_name/$(basename "$f")"
|
|
96
|
-
done
|
|
97
|
-
ok "Skill '$skill_name': updated"
|
|
98
|
-
fi
|
|
99
|
-
done
|
|
100
|
-
fi
|
|
101
|
-
|
|
102
|
-
# Copy generated agents → .claude/agents (skip if override exists)
|
|
103
|
-
if [ -d "$GENERATED/agents" ]; then
|
|
104
|
-
for agent_file in "$GENERATED/agents"/*.md; do
|
|
105
|
-
[ -f "$agent_file" ] || continue
|
|
106
|
-
agent_name=$(basename "$agent_file")
|
|
107
|
-
override="$PROJECT_ROOT/.dw/adapters/claude-cli/overrides/agents/$agent_name"
|
|
108
|
-
|
|
109
|
-
if [ -f "$override" ]; then
|
|
110
|
-
warn "Agent '$agent_name': override exists → keeping override"
|
|
111
|
-
do_copy "$override" "$CLAUDE_DIR/agents/$agent_name"
|
|
112
|
-
else
|
|
113
|
-
do_copy "$agent_file" "$CLAUDE_DIR/agents/$agent_name"
|
|
114
|
-
ok "Agent '$agent_name': updated"
|
|
115
|
-
fi
|
|
116
|
-
done
|
|
117
|
-
fi
|
|
118
|
-
fi
|
|
119
|
-
|
|
120
|
-
# ── Phase 2: Copy extensions/ ────────────────────────────────────────────────
|
|
121
|
-
info "Phase 2: Copy extensions (team-specific skills)"
|
|
122
|
-
|
|
123
|
-
EXTENSIONS="$PROJECT_ROOT/.dw/adapters/claude-cli/extensions"
|
|
124
|
-
if [ -d "$EXTENSIONS" ]; then
|
|
125
|
-
ext_count=0
|
|
126
|
-
for ext_dir in "$EXTENSIONS"/*/; do
|
|
127
|
-
[ -d "$ext_dir" ] || continue
|
|
128
|
-
ext_name=$(basename "$ext_dir")
|
|
129
|
-
[ "$ext_name" = ".gitkeep" ] && continue
|
|
130
|
-
|
|
131
|
-
do_mkdir "$CLAUDE_DIR/skills/$ext_name"
|
|
132
|
-
for f in "$ext_dir"*; do
|
|
133
|
-
[ -f "$f" ] && do_copy "$f" "$CLAUDE_DIR/skills/$ext_name/$(basename "$f")"
|
|
134
|
-
done
|
|
135
|
-
ok "Extension '$ext_name': installed"
|
|
136
|
-
ext_count=$((ext_count + 1))
|
|
137
|
-
done
|
|
138
|
-
[ $ext_count -eq 0 ] && log "No extensions found"
|
|
139
|
-
fi
|
|
140
|
-
|
|
141
|
-
# ── Phase 3: Merge settings.json ─────────────────────────────────────────────
|
|
142
|
-
info "Phase 3: Merge settings.json"
|
|
143
|
-
|
|
144
|
-
SETTINGS_TEMPLATE="$GENERATED/settings.json"
|
|
145
|
-
SETTINGS_TARGET="$CLAUDE_DIR/settings.json"
|
|
146
|
-
|
|
147
|
-
if [ -f "$SETTINGS_TEMPLATE" ] && [ -f "$SETTINGS_TARGET" ]; then
|
|
148
|
-
if command -v python3 &>/dev/null; then
|
|
149
|
-
if [ "$DRY_RUN" = true ]; then
|
|
150
|
-
dry "Merge settings.json (python3)"
|
|
151
|
-
else
|
|
152
|
-
python3 - "$SETTINGS_TEMPLATE" "$SETTINGS_TARGET" <<'PYEOF'
|
|
153
|
-
import json, sys
|
|
154
|
-
|
|
155
|
-
template_path = sys.argv[1]
|
|
156
|
-
target_path = sys.argv[2]
|
|
157
|
-
|
|
158
|
-
with open(template_path) as f:
|
|
159
|
-
template = json.load(f)
|
|
160
|
-
with open(target_path) as f:
|
|
161
|
-
target = json.load(f)
|
|
162
|
-
|
|
163
|
-
def deep_merge(base, override):
|
|
164
|
-
"""Override values from base with override, but preserve keys not in template."""
|
|
165
|
-
result = dict(base)
|
|
166
|
-
for k, v in override.items():
|
|
167
|
-
if k in result and isinstance(result[k], dict) and isinstance(v, dict):
|
|
168
|
-
result[k] = deep_merge(result[k], v)
|
|
169
|
-
else:
|
|
170
|
-
result[k] = v
|
|
171
|
-
return result
|
|
172
|
-
|
|
173
|
-
merged = deep_merge(template, target)
|
|
174
|
-
|
|
175
|
-
with open(target_path, 'w') as f:
|
|
176
|
-
json.dump(merged, f, indent=2, ensure_ascii=False)
|
|
177
|
-
f.write('\n')
|
|
178
|
-
PYEOF
|
|
179
|
-
ok "settings.json: merged"
|
|
180
|
-
fi
|
|
181
|
-
else
|
|
182
|
-
warn "python3 not found — skipping settings.json merge. Manual merge may be needed."
|
|
183
|
-
fi
|
|
184
|
-
elif [ -f "$SETTINGS_TEMPLATE" ] && [ ! -f "$SETTINGS_TARGET" ]; then
|
|
185
|
-
do_copy "$SETTINGS_TEMPLATE" "$SETTINGS_TARGET"
|
|
186
|
-
ok "settings.json: created from template"
|
|
187
|
-
fi
|
|
188
|
-
|
|
189
|
-
# ── Phase 4: Update version in config ────────────────────────────────────────
|
|
190
|
-
info "Phase 4: Update version tracking"
|
|
191
|
-
|
|
192
|
-
if [ "$DRY_RUN" = false ] && [ -f "$CONFIG_FILE" ]; then
|
|
193
|
-
TODAY=$(date +%Y-%m-%d)
|
|
194
|
-
if command -v python3 &>/dev/null; then
|
|
195
|
-
python3 - "$CONFIG_FILE" "$TOOLKIT_CORE" "$TODAY" <<'PYEOF'
|
|
196
|
-
import sys, re
|
|
197
|
-
|
|
198
|
-
config_path = sys.argv[1]
|
|
199
|
-
new_core = sys.argv[2]
|
|
200
|
-
today = sys.argv[3]
|
|
201
|
-
|
|
202
|
-
with open(config_path) as f:
|
|
203
|
-
content = f.read()
|
|
204
|
-
|
|
205
|
-
content = re.sub(r'(core_version:\s*)["\']?[\d.]+["\']?', f'\\g<1>"{new_core}"', content)
|
|
206
|
-
content = re.sub(r'(last_upgrade:\s*)["\']?[\d-]+["\']?', f'\\g<1>"{today}"', content)
|
|
207
|
-
|
|
208
|
-
with open(config_path, 'w') as f:
|
|
209
|
-
f.write(content)
|
|
210
|
-
PYEOF
|
|
211
|
-
ok "Config version updated: core=$TOOLKIT_CORE, last_upgrade=$TODAY"
|
|
212
|
-
fi
|
|
213
|
-
fi
|
|
214
|
-
|
|
215
|
-
# ── Phase 5: Check for CI/CD references needing manual update ────────────────
|
|
216
|
-
info "Phase 5: Check backward compatibility"
|
|
217
|
-
|
|
218
|
-
OLD_CONFIG="$PROJECT_ROOT/.dw/config/dw.config.yml"
|
|
219
|
-
if [ -f "$OLD_CONFIG" ]; then
|
|
220
|
-
if [ -L "$OLD_CONFIG" ]; then
|
|
221
|
-
ok "config/dw.config.yml: symlink intact (backward compat)"
|
|
222
|
-
else
|
|
223
|
-
warn "config/dw.config.yml exists as real file. Run scripts/migrate-v03-to-v1.sh first."
|
|
224
|
-
fi
|
|
225
|
-
fi
|
|
226
|
-
|
|
227
|
-
# Check for CI references
|
|
228
|
-
for ci_file in ".github/workflows/"*.yml ".gitlab-ci.yml" "Makefile" ".circleci/config.yml"; do
|
|
229
|
-
full_path="$PROJECT_ROOT/$ci_file"
|
|
230
|
-
if [ -f "$full_path" ] && grep -q ".dw/.dw/config/dw.config.yml" "$full_path" 2>/dev/null; then
|
|
231
|
-
warn "CI file '$ci_file' references config/dw.config.yml — update manually"
|
|
232
|
-
fi
|
|
233
|
-
done
|
|
234
|
-
|
|
235
|
-
# ── Summary ───────────────────────────────────────────────────────────────────
|
|
236
|
-
echo ""
|
|
237
|
-
echo "══════════════════════════════════════════"
|
|
238
|
-
if [ "$DRY_RUN" = true ]; then
|
|
239
|
-
echo " DRY RUN complete. No changes made."
|
|
240
|
-
echo " Run without --dry-run to apply."
|
|
241
|
-
else
|
|
242
|
-
echo " Upgrade complete!"
|
|
243
|
-
echo " Core version: $TOOLKIT_CORE"
|
|
244
|
-
fi
|
|
245
|
-
echo "══════════════════════════════════════════"
|
|
246
|
-
echo ""
|
package/setup.sh
DELETED
|
@@ -1,382 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# =============================================================================
|
|
3
|
-
# dw-kit — Interactive Setup Wizard (LEGACY)
|
|
4
|
-
# =============================================================================
|
|
5
|
-
# ⚠ DEPRECATED: Use `dw init` instead (npm install -g dw-kit)
|
|
6
|
-
#
|
|
7
|
-
# npm install -g dw-kit
|
|
8
|
-
# dw init
|
|
9
|
-
#
|
|
10
|
-
# This script is kept for environments without Node.js.
|
|
11
|
-
# New features will only be added to the `dw` CLI.
|
|
12
|
-
# =============================================================================
|
|
13
|
-
# Chạy từ root của dự án của bạn. Hỏi 4 câu, tự cấu hình mọi thứ.
|
|
14
|
-
# Thời gian: ~1-2 phút
|
|
15
|
-
#
|
|
16
|
-
# Usage:
|
|
17
|
-
# bash .dw-module/setup.sh
|
|
18
|
-
#
|
|
19
|
-
# Silent mode (CI/scripted):
|
|
20
|
-
# DW_NAME="my-app" DW_LEVEL=2 DW_ROLES="dev,techlead" DW_LANG="vi" \
|
|
21
|
-
# bash .dw-module/setup.sh --silent
|
|
22
|
-
# =============================================================================
|
|
23
|
-
|
|
24
|
-
set -e
|
|
25
|
-
|
|
26
|
-
TOOLKIT_DIR=".dw-module"
|
|
27
|
-
SILENT="${1:-}"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
CYAN='\033[0;36m'
|
|
31
|
-
GREEN='\033[0;32m'
|
|
32
|
-
YELLOW='\033[1;33m'
|
|
33
|
-
BOLD='\033[1m'
|
|
34
|
-
NC='\033[0m'
|
|
35
|
-
|
|
36
|
-
# Kiểm tra submodule
|
|
37
|
-
if [ ! -d "$TOOLKIT_DIR/.claude" ]; then
|
|
38
|
-
echo "Toolkit chưa được add. Chạy trước:"
|
|
39
|
-
echo " git submodule add https://github.com/dv-workflow/dv-workflow.git .dw-module"
|
|
40
|
-
exit 1
|
|
41
|
-
fi
|
|
42
|
-
|
|
43
|
-
clear
|
|
44
|
-
echo -e "${CYAN}${BOLD}"
|
|
45
|
-
echo " ██████╗ ██╗ ██╗ ██╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗██╗ ██████╗ ██╗ ██╗"
|
|
46
|
-
echo " ██╔══██╗██║ ██║ ██║ ██║██╔═══██╗██╔══██╗██║ ██╔╝██╔════╝██║ ██╔═══██╗██║ ██║"
|
|
47
|
-
echo " ██║ ██║██║ ██║ ██║ █╗ ██║██║ ██║██████╔╝█████╔╝ █████╗ ██║ ██║ ██║██║ █╗ ██║"
|
|
48
|
-
echo " ██║ ██║╚██╗ ██╔╝ ██║███╗██║██║ ██║██╔══██╗██╔═██╗ ██╔══╝ ██║ ██║ ██║██║███╗██║"
|
|
49
|
-
echo " ██████╔╝ ╚████╔╝ ╚███╔███╔╝╚██████╔╝██║ ██║██║ ██╗██║ ███████╗╚██████╔╝╚███╔███╔╝"
|
|
50
|
-
echo " ╚═════╝ ╚═══╝ ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚══════╝ ╚═════╝ ╚══╝╚══╝"
|
|
51
|
-
echo -e "${NC}"
|
|
52
|
-
echo -e "${CYAN} Setup Wizard v0.3 — ~1 phút${NC}"
|
|
53
|
-
echo ""
|
|
54
|
-
|
|
55
|
-
# =============================================================================
|
|
56
|
-
# BƯỚC 1: Đọc từ env (silent mode) hoặc hỏi user (interactive mode)
|
|
57
|
-
# =============================================================================
|
|
58
|
-
|
|
59
|
-
if [ -n "$SILENT" ] && [ "$SILENT" = "--silent" ]; then
|
|
60
|
-
# Silent mode: đọc từ environment variables
|
|
61
|
-
PROJECT_NAME="${DW_NAME:-my-project}"
|
|
62
|
-
LEVEL="${DW_LEVEL:-2}"
|
|
63
|
-
ROLES_RAW="${DW_ROLES:-dev,techlead}"
|
|
64
|
-
LANG="${DW_LANG:-vi}"
|
|
65
|
-
else
|
|
66
|
-
# ---- Câu 1: Project name ----
|
|
67
|
-
echo -e "${BOLD}[Project] Tên project?${NC}"
|
|
68
|
-
read -r -p " > " PROJECT_NAME
|
|
69
|
-
PROJECT_NAME="${PROJECT_NAME:-my-project}"
|
|
70
|
-
echo ""
|
|
71
|
-
|
|
72
|
-
# ---- Câu 2: Level ----
|
|
73
|
-
echo -e "${BOLD}[Level] Chọn level workflow:${NC}"
|
|
74
|
-
echo " 1 = Lite — research → execute → commit (solo dev, hotfix nhanh)"
|
|
75
|
-
echo " 2 = Standard — research → plan → execute → review (team, feature mới) [default]"
|
|
76
|
-
echo " 3 = Enterprise — full workflow + living docs + metrics (team lớn, audit trail)"
|
|
77
|
-
read -r -p " Level [1/2/3, Enter = 2]: " LEVEL
|
|
78
|
-
LEVEL="${LEVEL:-2}"
|
|
79
|
-
echo ""
|
|
80
|
-
|
|
81
|
-
# ---- Câu 3: Roles ----
|
|
82
|
-
echo -e "${BOLD}[Role] Team có những roles nào? (Dev luôn được bật)${NC}"
|
|
83
|
-
echo " Nhập số cách nhau bởi dấu phẩy, hoặc Enter để chọn dev+techlead:"
|
|
84
|
-
echo " 1 = Dev (luôn bật)"
|
|
85
|
-
echo " 2 = Tech Lead — architecture review, approve plans"
|
|
86
|
-
echo " 3 = BA — requirements, user stories"
|
|
87
|
-
echo " 4 = QC — test plans, bug reports"
|
|
88
|
-
echo " 5 = PM — dashboard, metrics"
|
|
89
|
-
read -r -p " Roles [1,2,...]: " ROLES_INPUT
|
|
90
|
-
ROLES_INPUT="${ROLES_INPUT:-1,2}"
|
|
91
|
-
echo ""
|
|
92
|
-
|
|
93
|
-
# ---- Câu 4: Language ----
|
|
94
|
-
echo -e "${BOLD}[Lang] Ngôn ngữ docs output?${NC}"
|
|
95
|
-
echo " vi = Tiếng Việt [default]"
|
|
96
|
-
echo " en = English"
|
|
97
|
-
read -r -p " Language [vi/en]: " LANG
|
|
98
|
-
LANG="${LANG:-vi}"
|
|
99
|
-
echo ""
|
|
100
|
-
fi
|
|
101
|
-
|
|
102
|
-
# =============================================================================
|
|
103
|
-
# BUILD roles list từ input
|
|
104
|
-
# =============================================================================
|
|
105
|
-
|
|
106
|
-
build_roles() {
|
|
107
|
-
local input="$1"
|
|
108
|
-
local roles=" - dev"
|
|
109
|
-
# Nếu input dạng "dev,techlead" (silent mode)
|
|
110
|
-
if echo "$input" | grep -q "[a-z]"; then
|
|
111
|
-
echo "$input" | tr ',' '\n' | while read -r r; do
|
|
112
|
-
r=$(echo "$r" | tr -d ' ')
|
|
113
|
-
[ "$r" != "dev" ] && echo " - $r"
|
|
114
|
-
done
|
|
115
|
-
return
|
|
116
|
-
fi
|
|
117
|
-
# Nếu input dạng "1,2,3" (interactive mode)
|
|
118
|
-
echo "$input" | tr ',' '\n' | while read -r n; do
|
|
119
|
-
n=$(echo "$n" | tr -d ' ')
|
|
120
|
-
case "$n" in
|
|
121
|
-
2) echo " - techlead" ;;
|
|
122
|
-
3) echo " - ba" ;;
|
|
123
|
-
4) echo " - qc" ;;
|
|
124
|
-
5) echo " - pm" ;;
|
|
125
|
-
esac
|
|
126
|
-
done
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
HAS_TL=false; HAS_BA=false; HAS_QC=false; HAS_PM=false
|
|
130
|
-
|
|
131
|
-
check_role() {
|
|
132
|
-
local input="$1"
|
|
133
|
-
if echo "$input" | grep -q "techlead\|,2\b\|^2\b"; then HAS_TL=true; fi
|
|
134
|
-
if echo "$input" | grep -q "ba\b\|,3\b\|^3\b"; then HAS_BA=true; fi
|
|
135
|
-
if echo "$input" | grep -q "qc\b\|,4\b\|^4\b"; then HAS_QC=true; fi
|
|
136
|
-
if echo "$input" | grep -q "pm\b\|,5\b\|^5\b"; then HAS_PM=true; fi
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
ROLES_RAW="${ROLES_RAW:-$ROLES_INPUT}"
|
|
140
|
-
check_role "$ROLES_RAW"
|
|
141
|
-
|
|
142
|
-
# =============================================================================
|
|
143
|
-
# APPLY: Copy files + Write config
|
|
144
|
-
# =============================================================================
|
|
145
|
-
|
|
146
|
-
echo -e "${CYAN}Đang setup...${NC}"
|
|
147
|
-
|
|
148
|
-
# Copy .claude/
|
|
149
|
-
if [ -d ".claude" ]; then
|
|
150
|
-
cp -rn "$TOOLKIT_DIR/.claude/skills" ".claude/" 2>/dev/null || true
|
|
151
|
-
cp -rn "$TOOLKIT_DIR/.claude/agents" ".claude/" 2>/dev/null || true
|
|
152
|
-
cp -rn "$TOOLKIT_DIR/.claude/rules" ".claude/" 2>/dev/null || true
|
|
153
|
-
cp -rn "$TOOLKIT_DIR/.claude/hooks" ".claude/" 2>/dev/null || true
|
|
154
|
-
cp -rn "$TOOLKIT_DIR/.claude/templates" ".claude/" 2>/dev/null || true
|
|
155
|
-
[ ! -f ".claude/settings.json" ] && cp "$TOOLKIT_DIR/.claude/settings.json" ".claude/"
|
|
156
|
-
else
|
|
157
|
-
cp -r "$TOOLKIT_DIR/.claude" "./"
|
|
158
|
-
fi
|
|
159
|
-
|
|
160
|
-
# Chọn base config theo level
|
|
161
|
-
if [ "$LEVEL" = "3" ]; then
|
|
162
|
-
BASE_CONFIG="$TOOLKIT_DIR/project-templates/enterprise/.dw/config/dw.config.yml"
|
|
163
|
-
elif [ "$LEVEL" = "1" ]; then
|
|
164
|
-
BASE_CONFIG="$TOOLKIT_DIR/project-templates/old-maintenance/.dw/config/dw.config.yml"
|
|
165
|
-
else
|
|
166
|
-
BASE_CONFIG="$TOOLKIT_DIR/project-templates/new-product/.dw/config/dw.config.yml"
|
|
167
|
-
fi
|
|
168
|
-
|
|
169
|
-
# Ghi config với giá trị user đã chọn
|
|
170
|
-
if [ ! -f ".dw/.dw/config/dw.config.yml" ]; then
|
|
171
|
-
ROLES_YAML=" roles:\n - dev"
|
|
172
|
-
$HAS_TL && ROLES_YAML="$ROLES_YAML\n - techlead"
|
|
173
|
-
$HAS_BA && ROLES_YAML="$ROLES_YAML\n - ba"
|
|
174
|
-
$HAS_QC && ROLES_YAML="$ROLES_YAML\n - qc"
|
|
175
|
-
$HAS_PM && ROLES_YAML="$ROLES_YAML\n - pm"
|
|
176
|
-
|
|
177
|
-
sed \
|
|
178
|
-
-e "s|name: \"your.*\"|name: \"$PROJECT_NAME\"|" \
|
|
179
|
-
-e "s|name: \"your-enterprise-project\"|name: \"$PROJECT_NAME\"|" \
|
|
180
|
-
-e "s|language: \"vi\"|language: \"$LANG\"|" \
|
|
181
|
-
"$BASE_CONFIG" > ".dw/.dw/config/dw.config.yml"
|
|
182
|
-
|
|
183
|
-
# Inject roles (replace toàn bộ roles section) — pure awk, no Python needed
|
|
184
|
-
ROLES_LINES=" - dev"
|
|
185
|
-
$HAS_TL && ROLES_LINES="${ROLES_LINES}| - techlead"
|
|
186
|
-
$HAS_BA && ROLES_LINES="${ROLES_LINES}| - ba"
|
|
187
|
-
$HAS_QC && ROLES_LINES="${ROLES_LINES}| - qc"
|
|
188
|
-
$HAS_PM && ROLES_LINES="${ROLES_LINES}| - pm"
|
|
189
|
-
|
|
190
|
-
awk -v roles="$ROLES_LINES" '
|
|
191
|
-
/^ roles:/ {
|
|
192
|
-
print " roles:"
|
|
193
|
-
n = split(roles, a, "|")
|
|
194
|
-
for (i = 1; i <= n; i++) print a[i]
|
|
195
|
-
in_roles = 1; next
|
|
196
|
-
}
|
|
197
|
-
in_roles && /^ -/ { next }
|
|
198
|
-
{ in_roles = 0; print }
|
|
199
|
-
' config/dw.config.yml > config/dw.config.yml.tmp \
|
|
200
|
-
&& mv config/dw.config.yml.tmp config/dw.config.yml
|
|
201
|
-
fi
|
|
202
|
-
|
|
203
|
-
# Tạo CLAUDE.md
|
|
204
|
-
if [ ! -f "CLAUDE.md" ]; then
|
|
205
|
-
cp "$TOOLKIT_DIR/CLAUDE.md" .
|
|
206
|
-
cat >> CLAUDE.md << 'SECTION'
|
|
207
|
-
|
|
208
|
-
---
|
|
209
|
-
|
|
210
|
-
## Tech Stack
|
|
211
|
-
|
|
212
|
-
<!-- Cập nhật phần này với stack thực tế của project -->
|
|
213
|
-
- Framework: [e.g. NestJS / Django / Laravel / Next.js]
|
|
214
|
-
- Database: [e.g. PostgreSQL / MySQL / MongoDB]
|
|
215
|
-
- Testing: [e.g. Jest / Pytest / PHPUnit]
|
|
216
|
-
|
|
217
|
-
## Project-Specific Rules
|
|
218
|
-
|
|
219
|
-
<!-- Thêm rules đặc thù của project -->
|
|
220
|
-
- [Rule 1]
|
|
221
|
-
SECTION
|
|
222
|
-
fi
|
|
223
|
-
|
|
224
|
-
# Tạo runtime directories (gom vào .dw/ để không pollute root)
|
|
225
|
-
mkdir -p .dw/tasks .dw/docs .dw/metrics .dw/reports
|
|
226
|
-
|
|
227
|
-
# =============================================================================
|
|
228
|
-
# V2: Generate config/dw.config.yml và settings.json từ MCP config
|
|
229
|
-
# =============================================================================
|
|
230
|
-
|
|
231
|
-
# Copy config mới nếu chưa có (v1 structure)
|
|
232
|
-
if [ ! -f ".dw/.dw/config/dw.config.yml" ] && [ -f "$TOOLKIT_DIR/.dw/config/dw.config.yml" ]; then
|
|
233
|
-
mkdir -p config/presets
|
|
234
|
-
cp "$TOOLKIT_DIR/.dw/config/dw.config.yml" ".dw/.dw/config/dw.config.yml"
|
|
235
|
-
cp "$TOOLKIT_DIR/config/config.schema.json" ".dw/config/config.schema.json" 2>/dev/null || true
|
|
236
|
-
cp "$TOOLKIT_DIR/config/presets/"* "config/presets/" 2>/dev/null || true
|
|
237
|
-
fi
|
|
238
|
-
|
|
239
|
-
# Generate settings.json từ claude.mcp config (nếu có python3 + có mcp config)
|
|
240
|
-
generate_mcp_settings() {
|
|
241
|
-
local config_file="${1:-config/dw.config.yml}"
|
|
242
|
-
[ ! -f "$config_file" ] && return 0
|
|
243
|
-
[ ! -f ".claude/settings.json" ] && return 0
|
|
244
|
-
command -v python3 &>/dev/null || return 0
|
|
245
|
-
|
|
246
|
-
python3 - "$config_file" ".claude/settings.json" <<'PYEOF' 2>/dev/null || true
|
|
247
|
-
import sys, json, re
|
|
248
|
-
|
|
249
|
-
config_path = sys.argv[1]
|
|
250
|
-
settings_path = sys.argv[2]
|
|
251
|
-
|
|
252
|
-
with open(config_path) as f:
|
|
253
|
-
content = f.read()
|
|
254
|
-
|
|
255
|
-
# Extract MCP servers từ YAML (simple parser — không cần PyYAML)
|
|
256
|
-
mcp_servers = {}
|
|
257
|
-
in_mcp = False
|
|
258
|
-
current_server = None
|
|
259
|
-
|
|
260
|
-
for line in content.split('\n'):
|
|
261
|
-
stripped = line.rstrip()
|
|
262
|
-
# Detect mcp: block
|
|
263
|
-
if re.match(r'\s*mcp:\s*$', stripped):
|
|
264
|
-
in_mcp = True
|
|
265
|
-
continue
|
|
266
|
-
if in_mcp:
|
|
267
|
-
# New top-level key = end of mcp block
|
|
268
|
-
if re.match(r'^[a-z_]', stripped) or re.match(r'^_toolkit', stripped):
|
|
269
|
-
in_mcp = False
|
|
270
|
-
continue
|
|
271
|
-
# Server entry: - name: "..."
|
|
272
|
-
m = re.match(r'\s*-\s*name:\s*["\']?([^"\']+)["\']?', stripped)
|
|
273
|
-
if m:
|
|
274
|
-
current_server = m.group(1).strip()
|
|
275
|
-
mcp_servers[current_server] = {}
|
|
276
|
-
continue
|
|
277
|
-
if current_server:
|
|
278
|
-
# command:
|
|
279
|
-
m = re.match(r'\s+command:\s*["\']?([^"\']+)["\']?', stripped)
|
|
280
|
-
if m:
|
|
281
|
-
mcp_servers[current_server]['command'] = m.group(1).strip()
|
|
282
|
-
# args: (array — simplified)
|
|
283
|
-
m = re.match(r'\s+args:\s*\[([^\]]*)\]', stripped)
|
|
284
|
-
if m:
|
|
285
|
-
args = [a.strip().strip('"').strip("'") for a in m.group(1).split(',') if a.strip()]
|
|
286
|
-
mcp_servers[current_server]['args'] = args
|
|
287
|
-
|
|
288
|
-
if not mcp_servers:
|
|
289
|
-
sys.exit(0)
|
|
290
|
-
|
|
291
|
-
# Update settings.json
|
|
292
|
-
with open(settings_path) as f:
|
|
293
|
-
settings = json.load(f)
|
|
294
|
-
|
|
295
|
-
settings['mcpServers'] = {}
|
|
296
|
-
for name, server in mcp_servers.items():
|
|
297
|
-
settings['mcpServers'][name] = {
|
|
298
|
-
'command': server.get('command', ''),
|
|
299
|
-
'args': server.get('args', [])
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
with open(settings_path, 'w') as f:
|
|
303
|
-
json.dump(settings, f, indent=2, ensure_ascii=False)
|
|
304
|
-
f.write('\n')
|
|
305
|
-
|
|
306
|
-
print(f" MCP servers configured: {', '.join(mcp_servers.keys())}")
|
|
307
|
-
PYEOF
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
generate_mcp_settings ".dw/.dw/config/dw.config.yml"
|
|
311
|
-
|
|
312
|
-
# Validate config nếu có python3 và jsonschema
|
|
313
|
-
if command -v python3 &>/dev/null && [ -f ".dw/.dw/config/dw.config.yml" ] && [ -f ".dw/config/config.schema.json" ]; then
|
|
314
|
-
python3 -c "import jsonschema" 2>/dev/null && \
|
|
315
|
-
python3 - ".dw/.dw/config/dw.config.yml" ".dw/config/config.schema.json" <<'PYEOF' 2>/dev/null || true
|
|
316
|
-
import sys, json
|
|
317
|
-
try:
|
|
318
|
-
import yaml
|
|
319
|
-
with open(sys.argv[1]) as f:
|
|
320
|
-
config = yaml.safe_load(f)
|
|
321
|
-
import jsonschema
|
|
322
|
-
with open(sys.argv[2]) as f:
|
|
323
|
-
schema = json.load(f)
|
|
324
|
-
jsonschema.validate(config, schema)
|
|
325
|
-
print(" config/dw.config.yml: valid")
|
|
326
|
-
except jsonschema.ValidationError as e:
|
|
327
|
-
print(f" ⚠ Config validation warning: {e.message}")
|
|
328
|
-
except Exception:
|
|
329
|
-
pass
|
|
330
|
-
PYEOF
|
|
331
|
-
fi
|
|
332
|
-
|
|
333
|
-
# Gitignore
|
|
334
|
-
if [ -f ".gitignore" ]; then
|
|
335
|
-
if ! grep -q ".dw/metrics" .gitignore; then
|
|
336
|
-
printf "\n# dw-kit\n.dw/metrics/\n.dw/reports/\nCLAUDE.local.md\n" >> .gitignore
|
|
337
|
-
fi
|
|
338
|
-
else
|
|
339
|
-
cp "$TOOLKIT_DIR/.gitignore" .
|
|
340
|
-
fi
|
|
341
|
-
|
|
342
|
-
# =============================================================================
|
|
343
|
-
# SUMMARY
|
|
344
|
-
# =============================================================================
|
|
345
|
-
|
|
346
|
-
clear
|
|
347
|
-
echo -e "${GREEN}${BOLD}"
|
|
348
|
-
echo " ✅ Setup hoàn tất!"
|
|
349
|
-
echo -e "${NC}"
|
|
350
|
-
echo " Project : $PROJECT_NAME"
|
|
351
|
-
echo " Level : $LEVEL $([ "$LEVEL" = "1" ] && echo "(Lite)" || ([ "$LEVEL" = "2" ] && echo "(Standard)" || echo "(Enterprise — Level 3 beta)"))"
|
|
352
|
-
echo " Language: $LANG"
|
|
353
|
-
printf " Roles : dev"
|
|
354
|
-
$HAS_TL && printf ", techlead"
|
|
355
|
-
$HAS_BA && printf ", ba"
|
|
356
|
-
$HAS_QC && printf ", qc"
|
|
357
|
-
$HAS_PM && printf ", pm"
|
|
358
|
-
echo ""
|
|
359
|
-
echo ""
|
|
360
|
-
echo " Files tạo:"
|
|
361
|
-
echo " .claude/ — 22 skills, agents, rules, hooks, templates"
|
|
362
|
-
echo " config/dw.config.yml"
|
|
363
|
-
echo " CLAUDE.md"
|
|
364
|
-
echo " .dw/tasks/ .dw/docs/ .dw/metrics/ .dw/reports/"
|
|
365
|
-
echo ""
|
|
366
|
-
echo -e "${CYAN} Skills đã bật:${NC}"
|
|
367
|
-
echo " /dw-task-init /dw-research /dw-execute /dw-commit /dw-debug /dw-handoff"
|
|
368
|
-
[ "$LEVEL" -ge 2 ] && echo " /dw-plan /dw-review /dw-estimate /dw-log-work"
|
|
369
|
-
[ "$LEVEL" -ge 3 ] && echo " /dw-docs-update /dw-dashboard /dw-sprint-review"
|
|
370
|
-
$HAS_TL && echo " /dw-arch-review (Tech Lead)"
|
|
371
|
-
$HAS_BA && echo " /dw-requirements (BA)"
|
|
372
|
-
$HAS_QC && echo " /dw-test-plan (QC)"
|
|
373
|
-
$HAS_PM && echo " /dw-dashboard (PM)"
|
|
374
|
-
echo ""
|
|
375
|
-
echo -e "${YELLOW} Bước tiếp theo:${NC}"
|
|
376
|
-
echo " 1. Mở Claude Code trong thư mục này"
|
|
377
|
-
echo " 2. Cập nhật Tech Stack trong CLAUDE.md (tuỳ chọn nhưng nên làm)"
|
|
378
|
-
echo " 3. Chạy: /dw-task-init [tên-feature-đầu-tiên]"
|
|
379
|
-
echo ""
|
|
380
|
-
echo " Docs: .dw-module/docs/README.md"
|
|
381
|
-
echo " Cheatsheet: .dw-module/docs/cheatsheet.md"
|
|
382
|
-
echo ""
|