dw-kit 1.2.1 → 1.3.4

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 (59) 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 +6 -0
  8. package/.claude/hooks/stop-check.sh +88 -36
  9. package/.claude/hooks/telemetry-log.sh +34 -0
  10. package/.claude/rules/dw.md +138 -0
  11. package/.claude/settings.json +28 -1
  12. package/.claude/skills/dw-arch-review/SKILL.md +119 -119
  13. package/.claude/skills/dw-archive/SKILL.md +81 -81
  14. package/.claude/skills/dw-commit/SKILL.md +81 -81
  15. package/.claude/skills/dw-config-init/SKILL.md +91 -91
  16. package/.claude/skills/dw-config-validate/SKILL.md +75 -75
  17. package/.claude/skills/dw-dashboard/SKILL.md +209 -209
  18. package/.claude/skills/dw-debug/SKILL.md +97 -97
  19. package/.claude/skills/dw-decision/SKILL.md +116 -0
  20. package/.claude/skills/dw-docs-update/SKILL.md +125 -125
  21. package/.claude/skills/dw-estimate/SKILL.md +90 -90
  22. package/.claude/skills/dw-execute/SKILL.md +121 -98
  23. package/.claude/skills/dw-flow/SKILL.md +274 -274
  24. package/.claude/skills/dw-handoff/SKILL.md +92 -81
  25. package/.claude/skills/dw-kit-report/SKILL.md +152 -152
  26. package/.claude/skills/dw-log-work/SKILL.md +69 -69
  27. package/.claude/skills/dw-onboard/SKILL.md +201 -201
  28. package/.claude/skills/dw-plan/SKILL.md +222 -125
  29. package/.claude/skills/dw-prompt/SKILL.md +62 -62
  30. package/.claude/skills/dw-requirements/SKILL.md +98 -98
  31. package/.claude/skills/dw-research/SKILL.md +128 -114
  32. package/.claude/skills/dw-retroactive/SKILL.md +195 -311
  33. package/.claude/skills/dw-review/SKILL.md +66 -66
  34. package/.claude/skills/dw-rollback/SKILL.md +90 -90
  35. package/.claude/skills/dw-sprint-review/SKILL.md +99 -99
  36. package/.claude/skills/dw-task-init/SKILL.md +71 -59
  37. package/.claude/skills/dw-test-plan/SKILL.md +113 -113
  38. package/.claude/skills/dw-thinking/SKILL.md +70 -70
  39. package/.claude/skills/dw-upgrade/SKILL.md +72 -72
  40. package/.dw/core/PILLARS.md +122 -0
  41. package/.dw/core/ROLES.md +257 -257
  42. package/.dw/core/templates/v2/spec.md +68 -0
  43. package/.dw/core/templates/v2/tracking.md +62 -0
  44. package/.dw/core/v14-evaluation-protocol.md +118 -0
  45. package/CLAUDE.md +42 -39
  46. package/MIGRATION-v1.3.md +202 -0
  47. package/README.md +35 -6
  48. package/package.json +4 -2
  49. package/src/cli.mjs +29 -1
  50. package/src/commands/dashboard.mjs +116 -0
  51. package/src/commands/doctor.mjs +165 -149
  52. package/src/commands/init.mjs +339 -332
  53. package/src/commands/metrics.mjs +185 -0
  54. package/src/lib/active-index.mjs +87 -0
  55. package/src/lib/cut-analysis.mjs +240 -0
  56. package/src/lib/telemetry.mjs +80 -0
  57. package/.claude/rules/dw-core.md +0 -100
  58. package/.claude/rules/dw-skills.md +0 -53
  59. package/.claude/rules/workflow-rules.md +0 -77
