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.
@@ -0,0 +1,125 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { load as yamlLoad } from 'js-yaml';
4
+ import { header, info, ok, warn, err, log } from '../lib/ui.mjs';
5
+ import { copyToClipboard } from '../lib/clipboard.mjs';
6
+ import { getSuggestions, isVague, expandTemplate } from '../lib/prompt-suggest.mjs';
7
+
8
+ export async function promptCommand(opts) {
9
+ header('dw-kit Prompt Builder');
10
+
11
+ const adapter = readAdapter();
12
+
13
+ // Non-interactive mode: --text <text>
14
+ if (opts.text !== undefined) {
15
+ if (!opts.text.trim()) {
16
+ err('--text cannot be empty.');
17
+ process.exit(1);
18
+ }
19
+ const result = await buildPrompt(opts.text, { interactive: false });
20
+ outputResult(result, adapter);
21
+ return;
22
+ }
23
+
24
+ // Interactive mode
25
+ const result = await buildPrompt('', { interactive: true });
26
+ outputResult(result, adapter);
27
+ }
28
+
29
+ async function buildPrompt(initialText, { interactive }) {
30
+ let description = initialText;
31
+
32
+ if (interactive) {
33
+ description = await runAutocompleteStep();
34
+ if (!description.trim()) {
35
+ err('No description provided.');
36
+ process.exit(1);
37
+ }
38
+ }
39
+
40
+ let area = '';
41
+ let outcome = '';
42
+
43
+ if (isVague(description)) {
44
+ if (interactive) {
45
+ info('Description seems short — a couple of quick questions (Enter to skip):');
46
+ ({ area, outcome } = await runWizardStep());
47
+ }
48
+ // In non-interactive mode: expand with just the description (no wizard data)
49
+ }
50
+
51
+ return expandTemplate(description, { area, outcome });
52
+ }
53
+
54
+ async function runAutocompleteStep() {
55
+ const { AutoComplete } = await import('enquirer');
56
+ const suggestions = getSuggestions(process.cwd());
57
+
58
+ const prompt = new AutoComplete({
59
+ name: 'description',
60
+ message: 'Describe your task:',
61
+ limit: 7,
62
+ choices: suggestions.length ? suggestions : ['(no suggestions — type your task)'],
63
+ suggest(typed, choices) {
64
+ const lower = typed.toLowerCase();
65
+ const filtered = choices.filter((c) => c.message.toLowerCase().includes(lower));
66
+ // Always offer the raw typed text as first selectable option
67
+ if (typed && !filtered.find((c) => c.message === typed)) {
68
+ return [{ message: typed, value: typed }, ...filtered];
69
+ }
70
+ return filtered.length ? filtered : [{ message: typed || '(empty)', value: typed }];
71
+ },
72
+ });
73
+
74
+ return prompt.run();
75
+ }
76
+
77
+ async function runWizardStep() {
78
+ const { Input } = await import('enquirer');
79
+
80
+ const area = await new Input({
81
+ name: 'area',
82
+ message: 'Which area/files? (e.g. auth middleware, src/login/)',
83
+ initial: '',
84
+ }).run().catch(() => '');
85
+
86
+ const outcome = await new Input({
87
+ name: 'outcome',
88
+ message: 'Expected outcome? (e.g. user redirected to /dashboard)',
89
+ initial: '',
90
+ }).run().catch(() => '');
91
+
92
+ return { area, outcome };
93
+ }
94
+
95
+ function outputResult(text, adapter) {
96
+ log('');
97
+ log('─── Result ───────────────────────────────────────────');
98
+ log(text);
99
+ log('──────────────────────────────────────────────────────');
100
+ log('');
101
+
102
+ const shouldCopy = adapter === 'claude-cli' || adapter === 'cursor';
103
+ if (shouldCopy) {
104
+ const copied = copyToClipboard(text);
105
+ if (copied) {
106
+ ok('Copied to clipboard. Paste into Claude CLI or your IDE.');
107
+ } else {
108
+ warn('Clipboard copy failed. Copy the text above manually.');
109
+ }
110
+ } else {
111
+ // generic adapter: just output to stdout
112
+ info('(generic adapter — copy the text above manually)');
113
+ }
114
+ }
115
+
116
+ function readAdapter() {
117
+ const configPath = join(process.cwd(), '.dw', 'config', 'dw.config.yml');
118
+ if (!existsSync(configPath)) return 'claude-cli';
119
+ try {
120
+ const config = yamlLoad(readFileSync(configPath, 'utf-8'));
121
+ return config?.adapter || 'claude-cli';
122
+ } catch {
123
+ return 'claude-cli';
124
+ }
125
+ }
@@ -0,0 +1,24 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { platform } from 'node:process';
3
+
4
+ /**
5
+ * Copy text to system clipboard.
6
+ * @returns {boolean} true if succeeded
7
+ */
8
+ export function copyToClipboard(text) {
9
+ const candidates = platform === 'win32'
10
+ ? [['clip']]
11
+ : platform === 'darwin'
12
+ ? [['pbcopy']]
13
+ : [['wl-copy'], ['xclip', '-selection', 'clipboard'], ['xsel', '--clipboard', '--input']];
14
+
15
+ for (const [cmd, ...args] of candidates) {
16
+ try {
17
+ const result = spawnSync(cmd, args, { input: text, encoding: 'utf-8' });
18
+ if (result.status === 0) return true;
19
+ } catch {
20
+ // Try next candidate.
21
+ }
22
+ }
23
+ return false;
24
+ }
@@ -0,0 +1,84 @@
1
+ import { execSync } from 'node:child_process';
2
+
3
+ const TEMPLATE_SUGGESTIONS = [
4
+ 'fix: ',
5
+ 'fix authentication redirect after login',
6
+ 'fix null pointer / undefined error in ',
7
+ 'fix performance issue in ',
8
+ 'feat: add ',
9
+ 'feat: implement ',
10
+ 'feat: support ',
11
+ 'refactor: simplify ',
12
+ 'refactor: extract ',
13
+ 'refactor: rename ',
14
+ 'perf: optimize ',
15
+ 'perf: reduce load time of ',
16
+ 'chore: update dependencies',
17
+ 'docs: update README',
18
+ 'test: add tests for ',
19
+ ];
20
+
21
+ // Common verbs to detect whether a description has intent
22
+ const INTENT_VERBS = [
23
+ 'fix', 'add', 'update', 'remove', 'delete', 'create', 'implement', 'refactor',
24
+ 'rename', 'move', 'improve', 'optimize', 'support', 'extract', 'migrate',
25
+ 'replace', 'upgrade', 'enable', 'disable', 'integrate', 'patch',
26
+ ];
27
+
28
+ function getTemplateSuggestions() {
29
+ return [...TEMPLATE_SUGGESTIONS];
30
+ }
31
+
32
+ export function getGitSuggestions(cwd) {
33
+ try {
34
+ const out = execSync('git log --oneline -50 --no-merges', {
35
+ cwd,
36
+ encoding: 'utf-8',
37
+ timeout: 3000,
38
+ stdio: ['ignore', 'pipe', 'ignore'],
39
+ });
40
+ return out
41
+ .trim()
42
+ .split('\n')
43
+ .filter(Boolean)
44
+ .map((line) => line.replace(/^[a-f0-9]+ /, '').trim()) // strip hash
45
+ .filter((msg) => msg.length > 5)
46
+ .slice(0, 30);
47
+ } catch {
48
+ return [];
49
+ }
50
+ }
51
+
52
+ export function getSuggestions(cwd = process.cwd()) {
53
+ const git = getGitSuggestions(cwd);
54
+ const templates = getTemplateSuggestions();
55
+ // git log first (most relevant to this repo), then templates
56
+ const merged = [...git, ...templates];
57
+ // dedupe
58
+ return [...new Set(merged)].slice(0, 20);
59
+ }
60
+
61
+ /**
62
+ * Returns true if the description is likely too vague to give Claude good context.
63
+ */
64
+ export function isVague(text) {
65
+ const trimmed = (text || '').trim();
66
+ if (trimmed.length < 50) return true;
67
+ const lower = trimmed.toLowerCase();
68
+ const hasVerb = INTENT_VERBS.some((v) => lower.startsWith(v) || lower.includes(` ${v} `));
69
+ return !hasVerb;
70
+ }
71
+
72
+ /**
73
+ * Expand a short description into a structured prompt.
74
+ * @param {string} text - core task description
75
+ * @param {{ area?: string, outcome?: string }} extras
76
+ * @returns {string}
77
+ */
78
+ export function expandTemplate(text, { area = '', outcome = '' } = {}) {
79
+ const base = text.trim();
80
+ const parts = [base];
81
+ if (area) parts.push(`Scope: ${area.trim()}.`);
82
+ if (outcome) parts.push(`Expected: ${outcome.trim()}.`);
83
+ return parts.join('\n');
84
+ }
@@ -0,0 +1,73 @@
1
+ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+
5
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
6
+ const REGISTRY_URL = 'https://registry.npmjs.org/dw-kit/latest';
7
+ const CACHE_DIR = join(homedir(), '.dw-kit');
8
+ const CACHE_FILE = join(CACHE_DIR, 'update-cache.json');
9
+
10
+ function parseSemver(v) {
11
+ const parts = String(v).replace(/^v/, '').split('.').map(Number);
12
+ return { major: parts[0] || 0, minor: parts[1] || 0, patch: parts[2] || 0 };
13
+ }
14
+
15
+ function isNewer(latest, current) {
16
+ const l = parseSemver(latest);
17
+ const c = parseSemver(current);
18
+ if (l.major !== c.major) return l.major > c.major;
19
+ if (l.minor !== c.minor) return l.minor > c.minor;
20
+ return l.patch > c.patch;
21
+ }
22
+
23
+ function readCache() {
24
+ try {
25
+ return JSON.parse(readFileSync(CACHE_FILE, 'utf-8'));
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+
31
+ function writeCache(data) {
32
+ try {
33
+ mkdirSync(CACHE_DIR, { recursive: true });
34
+ writeFileSync(CACHE_FILE, JSON.stringify(data), 'utf-8');
35
+ } catch {
36
+ // ignore write errors (permission, disk full, etc.)
37
+ }
38
+ }
39
+
40
+ async function fetchLatestVersion() {
41
+ const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(3000) });
42
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
43
+ const data = await res.json();
44
+ return data.version;
45
+ }
46
+
47
+ /**
48
+ * Returns latest version string if an update is available (from cache), null otherwise.
49
+ * Never throws, never makes network calls.
50
+ */
51
+ export function getUpdateNotice(currentVersion) {
52
+ if (process.env.DW_NO_UPDATE_CHECK) return null;
53
+ const cache = readCache();
54
+ if (!cache?.latest) return null;
55
+ return isNewer(cache.latest, currentVersion) ? cache.latest : null;
56
+ }
57
+
58
+ /**
59
+ * Fires off an async check against npm registry and updates the cache.
60
+ * Non-blocking — caller should NOT await this.
61
+ * Skips the check if cache is still fresh (< 24h).
62
+ */
63
+ export function scheduleUpdateCheck(currentVersion) {
64
+ if (process.env.DW_NO_UPDATE_CHECK) return;
65
+
66
+ const cache = readCache();
67
+ const now = Date.now();
68
+ if (cache?.checkedAt && now - cache.checkedAt < CACHE_TTL_MS) return;
69
+
70
+ fetchLatestVersion()
71
+ .then((latest) => writeCache({ latest, checkedAt: now, current: currentVersion }))
72
+ .catch(() => {});
73
+ }
@@ -11,6 +11,7 @@ import { mkdirSync, rmSync, existsSync, readFileSync } from 'node:fs';
11
11
  import { join, resolve } from 'node:path';
