dw-kit 1.1.0 → 1.2.1
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 +9 -11
- package/.claude/hooks/privacy-block.sh +94 -0
- package/.claude/hooks/scout-block.sh +82 -0
- package/.claude/hooks/session-init.sh +85 -0
- package/.claude/hooks/stop-check.sh +36 -0
- package/.claude/rules/dw-core.md +100 -0
- package/.claude/rules/dw-skills.md +53 -0
- package/.claude/settings.json +101 -71
- package/.claude/skills/dw-kit-report/SKILL.md +152 -0
- package/.claude/skills/dw-research/SKILL.md +17 -1
- package/.claude/templates/agent-report.md +35 -0
- package/.dw/config/dw.config.yml +82 -82
- package/.dw/core/AGENTS.md +53 -0
- package/CLAUDE.md +27 -99
- package/README.md +127 -119
- package/package.json +84 -57
- package/src/cli.mjs +100 -92
- package/src/commands/init.mjs +17 -17
- package/src/commands/upgrade.mjs +297 -262
- package/src/lib/config.mjs +31 -2
- package/src/lib/copy.mjs +118 -110
- package/scripts/e2e-local-check.sh +0 -75
- package/src/__fixtures__/claude-cli-bug-snippet.js +0 -15
- package/src/smoke-test.mjs +0 -351
package/src/commands/init.mjs
CHANGED
|
@@ -135,7 +135,7 @@ async function setupProject(projectDir, { projectName, depth, roles, language, a
|
|
|
135
135
|
|
|
136
136
|
if (adapter === 'claude-cli') {
|
|
137
137
|
copyClaudeFiles(projectDir);
|
|
138
|
-
|
|
138
|
+
createMinimalCLAUDEmd(projectDir, projectName);
|
|
139
139
|
} else if (adapter === 'cursor') {
|
|
140
140
|
copyCursorFiles(projectDir);
|
|
141
141
|
copyGenericAdapter(projectDir);
|
|
@@ -191,34 +191,34 @@ function copyClaudeFiles(projectDir) {
|
|
|
191
191
|
if (skipCount > 0) log(` ${skipCount} existing files preserved`);
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
function
|
|
195
|
-
const src = join(TOOLKIT_ROOT, 'CLAUDE.md');
|
|
194
|
+
function createMinimalCLAUDEmd(projectDir, projectName) {
|
|
196
195
|
const dst = join(projectDir, 'CLAUDE.md');
|
|
197
196
|
|
|
198
197
|
if (existsSync(dst)) {
|
|
199
|
-
|
|
198
|
+
// User already has their own CLAUDE.md — leave it completely alone
|
|
200
199
|
return;
|
|
201
200
|
}
|
|
202
201
|
|
|
203
|
-
|
|
202
|
+
const content = `# ${projectName}
|
|
203
|
+
|
|
204
|
+
> Add your project description here.
|
|
204
205
|
|
|
205
|
-
const techStackSection = `
|
|
206
206
|
---
|
|
207
207
|
|
|
208
208
|
## Tech Stack
|
|
209
209
|
|
|
210
210
|
<!-- Update with your project's actual stack -->
|
|
211
|
-
- Framework:
|
|
212
|
-
- Database:
|
|
213
|
-
- Testing:
|
|
211
|
+
- Framework:
|
|
212
|
+
- Database:
|
|
213
|
+
- Testing:
|
|
214
214
|
|
|
215
215
|
## Project-Specific Rules
|
|
216
216
|
|
|
217
|
-
<!-- Add project-specific rules -->
|
|
218
|
-
-
|
|
217
|
+
<!-- Add project-specific rules here -->
|
|
218
|
+
<!-- dw workflow rules are auto-loaded from .claude/rules/ -->
|
|
219
219
|
`;
|
|
220
|
-
|
|
221
|
-
ok('CLAUDE.md (
|
|
220
|
+
writeFileSync(dst, content, 'utf-8');
|
|
221
|
+
ok('CLAUDE.md (minimal project template — dw rules are in .claude/rules/)');
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
function copyCursorFiles(projectDir) {
|
|
@@ -276,7 +276,7 @@ function createRuntimeDirs(projectDir) {
|
|
|
276
276
|
|
|
277
277
|
function updateGitignore(projectDir) {
|
|
278
278
|
const gitignorePath = join(projectDir, '.gitignore');
|
|
279
|
-
const entriesToAdd = ['CLAUDE.local.md', '.claude/settings.local.json'];
|
|
279
|
+
const entriesToAdd = ['CLAUDE.local.md', '.claude/settings.local.json', '.dw/config/dw.config.local.yml'];
|
|
280
280
|
|
|
281
281
|
if (existsSync(gitignorePath)) {
|
|
282
282
|
const content = readFileSync(gitignorePath, 'utf-8');
|
|
@@ -304,8 +304,8 @@ function printSummary({ projectName, depth, roles, language, adapter }) {
|
|
|
304
304
|
console.log(` Files created:`);
|
|
305
305
|
console.log(` .dw/ — core/, config/, adapters/, tasks, docs`);
|
|
306
306
|
if (adapter === 'claude-cli') {
|
|
307
|
-
console.log(` .claude/ — skills, agents, hooks, rules
|
|
308
|
-
console.log(` CLAUDE.md`);
|
|
307
|
+
console.log(` .claude/ — skills, agents, hooks, rules/`);
|
|
308
|
+
console.log(` CLAUDE.md — minimal project template (dw rules in .claude/rules/)`);
|
|
309
309
|
} else if (adapter === 'cursor') {
|
|
310
310
|
console.log(` .cursor/rules/ — workflow rules for Cursor`);
|
|
311
311
|
console.log(` AGENT.md — methodology reference`);
|
|
@@ -317,7 +317,7 @@ function printSummary({ projectName, depth, roles, language, adapter }) {
|
|
|
317
317
|
console.log(` Next steps:`);
|
|
318
318
|
console.log(` Run: claude (to open Claude Code in this directory in terminal)`);
|
|
319
319
|
console.log(` Run: /dw-flow [task-name]`);
|
|
320
|
-
console.log(` Suggested: Update Tech Stack in CLAUDE.md
|
|
320
|
+
console.log(` Suggested: Update Tech Stack + rules in CLAUDE.md`);
|
|
321
321
|
} else if (adapter === 'cursor') {
|
|
322
322
|
console.log(` Next steps:`);
|
|
323
323
|
console.log(` 1. Open Cursor in this directory`);
|
package/src/commands/upgrade.mjs
CHANGED
|
@@ -1,262 +1,297 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';
|
|
2
|
-
import { join, resolve } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { header, ok, warn, err, info, log, dry } from '../lib/ui.mjs';
|
|
5
|
-
import { loadConfig, writeConfig, getToolkitVersions } from '../lib/config.mjs';
|
|
6
|
-
import { diffDirs, copyDir, copyFile } from '../lib/copy.mjs';
|
|
7
|
-
|
|
8
|
-
const TOOLKIT_ROOT = resolve(fileURLToPath(import.meta.url), '..', '..', '..');
|
|
9
|
-
|
|
10
|
-
export async function upgradeCommand(opts) {
|
|
11
|
-
const projectDir = process.cwd();
|
|
12
|
-
const configPath = join(projectDir, '.dw', 'config', 'dw.config.yml');
|
|
13
|
-
|
|
14
|
-
if (!existsSync(configPath)) {
|
|
15
|
-
err('No .dw/config/dw.config.yml found. Run `dw init` first.');
|
|
16
|
-
process.exit(1);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const projectConfig = loadConfig(configPath);
|
|
20
|
-
const projectVersions = getToolkitVersions(projectConfig);
|
|
21
|
-
|
|
22
|
-
const toolkitPkg = JSON.parse(readFileSync(join(TOOLKIT_ROOT, 'package.json'), 'utf-8'));
|
|
23
|
-
const toolkitConfig = loadConfig(join(TOOLKIT_ROOT, '.dw', 'config', 'dw.config.yml'));
|
|
24
|
-
const toolkitVersions = getToolkitVersions(toolkitConfig);
|
|
25
|
-
|
|
26
|
-
header('dw-kit Upgrade');
|
|
27
|
-
log(`Installed (package) : v${toolkitPkg.version}`);
|
|
28
|
-
log(`Project core : ${projectVersions.core}`);
|
|
29
|
-
log(`Toolkit core : ${toolkitVersions.core}`);
|
|
30
|
-
if (opts.dryRun) log('Mode: DRY RUN (no changes)');
|
|
31
|
-
console.log();
|
|
32
|
-
|
|
33
|
-
if (opts.check) {
|
|
34
|
-
if (projectVersions.core === toolkitVersions.core) {
|
|
35
|
-
ok('Already up to date.');
|
|
36
|
-
} else {
|
|
37
|
-
log(`Update available: ${projectVersions.core} → ${toolkitVersions.core}`);
|
|
38
|
-
}
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const layer = opts.layer || 'all';
|
|
43
|
-
let totalChanges = 0;
|
|
44
|
-
|
|
45
|
-
if (layer === 'all' || layer === 'core') {
|
|
46
|
-
totalChanges += upgradeCore(projectDir, opts);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (layer === 'all' || layer === 'platform') {
|
|
50
|
-
totalChanges += upgradePlatform(projectDir, opts);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { header, ok, warn, err, info, log, dry } from '../lib/ui.mjs';
|
|
5
|
+
import { loadConfig, writeConfig, getToolkitVersions } from '../lib/config.mjs';
|
|
6
|
+
import { diffDirs, copyDir, copyFile } from '../lib/copy.mjs';
|
|
7
|
+
|
|
8
|
+
const TOOLKIT_ROOT = resolve(fileURLToPath(import.meta.url), '..', '..', '..');
|
|
9
|
+
|
|
10
|
+
export async function upgradeCommand(opts) {
|
|
11
|
+
const projectDir = process.cwd();
|
|
12
|
+
const configPath = join(projectDir, '.dw', 'config', 'dw.config.yml');
|
|
13
|
+
|
|
14
|
+
if (!existsSync(configPath)) {
|
|
15
|
+
err('No .dw/config/dw.config.yml found. Run `dw init` first.');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const projectConfig = loadConfig(configPath);
|
|
20
|
+
const projectVersions = getToolkitVersions(projectConfig);
|
|
21
|
+
|
|
22
|
+
const toolkitPkg = JSON.parse(readFileSync(join(TOOLKIT_ROOT, 'package.json'), 'utf-8'));
|
|
23
|
+
const toolkitConfig = loadConfig(join(TOOLKIT_ROOT, '.dw', 'config', 'dw.config.yml'));
|
|
24
|
+
const toolkitVersions = getToolkitVersions(toolkitConfig);
|
|
25
|
+
|
|
26
|
+
header('dw-kit Upgrade');
|
|
27
|
+
log(`Installed (package) : v${toolkitPkg.version}`);
|
|
28
|
+
log(`Project core : ${projectVersions.core}`);
|
|
29
|
+
log(`Toolkit core : ${toolkitVersions.core}`);
|
|
30
|
+
if (opts.dryRun) log('Mode: DRY RUN (no changes)');
|
|
31
|
+
console.log();
|
|
32
|
+
|
|
33
|
+
if (opts.check) {
|
|
34
|
+
if (projectVersions.core === toolkitVersions.core) {
|
|
35
|
+
ok('Already up to date.');
|
|
36
|
+
} else {
|
|
37
|
+
log(`Update available: ${projectVersions.core} → ${toolkitVersions.core}`);
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const layer = opts.layer || 'all';
|
|
43
|
+
let totalChanges = 0;
|
|
44
|
+
|
|
45
|
+
if (layer === 'all' || layer === 'core') {
|
|
46
|
+
totalChanges += upgradeCore(projectDir, opts);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (layer === 'all' || layer === 'platform') {
|
|
50
|
+
totalChanges += upgradePlatform(projectDir, opts);
|
|
51
|
+
upgradeGitattributes(projectDir, opts);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (layer === 'all' || layer === 'capability') {
|
|
55
|
+
totalChanges += upgradeCapability(projectDir, opts);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
upgradeScripts(projectDir, opts);
|
|
59
|
+
upgradeConfigSchema(projectDir, opts);
|
|
60
|
+
|
|
61
|
+
if (!opts.dryRun && totalChanges > 0) {
|
|
62
|
+
updateVersionTracking(configPath, projectConfig, toolkitVersions);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log();
|
|
66
|
+
header(opts.dryRun ? 'DRY RUN complete — no changes made' : `Upgrade complete (${totalChanges} files updated)`);
|
|
67
|
+
if (opts.dryRun) log('Run without --dry-run to apply.');
|
|
68
|
+
console.log();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function upgradeCore(projectDir, opts) {
|
|
72
|
+
info('Layer 0: Methodology Core (.dw/core/)');
|
|
73
|
+
const src = join(TOOLKIT_ROOT, '.dw', 'core');
|
|
74
|
+
const dst = join(projectDir, '.dw', 'core');
|
|
75
|
+
|
|
76
|
+
const diff = diffDirs(src, dst);
|
|
77
|
+
reportDiff(diff);
|
|
78
|
+
|
|
79
|
+
if (diff.added.length === 0 && diff.modified.length === 0) {
|
|
80
|
+
ok('Core files are up to date');
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const filesToUpdate = [...diff.added, ...diff.modified];
|
|
85
|
+
for (const file of filesToUpdate) {
|
|
86
|
+
if (opts.dryRun) {
|
|
87
|
+
dry(`${diff.added.includes(file) ? 'add' : 'update'} .dw/core/${file}`);
|
|
88
|
+
} else {
|
|
89
|
+
copyFile(join(src, file), join(dst, file));
|
|
90
|
+
ok(`.dw/core/${file}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return filesToUpdate.length;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function upgradePlatform(projectDir, opts) {
|
|
97
|
+
info('Layer 1: Platform Files (.claude/)');
|
|
98
|
+
const src = join(TOOLKIT_ROOT, '.claude');
|
|
99
|
+
const dst = join(projectDir, '.claude');
|
|
100
|
+
|
|
101
|
+
if (!existsSync(dst)) {
|
|
102
|
+
warn('.claude/ not found — skipping platform upgrade (run dw init first)');
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const overridesDir = join(projectDir, '.dw', 'adapters', 'claude-cli', 'overrides');
|
|
107
|
+
const diff = diffDirs(src, dst);
|
|
108
|
+
reportDiff(diff);
|
|
109
|
+
|
|
110
|
+
if (diff.added.length === 0 && diff.modified.length === 0) {
|
|
111
|
+
ok('Platform files are up to date');
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let count = 0;
|
|
116
|
+
const filesToUpdate = [...diff.added, ...diff.modified];
|
|
117
|
+
for (const file of filesToUpdate) {
|
|
118
|
+
const overridePath = join(overridesDir, file);
|
|
119
|
+
if (existsSync(overridePath)) {
|
|
120
|
+
warn(`${file}: override exists → keeping your version`);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (opts.dryRun) {
|
|
125
|
+
dry(`${diff.added.includes(file) ? 'add' : 'update'} .claude/${file}`);
|
|
126
|
+
} else {
|
|
127
|
+
copyFile(join(src, file), join(dst, file));
|
|
128
|
+
ok(`.claude/${file}`);
|
|
129
|
+
}
|
|
130
|
+
count++;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
copyExtensions(projectDir, opts);
|
|
134
|
+
mergeSettingsJson(projectDir, opts);
|
|
135
|
+
|
|
136
|
+
return count;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function copyExtensions(projectDir, opts) {
|
|
140
|
+
const extDir = join(projectDir, '.dw', 'adapters', 'claude-cli', 'extensions');
|
|
141
|
+
const skillsDir = join(projectDir, '.claude', 'skills');
|
|
142
|
+
|
|
143
|
+
if (!existsSync(extDir)) return;
|
|
144
|
+
|
|
145
|
+
let count = 0;
|
|
146
|
+
try {
|
|
147
|
+
const entries = readdirSync(extDir, { withFileTypes: true });
|
|
148
|
+
for (const entry of entries) {
|
|
149
|
+
if (!entry.isDirectory() || entry.name === '.gitkeep') continue;
|
|
150
|
+
const src = join(extDir, entry.name);
|
|
151
|
+
const dst = join(skillsDir, entry.name);
|
|
152
|
+
if (opts.dryRun) {
|
|
153
|
+
dry(`install extension: ${entry.name}`);
|
|
154
|
+
} else {
|
|
155
|
+
copyDir(src, dst, { overwrite: true });
|
|
156
|
+
ok(`Extension '${entry.name}' installed`);
|
|
157
|
+
}
|
|
158
|
+
count++;
|
|
159
|
+
}
|
|
160
|
+
} catch { /* empty extensions dir */ }
|
|
161
|
+
if (count === 0) log('No extensions found');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function mergeSettingsJson(projectDir, opts) {
|
|
165
|
+
const toolkitSettings = join(TOOLKIT_ROOT, '.claude', 'settings.json');
|
|
166
|
+
const projectSettings = join(projectDir, '.claude', 'settings.json');
|
|
167
|
+
|
|
168
|
+
if (!existsSync(toolkitSettings) || !existsSync(projectSettings)) return;
|
|
169
|
+
|
|
170
|
+
if (opts.dryRun) {
|
|
171
|
+
dry('merge .claude/settings.json');
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const template = JSON.parse(readFileSync(toolkitSettings, 'utf-8'));
|
|
177
|
+
const current = JSON.parse(readFileSync(projectSettings, 'utf-8'));
|
|
178
|
+
const merged = deepMerge(template, current);
|
|
179
|
+
writeFileSync(projectSettings, JSON.stringify(merged, null, 2) + '\n', 'utf-8');
|
|
180
|
+
ok('settings.json: merged');
|
|
181
|
+
} catch (e) {
|
|
182
|
+
warn(`settings.json merge failed: ${e.message}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function deepMerge(base, override) {
|
|
187
|
+
const result = { ...base };
|
|
188
|
+
for (const [key, val] of Object.entries(override)) {
|
|
189
|
+
if (key in result && typeof result[key] === 'object' && !Array.isArray(result[key]) && typeof val === 'object' && !Array.isArray(val)) {
|
|
190
|
+
result[key] = deepMerge(result[key], val);
|
|
191
|
+
} else {
|
|
192
|
+
result[key] = val;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function upgradeGitattributes(projectDir, opts) {
|
|
199
|
+
info('Gitattributes');
|
|
200
|
+
const dst = join(projectDir, '.gitattributes');
|
|
201
|
+
|
|
202
|
+
// Entries dw-kit needs in the user's repo to survive git checkout on Windows
|
|
203
|
+
const requiredEntries = [
|
|
204
|
+
'.claude/hooks/*.sh text eol=lf',
|
|
205
|
+
'.claude/skills/**/*.sh text eol=lf',
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
const existing = existsSync(dst)
|
|
209
|
+
? readFileSync(dst, 'utf-8').replace(/\r\n/g, '\n')
|
|
210
|
+
: '';
|
|
211
|
+
|
|
212
|
+
const missing = requiredEntries.filter(e => !existing.includes(e));
|
|
213
|
+
|
|
214
|
+
if (missing.length === 0) {
|
|
215
|
+
ok('.gitattributes: dw-kit entries already present');
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (opts.dryRun) {
|
|
220
|
+
missing.forEach(e => dry(`add to .gitattributes: ${e}`));
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Append block (idempotent: only adds what's missing)
|
|
225
|
+
const separator = existing.length > 0 && !existing.endsWith('\n') ? '\n' : '';
|
|
226
|
+
const block = `${separator}\n# dw-kit: enforce LF for shell scripts (cross-platform safety)\n${missing.join('\n')}\n`;
|
|
227
|
+
writeFileSync(dst, existing + block, 'utf-8');
|
|
228
|
+
ok(`.gitattributes: added ${missing.length} dw-kit hook entr${missing.length > 1 ? 'ies' : 'y'}`);
|
|
229
|
+
log(' → Tip: run `git add --renormalize .claude/hooks/` to normalize any existing checked-out files');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function upgradeCapability(projectDir, opts) {
|
|
233
|
+
info('Layer 2: Capability Config');
|
|
234
|
+
ok('Capability layer is config-driven — no file changes needed');
|
|
235
|
+
log('Review claude: section in .dw/config/dw.config.yml for new options');
|
|
236
|
+
return 0;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function upgradeScripts(projectDir, opts) {
|
|
240
|
+
info('Scripts');
|
|
241
|
+
const src = join(TOOLKIT_ROOT, 'scripts');
|
|
242
|
+
const dst = join(projectDir, 'scripts');
|
|
243
|
+
|
|
244
|
+
if (!existsSync(src)) return;
|
|
245
|
+
|
|
246
|
+
const diff = diffDirs(src, dst);
|
|
247
|
+
if (diff.added.length === 0 && diff.modified.length === 0) {
|
|
248
|
+
ok('Scripts are up to date');
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
for (const file of [...diff.added, ...diff.modified]) {
|
|
253
|
+
if (opts.dryRun) {
|
|
254
|
+
dry(`update scripts/${file}`);
|
|
255
|
+
} else {
|
|
256
|
+
copyFile(join(src, file), join(dst, file));
|
|
257
|
+
ok(`scripts/${file}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function upgradeConfigSchema(projectDir, opts) {
|
|
263
|
+
const src = join(TOOLKIT_ROOT, '.dw', 'config', 'config.schema.json');
|
|
264
|
+
const dst = join(projectDir, '.dw', 'config', 'config.schema.json');
|
|
265
|
+
|
|
266
|
+
if (!existsSync(src)) return;
|
|
267
|
+
if (existsSync(dst)) {
|
|
268
|
+
const srcContent = readFileSync(src, 'utf-8');
|
|
269
|
+
const dstContent = readFileSync(dst, 'utf-8');
|
|
270
|
+
if (srcContent === dstContent) return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (opts.dryRun) {
|
|
274
|
+
dry('update .dw/config/config.schema.json');
|
|
275
|
+
} else {
|
|
276
|
+
copyFile(src, dst);
|
|
277
|
+
ok('.dw/config/config.schema.json updated');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function updateVersionTracking(configPath, config, toolkitVersions) {
|
|
282
|
+
const today = new Date().toISOString().split('T')[0];
|
|
283
|
+
if (!config._toolkit) config._toolkit = {};
|
|
284
|
+
config._toolkit.core_version = toolkitVersions.core;
|
|
285
|
+
config._toolkit.platform_version = toolkitVersions.platform;
|
|
286
|
+
config._toolkit.capability_version = toolkitVersions.capability;
|
|
287
|
+
config._toolkit.last_upgrade = today;
|
|
288
|
+
|
|
289
|
+
writeConfig(configPath, config);
|
|
290
|
+
ok(`Version tracking updated: core=${toolkitVersions.core}, date=${today}`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function reportDiff(diff) {
|
|
294
|
+
if (diff.added.length > 0) log(` New files: ${diff.added.length}`);
|
|
295
|
+
if (diff.modified.length > 0) log(` Modified: ${diff.modified.length}`);
|
|
296
|
+
if (diff.unchanged.length > 0) log(` Unchanged: ${diff.unchanged.length}`);
|
|
297
|
+
}
|