@@ -1,332 +1,339 @@
1
- import { existsSync, readFileSync, appendFileSync, writeFileSync } from 'node:fs';
2
- import { join, resolve } from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
- import { banner, ok, warn, info, log, ask, choose } from '../lib/ui.mjs';
5
- import { buildConfig, writeConfig } from '../lib/config.mjs';
6
- import { copyDir, copyFile, ensureDir } from '../lib/copy.mjs';
7
- import { detectPlatform, platformLabel } from '../lib/platform.mjs';
8
-
9
- const TOOLKIT_ROOT = resolve(fileURLToPath(import.meta.url), '..', '..', '..');
10
-
11
- const PRESETS = {
12
- 'solo-quick': { depth: 'quick', roles: ['dev'], tracking: false },
13
- 'small-team': { depth: 'standard', roles: ['dev', 'techlead'], tracking: true },
14
- 'enterprise': { depth: 'thorough', roles: ['dev', 'techlead', 'ba', 'qc', 'pm'], tracking: true },
15
- };
16
- const VALID_DEPTHS = ['quick', 'standard', 'thorough'];
17
- const VALID_LANGUAGES = ['vi', 'en'];
18
-
19
- export async function initCommand(opts) {
20
- const projectDir = process.cwd();
21
-
22
- if (existsSync(join(projectDir, '.dw', 'config', 'dw.config.yml'))) {
23
- warn('dw-kit is already initialized in this project (.dw/config/dw.config.yml exists).');
24
- const answer = await ask('Reinitialize? This will overwrite config. (y/N)', 'N');
25
- if (answer.toLowerCase() !== 'y') {
26
- log('Aborted.');
27
- return;
28
- }
29
- }
30
-
31
- let projectName, depth, roles, language;
32
-
33
- if (opts.preset) {
34
- const preset = PRESETS[opts.preset];
35
- if (!preset) {
36
- warn(`Unknown preset: ${opts.preset}. Available: ${Object.keys(PRESETS).join(', ')}`);
37
- process.exit(1);
38
- }
39
- banner(`Setup Wizard v1.0 — preset: ${opts.preset}`);
40
- projectName = process.env.DW_NAME || guessProjectName(projectDir);
41
- depth = preset.depth;
42
- roles = preset.roles;
43
- language = process.env.DW_LANG || 'vi';
44
- log(`Using preset: ${opts.preset}`);
45
- log(` Project: ${projectName}, Depth: ${depth}, Roles: ${roles.join(', ')}, Lang: ${language}`);
46
- } else if (opts.silent) {
47
- banner('Setup Wizard v1.0 silent mode');
48
- projectName = process.env.DW_NAME || guessProjectName(projectDir);
49
- depth = process.env.DW_DEPTH || 'standard';
50
- const parsedRoles = parseRolesEnv(process.env.DW_ROLES || '');
51
- language = process.env.DW_LANG || 'vi';
52
- if (!VALID_DEPTHS.includes(depth)) {
53
- warn(`Invalid DW_DEPTH="${depth}". Allowed: ${VALID_DEPTHS.join(', ')}`);
54
- process.exit(1);
55
- }
56
- if (!VALID_LANGUAGES.includes(language)) {
57
- warn(`Invalid DW_LANG="${language}". Allowed: ${VALID_LANGUAGES.join(', ')}`);
58
- process.exit(1);
59
- }
60
- roles = normalizeRolesForDepth(parsedRoles, depth);
61
- log(`Silent mode — reading from environment variables`);
62
- log(` Project: ${projectName}, Depth: ${depth}, Roles: ${roles.join(', ')}, Lang: ${language}`);
63
- } else {
64
- banner('Setup Wizard v1.0');
65
- projectName = await askProjectName(projectDir);
66
- depth = await askDepth();
67
- roles = defaultRolesForDepth(depth);
68
- language = await askLanguage();
69
- log(` Roles auto-selected by depth: ${roles.join(', ')}`);
70
- }
71
-
72
- console.log();
73
- info('Detecting platform...');
74
- const adapter = opts.adapter || detectPlatform(projectDir);
75
- ok(`Platform: ${platformLabel(adapter)}`);
76
-
77
- info('Setting up project...');
78
- await setupProject(projectDir, { projectName, depth, roles, language, adapter });
79
-
80
- printSummary({ projectName, depth, roles, language, adapter });
81
- }
82
-
83
- async function askProjectName(projectDir) {
84
- const guess = guessProjectName(projectDir);
85
- return ask('[Project] Project name?', guess);
86
- }
87
-
88
- async function askDepth() {
89
- return choose('[Depth] Default workflow depth:', [
90
- { value: 'quick', label: 'Solo dev, hotfix, familiar code — minimal ceremony' },
91
- { value: 'standard', label: 'Team, feature — full 6-phase workflow' },
92
- { value: 'thorough', label: 'Enterprise — full workflow + arch-review + test-plan' },
93
- ], 'standard');
94
- }
95
-
96
- async function askLanguage() {
97
- return choose('[Lang] Documentation language:', [
98
- { value: 'vi', label: 'Tiếng Việt' },
99
- { value: 'en', label: 'English' },
100
- ], 'vi');
101
- }
102
-
103
- function guessProjectName(dir) {
104
- const base = dir.split(/[/\\]/).pop();
105
- return base || 'my-project';
106
- }
107
-
108
- function parseRolesEnv(rolesStr) {
109
- if (!rolesStr) return [];
110
- const valid = ['dev', 'techlead', 'ba', 'qc', 'pm'];
111
- const parsed = rolesStr.split(',').map(r => r.trim().toLowerCase()).filter(r => valid.includes(r));
112
- return [...new Set(parsed)];
113
- }
114
-
115
- function defaultRolesForDepth(depth) {
116
- if (depth === 'quick') return ['dev'];
117
- if (depth === 'thorough') return ['dev', 'techlead', 'ba', 'qc', 'pm'];
118
- return ['dev', 'techlead'];
119
- }
120
-
121
- function normalizeRolesForDepth(parsedRoles, depth) {
122
- const required = defaultRolesForDepth(depth);
123
- const merged = [...new Set([...parsedRoles, ...required])];
124
- const missing = required.filter((r) => !parsedRoles.includes(r));
125
- if (parsedRoles.length > 0 && missing.length > 0) {
126
- warn(`DW_ROLES missing required roles for depth "${depth}". Auto-added: ${missing.join(', ')}`);
127
- }
128
- return merged;
129
- }
130
-
131
- async function setupProject(projectDir, { projectName, depth, roles, language, adapter }) {
132
- copyCoreDocs(projectDir);
133
- copyConfig(projectDir, { projectName, depth, roles, language });
134
- copyAdapterStructure(projectDir);
135
-
136
- if (adapter === 'claude-cli') {
137
- copyClaudeFiles(projectDir);
138
- createMinimalCLAUDEmd(projectDir, projectName);
139
- } else if (adapter === 'cursor') {
140
- copyCursorFiles(projectDir);
141
- copyGenericAdapter(projectDir);
142
- } else if (adapter === 'generic') {
143
- copyGenericAdapter(projectDir);
144
- }
145
-
146
- createRuntimeDirs(projectDir);
147
- updateGitignore(projectDir);
148
- }
149
-
150
- function copyCoreDocs(projectDir) {
151
- const src = join(TOOLKIT_ROOT, '.dw', 'core');
152
- const dst = join(projectDir, '.dw', 'core');
153
- copyDir(src, dst, { overwrite: true });
154
- ok('.dw/core/ (WORKFLOW.md, THINKING.md, QUALITY.md, ROLES.md, templates/)');
155
- }
156
-
157
- function copyConfig(projectDir, { projectName, depth, roles, language }) {
158
- const configDir = join(projectDir, '.dw', 'config');
159
- ensureDir(configDir);
160
-
161
- const config = buildConfig({ projectName, language, depth, roles });
162
- writeConfig(join(configDir, 'dw.config.yml'), config);
163
-
164
- copyFile(
165
- join(TOOLKIT_ROOT, '.dw', 'config', 'config.schema.json'),
166
- join(configDir, 'config.schema.json'),
167
- );
168
-
169
- const presetsDir = join(configDir, 'presets');
170
- ensureDir(presetsDir);
171
- copyDir(join(TOOLKIT_ROOT, '.dw', 'config', 'presets'), presetsDir, { overwrite: true });
172
-
173
- ok('.dw/config/dw.config.yml + schema + presets');
174
- }
175
-
176
- function copyAdapterStructure(projectDir) {
177
- const adaptersDir = join(projectDir, '.dw', 'adapters');
178
- copyDir(join(TOOLKIT_ROOT, '.dw', 'adapters'), adaptersDir);
179
- ok('.dw/adapters/ (claude-cli, generic)');
180
- }
181
-
182
- function copyClaudeFiles(projectDir) {
183
- const src = join(TOOLKIT_ROOT, '.claude');
184
- const dst = join(projectDir, '.claude');
185
- const results = copyDir(src, dst, { overwrite: true });
186
-
187
- const skipCount = results.filter(r => r.action === 'skip').length;
188
- const copyCount = results.filter(r => r.action === 'copy').length;
189
-
190
- ok(`.claude/ (${copyCount} files — skills, agents, hooks, rules, templates)`);
191
- if (skipCount > 0) log(` ${skipCount} existing files preserved`);
192
- }
193
-
194
- function createMinimalCLAUDEmd(projectDir, projectName) {
195
- const dst = join(projectDir, 'CLAUDE.md');
196
-
197
- if (existsSync(dst)) {
198
- // User already has their own CLAUDE.md — leave it completely alone
199
- return;
200
- }
201
-
202
- const content = `# ${projectName}
203
-
204
- > Add your project description here.
205
-
206
- ---
207
-
208
- ## Tech Stack
209
-
210
- <!-- Update with your project's actual stack -->
211
- - Framework:
212
- - Database:
213
- - Testing:
214
-
215
- ## Project-Specific Rules
216
-
217
- <!-- Add project-specific rules here -->
218
- <!-- dw workflow rules are auto-loaded from .claude/rules/ -->
219
- `;
220
- writeFileSync(dst, content, 'utf-8');
221
- ok('CLAUDE.md (minimal project template — dw rules are in .claude/rules/)');
222
- }
223
-
224
- function copyCursorFiles(projectDir) {
225
- const rulesDir = join(projectDir, '.cursor', 'rules');
226
- ensureDir(rulesDir);
227
-
228
- const ruleContent = `# dw-kit Workflow Rules
229
- # Auto-generated by dw init --adapter cursor
230
- # Reference: .dw/core/WORKFLOW.md for full methodology
231
-
232
- ## Workflow
233
- Follow the 6-phase workflow: Initialize → Understand → Plan → Execute → Verify → Close.
234
- Read @.dw/core/WORKFLOW.md for detailed phase instructions.
235
-
236
- ## Quality
237
- - Apply TDD: write test implement → refactor
238
- - Read @.dw/core/QUALITY.md for the 4-layer quality strategy
239
- - Run test/lint commands from .dw/config/dw.config.yml before committing
240
-
241
- ## Thinking
242
- When planning or debugging, apply the thinking framework from @.dw/core/THINKING.md:
243
- - Critical Thinking: question assumptions
244
- - Systems Thinking: trace dependencies and side effects
245
- - First Principles: break down to fundamentals
246
-
247
- ## Roles
248
- Check @.dw/core/ROLES.md for role definitions and decision authority.
249
-
250
- ## Task Tracking
251
- - Task docs go in .dw/tasks/[task-name]/
252
- - Use templates from .dw/core/templates/ for context, plan, and progress docs
253
- `;
254
-
255
- writeFileSync(join(rulesDir, 'dw-workflow.mdc'), ruleContent, 'utf-8');
256
- ok('.cursor/rules/dw-workflow.mdc (Cursor rules from core methodology)');
257
- }
258
-
259
- function copyGenericAdapter(projectDir) {
260
- const src = join(TOOLKIT_ROOT, '.dw', 'adapters', 'generic', 'AGENT.md');
261
- const dst = join(projectDir, 'AGENT.md');
262
- if (!existsSync(dst)) {
263
- copyFile(src, dst);
264
- ok('AGENT.md (generic adapter for Cursor/Windsurf/Copilot)');
265
- } else {
266
- warn('AGENT.md already exists — skipping');
267
- }
268
- }
269
-
270
- function createRuntimeDirs(projectDir) {
271
- for (const dir of ['.dw/tasks', '.dw/docs', '.dw/metrics', '.dw/reports']) {
272
- ensureDir(join(projectDir, dir));
273
- }
274
- ok('.dw/ (tasks, docs, metrics, reports)');
275
- }
276
-
277
- function updateGitignore(projectDir) {
278
- const gitignorePath = join(projectDir, '.gitignore');
279
- const entriesToAdd = ['CLAUDE.local.md', '.claude/settings.local.json', '.dw/config/dw.config.local.yml'];
280
-
281
- if (existsSync(gitignorePath)) {
282
- const content = readFileSync(gitignorePath, 'utf-8');
283
- const missing = entriesToAdd.filter(e => !content.includes(e));
284
- if (missing.length > 0) {
285
- appendFileSync(gitignorePath, `\n# dw-kit\n${missing.join('\n')}\n`, 'utf-8');
286
- ok('.gitignore updated');
287
- }
288
- } else {
289
- writeFileSync(gitignorePath, `# dw-kit\n${entriesToAdd.join('\n')}\n`, 'utf-8');
290
- ok('.gitignore created');
291
- }
292
- }
293
-
294
- function printSummary({ projectName, depth, roles, language, adapter }) {
295
- console.log();
296
- console.log(` ✅ Setup complete!`);
297
- console.log();
298
- console.log(` Project : ${projectName}`);
299
- console.log(` Depth : ${depth}`);
300
- console.log(` Roles : ${roles.join(', ')}`);
301
- console.log(` Language : ${language}`);
302
- console.log(` Platform : ${platformLabel(adapter)}`);
303
- console.log();
304
- console.log(` Files created:`);
305
- console.log(` .dw/ — core/, config/, adapters/, tasks, docs`);
306
- if (adapter === 'claude-cli') {
307
- console.log(` .claude/ — skills, agents, hooks, rules/`);
308
- console.log(` CLAUDE.md — minimal project template (dw rules in .claude/rules/)`);
309
- } else if (adapter === 'cursor') {
310
- console.log(` .cursor/rules/ — workflow rules for Cursor`);
311
- console.log(` AGENT.md — methodology reference`);
312
- } else {
313
- console.log(` AGENT.md — methodology reference`);
314
- }
315
- console.log();
316
- if (adapter === 'claude-cli') {
317
- console.log(` Next steps:`);
318
- console.log(` Run: claude (to open Claude Code in this directory in terminal)`);
319
- console.log(` Run: /dw-flow [task-name]`);
320
- console.log(` Suggested: Update Tech Stack + rules in CLAUDE.md`);
321
- } else if (adapter === 'cursor') {
322
- console.log(` Next steps:`);
323
- console.log(` 1. Open Cursor in this directory`);
324
- console.log(` 2. Rules auto-loaded from .cursor/rules/`);
325
- console.log(` 3. Reference AGENT.md + .dw/core/WORKFLOW.md for guidance`);
326
- } else {
327
- console.log(` Next steps:`);
328
- console.log(` 1. Open your AI coding tool in this directory`);
329
- console.log(` 2. Reference AGENT.md for workflow guidance`);
330
- }
331
- console.log();
332
- }
1
+ import { existsSync, readFileSync, appendFileSync, writeFileSync } from 'node:fs';
2
+ import { join, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { banner, ok, warn, info, log, ask, choose } from '../lib/ui.mjs';
5
+ import { buildConfig, writeConfig } from '../lib/config.mjs';
6
+ import { copyDir, copyFile, ensureDir } from '../lib/copy.mjs';
7
+ import { detectPlatform, platformLabel } from '../lib/platform.mjs';
8
+
9
+ const TOOLKIT_ROOT = resolve(fileURLToPath(import.meta.url), '..', '..', '..');
10
+
11
+ const PRESETS = {
12
+ 'solo': { depth: 'quick', roles: ['dev'], tracking: false, hooksProfile: 'safety-only' },
13
+ 'solo-quick': { depth: 'quick', roles: ['dev'], tracking: false },
14
+ 'small-team': { depth: 'standard', roles: ['dev', 'techlead'], tracking: true },
15
+ 'team': { depth: 'standard', roles: ['dev', 'techlead'], tracking: true, hooksProfile: 'full' },
16
+ 'enterprise': { depth: 'thorough', roles: ['dev', 'techlead', 'ba', 'qc', 'pm'], tracking: true, hooksProfile: 'full' },
17
+ };
18
+ const VALID_DEPTHS = ['quick', 'standard', 'thorough'];
19
+ const VALID_LANGUAGES = ['vi', 'en'];
20
+
21
+ export async function initCommand(opts) {
22
+ const projectDir = process.cwd();
23
+
24
+ if (existsSync(join(projectDir, '.dw', 'config', 'dw.config.yml'))) {
25
+ warn('dw-kit is already initialized in this project (.dw/config/dw.config.yml exists).');
26
+ const answer = await ask('Reinitialize? This will overwrite config. (y/N)', 'N');
27
+ if (answer.toLowerCase() !== 'y') {
28
+ log('Aborted.');
29
+ return;
30
+ }
31
+ }
32
+
33
+ let projectName, depth, roles, language;
34
+
35
+ // --solo shortcut maps to --preset solo
36
+ if (opts.solo && !opts.preset) {
37
+ opts.preset = 'solo';
38
+ }
39
+
40
+ if (opts.preset) {
41
+ const preset = PRESETS[opts.preset];
42
+ if (!preset) {
43
+ warn(`Unknown preset: ${opts.preset}. Available: ${Object.keys(PRESETS).join(', ')}`);
44
+ process.exit(1);
45
+ }
46
+ banner(`Setup Wizard v1.0 — preset: ${opts.preset}`);
47
+ projectName = process.env.DW_NAME || guessProjectName(projectDir);
48
+ depth = preset.depth;
49
+ roles = preset.roles;
50
+ language = process.env.DW_LANG || 'vi';
51
+ log(`Using preset: ${opts.preset}`);
52
+ log(` Project: ${projectName}, Depth: ${depth}, Roles: ${roles.join(', ')}, Lang: ${language}`);
53
+ } else if (opts.silent) {
54
+ banner('Setup Wizard v1.0 — silent mode');
55
+ projectName = process.env.DW_NAME || guessProjectName(projectDir);
56
+ depth = process.env.DW_DEPTH || 'standard';
57
+ const parsedRoles = parseRolesEnv(process.env.DW_ROLES || '');
58
+ language = process.env.DW_LANG || 'vi';
59
+ if (!VALID_DEPTHS.includes(depth)) {
60
+ warn(`Invalid DW_DEPTH="${depth}". Allowed: ${VALID_DEPTHS.join(', ')}`);
61
+ process.exit(1);
62
+ }
63
+ if (!VALID_LANGUAGES.includes(language)) {
64
+ warn(`Invalid DW_LANG="${language}". Allowed: ${VALID_LANGUAGES.join(', ')}`);
65
+ process.exit(1);
66
+ }
67
+ roles = normalizeRolesForDepth(parsedRoles, depth);
68
+ log(`Silent mode reading from environment variables`);
69
+ log(` Project: ${projectName}, Depth: ${depth}, Roles: ${roles.join(', ')}, Lang: ${language}`);
70
+ } else {
71
+ banner('Setup Wizard v1.0');
72
+ projectName = await askProjectName(projectDir);
73
+ depth = await askDepth();
74
+ roles = defaultRolesForDepth(depth);
75
+ language = await askLanguage();
76
+ log(` Roles auto-selected by depth: ${roles.join(', ')}`);
77
+ }
78
+
79
+ console.log();
80
+ info('Detecting platform...');
81
+ const adapter = opts.adapter || detectPlatform(projectDir);
82
+ ok(`Platform: ${platformLabel(adapter)}`);
83
+
84
+ info('Setting up project...');
85
+ await setupProject(projectDir, { projectName, depth, roles, language, adapter });
86
+
87
+ printSummary({ projectName, depth, roles, language, adapter });
88
+ }
89
+
90
+ async function askProjectName(projectDir) {
91
+ const guess = guessProjectName(projectDir);
92
+ return ask('[Project] Project name?', guess);
93
+ }
94
+
95
+ async function askDepth() {
96
+ return choose('[Depth] Default workflow depth:', [
97
+ { value: 'quick', label: 'Solo dev, hotfix, familiar code — minimal ceremony' },
98
+ { value: 'standard', label: 'Team, feature — full 6-phase workflow' },
99
+ { value: 'thorough', label: 'Enterprise — full workflow + arch-review + test-plan' },
100
+ ], 'standard');
101
+ }
102
+
103
+ async function askLanguage() {
104
+ return choose('[Lang] Documentation language:', [
105
+ { value: 'vi', label: 'Tiếng Việt' },
106
+ { value: 'en', label: 'English' },
107
+ ], 'vi');
108
+ }
109
+
110
+ function guessProjectName(dir) {
111
+ const base = dir.split(/[/\\]/).pop();
112
+ return base || 'my-project';
113
+ }
114
+
115
+ function parseRolesEnv(rolesStr) {
116
+ if (!rolesStr) return [];
117
+ const valid = ['dev', 'techlead', 'ba', 'qc', 'pm'];
118
+ const parsed = rolesStr.split(',').map(r => r.trim().toLowerCase()).filter(r => valid.includes(r));
119
+ return [...new Set(parsed)];
120
+ }
121
+
122
+ function defaultRolesForDepth(depth) {
123
+ if (depth === 'quick') return ['dev'];
124
+ if (depth === 'thorough') return ['dev', 'techlead', 'ba', 'qc', 'pm'];
125
+ return ['dev', 'techlead'];
126
+ }
127
+
128
+ function normalizeRolesForDepth(parsedRoles, depth) {
129
+ const required = defaultRolesForDepth(depth);
130
+ const merged = [...new Set([...parsedRoles, ...required])];
131
+ const missing = required.filter((r) => !parsedRoles.includes(r));
132
+ if (parsedRoles.length > 0 && missing.length > 0) {
133
+ warn(`DW_ROLES missing required roles for depth "${depth}". Auto-added: ${missing.join(', ')}`);
134
+ }
135
+ return merged;
136
+ }
137
+
138
+ async function setupProject(projectDir, { projectName, depth, roles, language, adapter }) {
139
+ copyCoreDocs(projectDir);
140
+ copyConfig(projectDir, { projectName, depth, roles, language });
141
+ copyAdapterStructure(projectDir);
142
+
143
+ if (adapter === 'claude-cli') {
144
+ copyClaudeFiles(projectDir);
145
+ createMinimalCLAUDEmd(projectDir, projectName);
146
+ } else if (adapter === 'cursor') {
147
+ copyCursorFiles(projectDir);
148
+ copyGenericAdapter(projectDir);
149
+ } else if (adapter === 'generic') {
150
+ copyGenericAdapter(projectDir);
151
+ }
152
+
153
+ createRuntimeDirs(projectDir);
154
+ updateGitignore(projectDir);
155
+ }
156
+
157
+ function copyCoreDocs(projectDir) {
158
+ const src = join(TOOLKIT_ROOT, '.dw', 'core');
159
+ const dst = join(projectDir, '.dw', 'core');
160
+ copyDir(src, dst, { overwrite: true });
161
+ ok('.dw/core/ (WORKFLOW.md, THINKING.md, QUALITY.md, ROLES.md, templates/)');
162
+ }
163
+
164
+ function copyConfig(projectDir, { projectName, depth, roles, language }) {
165
+ const configDir = join(projectDir, '.dw', 'config');
166
+ ensureDir(configDir);
167
+
168
+ const config = buildConfig({ projectName, language, depth, roles });
169
+ writeConfig(join(configDir, 'dw.config.yml'), config);
170
+
171
+ copyFile(
172
+ join(TOOLKIT_ROOT, '.dw', 'config', 'config.schema.json'),
173
+ join(configDir, 'config.schema.json'),
174
+ );
175
+
176
+ const presetsDir = join(configDir, 'presets');
177
+ ensureDir(presetsDir);
178
+ copyDir(join(TOOLKIT_ROOT, '.dw', 'config', 'presets'), presetsDir, { overwrite: true });
179
+
180
+ ok('.dw/config/dw.config.yml + schema + presets');
181
+ }
182
+
183
+ function copyAdapterStructure(projectDir) {
184
+ const adaptersDir = join(projectDir, '.dw', 'adapters');
185
+ copyDir(join(TOOLKIT_ROOT, '.dw', 'adapters'), adaptersDir);
186
+ ok('.dw/adapters/ (claude-cli, generic)');
187
+ }
188
+
189
+ function copyClaudeFiles(projectDir) {
190
+ const src = join(TOOLKIT_ROOT, '.claude');
191
+ const dst = join(projectDir, '.claude');
192
+ const results = copyDir(src, dst, { overwrite: true });
193
+
194
+ const skipCount = results.filter(r => r.action === 'skip').length;
195
+ const copyCount = results.filter(r => r.action === 'copy').length;
196
+
197
+ ok(`.claude/ (${copyCount} files — skills, agents, hooks, rules, templates)`);
198
+ if (skipCount > 0) log(` ${skipCount} existing files preserved`);
199
+ }
200
+
201
+ function createMinimalCLAUDEmd(projectDir, projectName) {
202
+ const dst = join(projectDir, 'CLAUDE.md');
203
+
204
+ if (existsSync(dst)) {
205
+ // User already has their own CLAUDE.md — leave it completely alone
206
+ return;
207
+ }
208
+
209
+ const content = `# ${projectName}
210
+
211
+ > Add your project description here.
212
+
213
+ ---
214
+
215
+ ## Tech Stack
216
+
217
+ <!-- Update with your project's actual stack -->
218
+ - Framework:
219
+ - Database:
220
+ - Testing:
221
+
222
+ ## Project-Specific Rules
223
+
224
+ <!-- Add project-specific rules here -->
225
+ <!-- dw workflow rules are auto-loaded from .claude/rules/ -->
226
+ `;
227
+ writeFileSync(dst, content, 'utf-8');
228
+ ok('CLAUDE.md (minimal project template dw rules are in .claude/rules/)');
229
+ }
230
+
231
+ function copyCursorFiles(projectDir) {
232
+ const rulesDir = join(projectDir, '.cursor', 'rules');
233
+ ensureDir(rulesDir);
234
+
235
+ const ruleContent = `# dw-kit Workflow Rules
236
+ # Auto-generated by dw init --adapter cursor
237
+ # Reference: .dw/core/WORKFLOW.md for full methodology
238
+
239
+ ## Workflow
240
+ Follow the 6-phase workflow: Initialize → Understand → Plan → Execute → Verify → Close.
241
+ Read @.dw/core/WORKFLOW.md for detailed phase instructions.
242
+
243
+ ## Quality
244
+ - Apply TDD: write test implement → refactor
245
+ - Read @.dw/core/QUALITY.md for the 4-layer quality strategy
246
+ - Run test/lint commands from .dw/config/dw.config.yml before committing
247
+
248
+ ## Thinking
249
+ When planning or debugging, apply the thinking framework from @.dw/core/THINKING.md:
250
+ - Critical Thinking: question assumptions
251
+ - Systems Thinking: trace dependencies and side effects
252
+ - First Principles: break down to fundamentals
253
+
254
+ ## Roles
255
+ Check @.dw/core/ROLES.md for role definitions and decision authority.
256
+
257
+ ## Task Tracking
258
+ - Task docs go in .dw/tasks/[task-name]/
259
+ - Use templates from .dw/core/templates/ for context, plan, and progress docs
260
+ `;
261
+
262
+ writeFileSync(join(rulesDir, 'dw-workflow.mdc'), ruleContent, 'utf-8');
263
+ ok('.cursor/rules/dw-workflow.mdc (Cursor rules from core methodology)');
264
+ }
265
+
266
+ function copyGenericAdapter(projectDir) {
267
+ const src = join(TOOLKIT_ROOT, '.dw', 'adapters', 'generic', 'AGENT.md');
268
+ const dst = join(projectDir, 'AGENT.md');
269
+ if (!existsSync(dst)) {
270
+ copyFile(src, dst);
271
+ ok('AGENT.md (generic adapter for Cursor/Windsurf/Copilot)');
272
+ } else {
273
+ warn('AGENT.md already exists — skipping');
274
+ }
275
+ }
276
+
277
+ function createRuntimeDirs(projectDir) {
278
+ for (const dir of ['.dw/tasks', '.dw/docs', '.dw/metrics', '.dw/reports']) {
279
+ ensureDir(join(projectDir, dir));
280
+ }
281
+ ok('.dw/ (tasks, docs, metrics, reports)');
282
+ }
283
+
284
+ function updateGitignore(projectDir) {
285
+ const gitignorePath = join(projectDir, '.gitignore');
286
+ const entriesToAdd = ['CLAUDE.local.md', '.claude/settings.local.json', '.dw/config/dw.config.local.yml'];
287
+
288
+ if (existsSync(gitignorePath)) {
289
+ const content = readFileSync(gitignorePath, 'utf-8');
290
+ const missing = entriesToAdd.filter(e => !content.includes(e));
291
+ if (missing.length > 0) {
292
+ appendFileSync(gitignorePath, `\n# dw-kit\n${missing.join('\n')}\n`, 'utf-8');
293
+ ok('.gitignore updated');
294
+ }
295
+ } else {
296
+ writeFileSync(gitignorePath, `# dw-kit\n${entriesToAdd.join('\n')}\n`, 'utf-8');
297
+ ok('.gitignore created');
298
+ }
299
+ }
300
+
301
+ function printSummary({ projectName, depth, roles, language, adapter }) {
302
+ console.log();
303
+ console.log(` ✅ Setup complete!`);
304
+ console.log();
305
+ console.log(` Project : ${projectName}`);
306
+ console.log(` Depth : ${depth}`);
307
+ console.log(` Roles : ${roles.join(', ')}`);
308
+ console.log(` Language : ${language}`);
309
+ console.log(` Platform : ${platformLabel(adapter)}`);
310
+ console.log();
311
+ console.log(` Files created:`);
312
+ console.log(` .dw/ — core/, config/, adapters/, tasks, docs`);
313
+ if (adapter === 'claude-cli') {
314
+ console.log(` .claude/ — skills, agents, hooks, rules/`);
315
+ console.log(` CLAUDE.md — minimal project template (dw rules in .claude/rules/)`);
316
+ } else if (adapter === 'cursor') {
317
+ console.log(` .cursor/rules/ — workflow rules for Cursor`);
318
+ console.log(` AGENT.md — methodology reference`);
319
+ } else {
320
+ console.log(` AGENT.md — methodology reference`);
321
+ }
322
+ console.log();
323
+ if (adapter === 'claude-cli') {
324
+ console.log(` Next steps:`);
325
+ console.log(` Run: claude (to open Claude Code in this directory in terminal)`);
326
+ console.log(` Run: /dw:flow [task-name]`);
327
+ console.log(` Suggested: Update Tech Stack + rules in CLAUDE.md`);
328
+ } else if (adapter === 'cursor') {
329
+ console.log(` Next steps:`);
330
+ console.log(` 1. Open Cursor in this directory`);
331
+ console.log(` 2. Rules auto-loaded from .cursor/rules/`);
332
+ console.log(` 3. Reference AGENT.md + .dw/core/WORKFLOW.md for guidance`);
333
+ } else {
334
+ console.log(` Next steps:`);
335
+ console.log(` 1. Open your AI coding tool in this directory`);
336
+ console.log(` 2. Reference AGENT.md for workflow guidance`);
337
+ }
338
+ console.log();
339
+ }