12
12
  import { execSync } from 'node:child_process';
13
13
  import { fileURLToPath } from 'node:url';
14
+ import { copyFileSync } from 'node:fs';
14
15
 
15
16
  const __dirname = resolve(fileURLToPath(import.meta.url), '..');
16
17
  const DW_BIN = resolve(__dirname, '..', 'bin', 'dw.mjs');
@@ -72,7 +73,7 @@ test('--version returns semver', () => {
72
73
 
73
74
  test('--help lists all commands', () => {
74
75
  const out = dw('--help', TEMP_BASE);
75
- for (const cmd of ['init', 'upgrade', 'validate', 'doctor', 'migrate']) {
76
+ for (const cmd of ['init', 'upgrade', 'validate', 'doctor', 'prompt', 'claude-vn-fix']) {
76
77
  assert(out.includes(cmd), `Missing command: ${cmd}`);
77
78
  }
78
79
  });
@@ -262,6 +263,51 @@ test('doctor reports issues on empty project', () => {
262
263
  }
263
264
  });
264
265
 
266
+ // ── Test: dw prompt ──────────────────────────────────────────────────────────
267
+ console.log();
268
+ console.log('▶ dw prompt');
269
+
270
+ test('prompt --text outputs structured result', () => {
271
+ const out = dw('prompt --text "fix login redirect after OAuth in auth middleware"', TEMP_BASE);
272
+ assert(out.includes('fix login redirect'), 'Should include description in output');
273
+ assert(!out.includes('Description seems short'), 'Long description should skip wizard');
274
+ });
275
+
276
+ test('prompt --text with short input expands without error', () => {
277
+ const out = dw('prompt --text "fix login"', TEMP_BASE);
278
+ assert(out.includes('fix login'), 'Should include description in output');
279
+ });
280
+
281
+ test('prompt --text empty string exits with error', () => {
282
+ try {
283
+ dw('prompt --text ""', TEMP_BASE);
284
+ assert(false, 'Should have thrown');
285
+ } catch (e) {
286
+ assert(e.status === 1, 'Should exit with code 1');
287
+ }
288
+ });
289
+
290
+ test('prompt --help shows options', () => {
291
+ const out = dw('prompt --help', TEMP_BASE);
292
+ assert(out.includes('--text'), 'Missing --text option');
293
+ });
294
+
295
+ // ── Test: dw claude-vn-fix (fixture patch) ───────────────────────────────────
296
+ console.log();
297
+ console.log('▶ dw claude-vn-fix');
298
+
299
+ test('claude-vn-fix patches known bug fixture', () => {
300
+ const dir = freshDir('claude-vn-fix-fixture');
301
+ const fixtureSrc = resolve(__dirname, '__fixtures__', 'claude-cli-bug-snippet.js');
302
+ const target = join(dir, 'cli.js');
303
+ copyFileSync(fixtureSrc, target);
304
+
305
+ const out = dw(`claude-vn-fix --path "${target}"`, dir);
306
+ assert(out.includes('Patch applied') || out.includes('Already patched'), 'Should apply patch');
307
+ const patched = readFileSync(target, 'utf-8');
308
+ assert(patched.includes('dw-kit Vietnamese IME fix'), 'Patch marker missing in output file');
309
+ });
310
+
265
311
  // ── Test: dw upgrade ─────────────────────────────────────────────────────────
266
312
  console.log();
267
313
  console.log('▶ dw upgrade');
@@ -288,16 +334,6 @@ test('upgrade fails on project without config', () => {
288
334
  }
289
335
  });
290
336
 
291
- // ── Test: dw migrate ─────────────────────────────────────────────────────────
292
- console.log();
293
- console.log('▶ dw migrate');
294
-
295
- test('migrate on v1 project reports no v0.3 config', () => {
296
- const dir = join(TEMP_BASE, 'init-solo');
297
- const out = dw('migrate', dir);
298
- assert(out.includes('Already on v1') || out.includes('No v0.3'), 'Should report no migration needed');
299
- });
300
-
301
337
  // ── Cleanup ──────────────────────────────────────────────────────────────────
302
338
  rmSync(TEMP_BASE, { recursive: true });
303
339
 
@@ -1,243 +0,0 @@
1
- #!/bin/bash
2
- # scripts/migrate-v03-to-v1.sh
3
- # ⚠ DEPRECATED: Prefer `dw migrate` from npm CLI.
4
- # Migrate project từ dw-kit v0.3 sang v1
5
- # Usage: bash scripts/migrate-v03-to-v1.sh [--dry-run]
6
-
7
- set -euo pipefail
8
-
9
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
- PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
11
-
12
- DRY_RUN=false
13
- [ "${1:-}" = "--dry-run" ] && DRY_RUN=true
14
-
15
- log() { echo " $*"; }
16
- info() { echo ""; echo "▶ $*"; }
17
- warn() { echo " ⚠ $*"; }
18
- ok() { echo " ✓ $*"; }
19
- dry() { [ "$DRY_RUN" = true ] && echo " [dry] $*" || true; }
20
- do_action() {
21
- if [ "$DRY_RUN" = true ]; then echo " [dry] $*"; else eval "$*"; fi
22
- }
23
-
24
- OLD_CONFIG="$PROJECT_ROOT/.dw/config/dw.config.yml"
25
- NEW_CONFIG_DIR="$PROJECT_ROOT/config"
26
- NEW_CONFIG="$NEW_CONFIG_DIR/dw.config.yml"
27
-
28
- echo ""
29
- echo "══════════════════════════════════════════"
30
- echo " dw-kit v0.3 → v1 Migration"
31
- [ "$DRY_RUN" = true ] && echo " Mode: DRY RUN"
32
- echo "══════════════════════════════════════════"
33
-
34
- # ── Step 1: Validate v0.3 present ────────────────────────────────────────────
35
- info "Step 1: Validate source config"
36
-
37
- if [ ! -f "$OLD_CONFIG" ]; then
38
- echo " No config/dw.config.yml found. Already on v1 or fresh install."
39
- exit 0
40
- fi
41
-
42
- if [ -L "$OLD_CONFIG" ]; then
43
- warn "config/dw.config.yml is already a symlink → migration may have already run."
44
- read -r -p " Continue anyway? [y/N] " confirm
45
- [[ "$confirm" =~ ^[Yy]$ ]] || exit 0
46
- fi
47
-
48
- ok "Found config/dw.config.yml"
49
-
50
- # ── Step 2: Detect customized skills ─────────────────────────────────────────
51
- info "Step 2: Detect customized skills"
52
-
53
- OVERRIDES_DIR="$PROJECT_ROOT/.dw/adapters/claude-cli/overrides/skills"
54
- SKILLS_DIR="$PROJECT_ROOT/.claude/skills"
55
- customized_count=0
56
-
57
- # Heuristic: compare với toolkit's own copy nếu có, hoặc check git
58
- if command -v git &>/dev/null && git -C "$PROJECT_ROOT" rev-parse --git-dir &>/dev/null; then
59
- for skill_dir in "$SKILLS_DIR"/*/; do
60
- [ -d "$skill_dir" ] || continue
61
- skill_name=$(basename "$skill_dir")
62
- skill_file="$skill_dir/SKILL.md"
63
- [ -f "$skill_file" ] || continue
64
-
65
- # Check if file was modified from its committed version
66
- if ! git -C "$PROJECT_ROOT" diff --quiet HEAD -- ".claude/skills/$skill_name/SKILL.md" 2>/dev/null; then
67
- warn "Skill '$skill_name' has uncommitted changes → preserving to overrides/"
68
- do_action "mkdir -p '$OVERRIDES_DIR/$skill_name'"
69
- do_action "cp '$skill_file' '$OVERRIDES_DIR/$skill_name/SKILL.md'"
70
- customized_count=$((customized_count + 1))
71
- fi
72
- done
73
- else
74
- warn "Not a git repo or git not available. Cannot auto-detect customizations."
75
- warn "Manually copy customized skills to adapters/claude-cli/overrides/skills/ before upgrading."
76
- fi
77
-
78
- if [ $customized_count -gt 0 ]; then
79
- ok "$customized_count customized skill(s) preserved to overrides/"
80
- else
81
- ok "No customized skills detected"
82
- fi
83
-
84
- # ── Step 3: Migrate config ────────────────────────────────────────────────────
85
- info "Step 3: Migrate config (config/dw.config.yml → config/dw.config.yml)"
86
-
87
- if [ -f "$NEW_CONFIG" ]; then
88
- warn "config/dw.config.yml already exists. Skipping config migration."
89
- warn "Review manually: $NEW_CONFIG"
90
- else
91
- if ! command -v python3 &>/dev/null; then
92
- warn "python3 not found. Manual config migration needed."
93
- warn "Map: level: 1 → default_depth: quick"
94
- warn " level: 2 → default_depth: standard"
95
- warn " level: 3 → default_depth: thorough"
96
- else
97
- if [ "$DRY_RUN" = false ]; then
98
- mkdir -p "$NEW_CONFIG_DIR"
99
- python3 - "$OLD_CONFIG" "$NEW_CONFIG" <<'PYEOF'
100
- import sys, re
101
-
102
- old_path = sys.argv[1]
103
- new_path = sys.argv[2]
104
-
105
- with open(old_path) as f:
106
- old_content = f.read()
107
-
108
- # Extract values from old config
109
- def get_val(key, content, default=""):
110
- m = re.search(rf'^\s*{re.escape(key)}:\s*([^\n#]+)', content, re.MULTILINE)
111
- if m:
112
- return m.group(1).strip().strip('"').strip("'")
113
- return default
114
-
115
- project_name = get_val("name", old_content, "my-project")
116
- project_lang = get_val("language", old_content, "vi")
117
- level_str = get_val("level", old_content, "2")
118
-
119
- depth_map = {"1": "quick", "2": "standard", "3": "thorough"}
120
- depth = depth_map.get(level_str, "standard")
121
-
122
- # Extract roles
123
- roles_match = re.findall(r'^\s+-\s+(dev|techlead|ba|qc|pm)\b', old_content, re.MULTILINE)
124
- roles = roles_match if roles_match else ["dev"]
125
-
126
- # Extract quality commands
127
- test_cmd = get_val("test_command", old_content, "")
128
- lint_cmd = get_val("lint_command", old_content, "")
129
-
130
- # Extract paths
131
- tasks_path = get_val("tasks", old_content, ".dw/tasks")
132
- docs_path = get_val("docs", old_content, ".dw/docs")
133
-
134
- # Build new config
135
- roles_yaml = "\n".join(f" - {r}" for r in roles)
136
-
137
- new_config = f"""# dw-kit Configuration
138
- # Generated by migrate-v03-to-v1.sh
139
- # Review and adjust as needed.
140
-
141
- project:
142
- name: "{project_name}"
143
- language: "{project_lang}"
144
-
145
- workflow:
146
- default_depth: "{depth}" # quick | standard | thorough
147
- # AI assesses per-task and may recommend different depth
148
-
149
- team:
150
- roles:
151
- {roles_yaml}
152
-
153
- quality:
154
- test_command: "{test_cmd}" # empty = skip
155
- lint_command: "{lint_cmd}" # empty = skip
156
- block_on_fail: false
157
-
158
- tracking:
159
- estimation: false
160
- log_work: false
161
- estimation_unit: "hours"
162
-
163
- paths:
164
- tasks: "{tasks_path}"
165
- docs: "{docs_path}"
166
-
167
- # Claude-specific capabilities (Layer 2)
168
- claude:
169
- models:
170
- plan: "" # empty = inherit from Claude Code settings
171
- execute: ""
172
- review: ""
173
- structured_output: true
174
- worktree_execution: false
175
- mcp: []
176
-
177
- # Toolkit version tracking (do not edit manually)
178
- _toolkit:
179
- core_version: "1.0"
180
- platform_version: "1.0"
181
- capability_version: "1.0"
182
- migrated_from: "v0.3"
183
- last_upgrade: "{__import__('datetime').date.today().isoformat()}"
184
- """
185
-
186
- with open(new_path, 'w') as f:
187
- f.write(new_config)
188
- PYEOF
189
- ok "config/dw.config.yml created (migrated from level: $level_str → depth: $(grep default_depth "$NEW_CONFIG" | head -1))"
190
- else
191
- dry "Would create config/dw.config.yml from config/dw.config.yml"
192
- fi
193
- fi
194
- fi
195
-
196
- # ── Step 4: Create backward-compat symlink ────────────────────────────────────
197
- info "Step 4: Create backward-compat symlink"
198
-
199
- if [ -f "$NEW_CONFIG" ] && [ ! -L "$OLD_CONFIG" ]; then
200
- if [ "$DRY_RUN" = false ]; then
201
- # Backup original
202
- cp "$OLD_CONFIG" "${OLD_CONFIG}.bak"
203
- ok "Backed up original to config/dw.config.yml.bak"
204
- # Create symlink
205
- rm "$OLD_CONFIG"
206
- ln -s ".dw/.dw/config/dw.config.yml" "$OLD_CONFIG"
207
- ok "Created symlink: config/dw.config.yml → config/dw.config.yml"
208
- else
209
- dry "Would create symlink: config/dw.config.yml → config/dw.config.yml"
210
- fi
211
- fi
212
-
213
- # ── Step 5: Check CI/CD references ───────────────────────────────────────────
214
- info "Step 5: Check CI/CD references"
215
-
216
- found_refs=false
217
- for ci_file in ".github/workflows/"*.yml ".gitlab-ci.yml" "Makefile" ".circleci/config.yml" "package.json"; do
218
- full_path="$PROJECT_ROOT/$ci_file"
219
- if [ -f "$full_path" ]; then
220
- if grep -qE "dv-workflow\.config\.yml|dv-workflow-kit" "$full_path" 2>/dev/null; then
221
- warn "Found reference in: $ci_file — update to config/dw.config.yml"
222
- found_refs=true
223
- fi
224
- fi
225
- done
226
- [ "$found_refs" = false ] && ok "No CI/CD references found"
227
-
228
- # ── Summary ───────────────────────────────────────────────────────────────────
229
- echo ""
230
- echo "══════════════════════════════════════════"
231
- if [ "$DRY_RUN" = true ]; then
232
- echo " DRY RUN complete. Review above, then run without --dry-run."
233
- else
234
- echo " Migration complete!"
235
- echo ""
236
- echo " Next steps:"
237
- echo " 1. Review config/dw.config.yml — adjust team.roles, quality commands"
238
- echo " 2. Run: bash setup.sh to regenerate .claude/ from new config"
239
- echo " 3. Check adapters/claude-cli/overrides/ for preserved customizations"
240
- [ "$found_refs" = true ] && echo " 4. ⚠ Update CI/CD files (see warnings above)"
241
- fi
242
- echo "══════════════════════════════════════════"
243
- echo ""