agentic-sdlc-wizard 1.71.0 → 1.72.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +46 -0
- package/CLAUDE_CODE_SDLC_WIZARD.md +2 -2
- package/cli/bin/sdlc-wizard.js +7 -5
- package/cli/init.js +61 -9
- package/package.json +1 -1
- package/skills/update/SKILL.md +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,52 @@ All notable changes to the SDLC Wizard.
|
|
|
4
4
|
|
|
5
5
|
> **Note:** This changelog is for humans to read. Don't manually apply these changes - just run the wizard ("Check for SDLC wizard updates") and it handles everything automatically.
|
|
6
6
|
|
|
7
|
+
## [1.72.0] - 2026-05-05
|
|
8
|
+
|
|
9
|
+
### Closes #323: `init --force` no longer silently overwrites CUSTOMIZED files
|
|
10
|
+
|
|
11
|
+
User report 2026-05-05 — `npx agentic-sdlc-wizard check` flags 6 files as CUSTOMIZED then recommends `init --force`, which silently clobbers all 6. Reporter: "the wizard correctly **detected** 6 CUSTOMIZED files and then **recommended a command that would overwrite all 6**." Fully closed in two parts:
|
|
12
|
+
|
|
13
|
+
#### Part 1 — customization-aware recommendation in `check` (PR #325)
|
|
14
|
+
|
|
15
|
+
When `check` finds CUSTOMIZED files alongside an available update, the suggested command now warns and points at `init --dry-run` first instead of silently recommending `init --force`. Pure function `buildUpdateRecommendation(updateInfo, customizedCount)` exported from `cli/init.js`. Backward compat: zero-customized recommendation byte-for-byte unchanged.
|
|
16
|
+
|
|
17
|
+
#### Part 2 — new `init --preserve-customized` flag (PR #328)
|
|
18
|
+
|
|
19
|
+
Composes existing `--force` semantics — when both flags are set:
|
|
20
|
+
|
|
21
|
+
- **CUSTOMIZED** files (sha256 mismatch with template) → action `PRESERVE`, skipped during write, reported in summary footer
|
|
22
|
+
- **MATCH** files → `OVERWRITE` (refresh; effectively no-op since hashes already match)
|
|
23
|
+
- **MISSING** files → `CREATE` (new files added in latest version still get installed)
|
|
24
|
+
|
|
25
|
+
Without `--force`, the flag is a no-op (all existing files SKIP regardless). Updated `check` recommendation now suggests `init --force --preserve-customized` as the "upgrade safely" path when CUSTOMIZED files exist.
|
|
26
|
+
|
|
27
|
+
#### Sample output
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
PRESERVE .claude/hooks/sdlc-prompt-check.sh
|
|
31
|
+
PRESERVE .claude/skills/sdlc/SKILL.md
|
|
32
|
+
OVERWRITE .claude/hooks/tdd-pretool-check.sh
|
|
33
|
+
CREATE .claude/skills/feedback/SKILL.md
|
|
34
|
+
|
|
35
|
+
PRESERVED 2 customized file(s) — review with `init --dry-run` to see what differs from the latest template.
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
#### Test coverage
|
|
39
|
+
|
|
40
|
+
10 new tests in `tests/test-cli.sh` across 3 PRs (78 → 88 green): customization-aware recommendation (2 from #325), null/undefined edge cases (2 from #326 P2 follow-up), preserve-customized core behavior (3 from #328 round 1), no-force-no-op + settings.json MERGE precedence + WIZARD_DOC parity (3 from #328 round-2).
|
|
41
|
+
|
|
42
|
+
#### Deferred
|
|
43
|
+
|
|
44
|
+
Option 3 from #323 (real `update` subcommand with backup directory + per-file diff prompt) — options 1 + 2 close the immediate footgun without a new subcommand and backup-storage convention.
|
|
45
|
+
|
|
46
|
+
#### Files
|
|
47
|
+
|
|
48
|
+
- `cli/init.js` — `buildUpdateRecommendation()` exported; `--preserve-customized` threaded through `init()` → `planOperations()`; `isCustomized()` hash helper; `PRESERVE` action skipped in `executeOperations()`; `printOps()` adds yellow `PRESERVE` color + summary footer
|
|
49
|
+
- `cli/bin/sdlc-wizard.js` — `--preserve-customized` flag + help text
|
|
50
|
+
- `tests/test-cli.sh` — 10 new tests across 3 PRs (78 → 88 green)
|
|
51
|
+
- `package.json`, `.claude-plugin/plugin.json` + `marketplace.json`, `SDLC.md`, `skills/update/SKILL.md`, `CLAUDE_CODE_SDLC_WIZARD.md`, `CHANGELOG.md` (1.71.0 → 1.72.0)
|
|
52
|
+
|
|
7
53
|
## [1.71.0] - 2026-05-05
|
|
8
54
|
|
|
9
55
|
### Token-bloat fix: SDLC skill Cross-Model Review section trimmed
|
|
@@ -2976,7 +2976,7 @@ If deployment fails or post-deploy verification catches issues:
|
|
|
2976
2976
|
|
|
2977
2977
|
**SDLC.md:**
|
|
2978
2978
|
```markdown
|
|
2979
|
-
<!-- SDLC Wizard Version: 1.
|
|
2979
|
+
<!-- SDLC Wizard Version: 1.72.0 -->
|
|
2980
2980
|
<!-- Setup Date: [DATE] -->
|
|
2981
2981
|
<!-- Completed Steps: step-0.1, step-0.2, step-0.4, step-1, step-2, step-3, step-4, step-5, step-6, step-7, step-8, step-9 -->
|
|
2982
2982
|
<!-- Git Workflow: [PRs or Solo] -->
|
|
@@ -4070,7 +4070,7 @@ Walk through updates? (y/n)
|
|
|
4070
4070
|
Store wizard state in `SDLC.md` as metadata comments (invisible to readers, parseable by Claude):
|
|
4071
4071
|
|
|
4072
4072
|
```markdown
|
|
4073
|
-
<!-- SDLC Wizard Version: 1.
|
|
4073
|
+
<!-- SDLC Wizard Version: 1.72.0 -->
|
|
4074
4074
|
<!-- Setup Date: 2026-01-24 -->
|
|
4075
4075
|
<!-- Completed Steps: step-0.1, step-0.2, step-1, step-2, step-3, step-4, step-5, step-6, step-7, step-8, step-9 -->
|
|
4076
4076
|
<!-- Git Workflow: PRs -->
|
package/cli/bin/sdlc-wizard.js
CHANGED
|
@@ -11,6 +11,7 @@ const flags = {
|
|
|
11
11
|
force: args.includes('--force'),
|
|
12
12
|
dryRun: args.includes('--dry-run'),
|
|
13
13
|
json: args.includes('--json'),
|
|
14
|
+
preserveCustomized: args.includes('--preserve-customized'),
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
const positional = args.filter((a) => !a.startsWith('--'));
|
|
@@ -31,11 +32,12 @@ if (args.includes('--help') || args.includes('-h') || !command) {
|
|
|
31
32
|
sdlc-wizard complexity [path] Print mixed-mode tier heuristic (roadmap #233)
|
|
32
33
|
|
|
33
34
|
Options:
|
|
34
|
-
--force
|
|
35
|
-
--
|
|
36
|
-
--
|
|
37
|
-
--
|
|
38
|
-
--
|
|
35
|
+
--force Overwrite existing files (init only)
|
|
36
|
+
--preserve-customized With --force, skip files that have local edits (init only)
|
|
37
|
+
--dry-run Preview changes without writing (init only)
|
|
38
|
+
--json Output as JSON (check / complexity)
|
|
39
|
+
--version Show version
|
|
40
|
+
--help Show this help
|
|
39
41
|
`.trim());
|
|
40
42
|
process.exit(0);
|
|
41
43
|
}
|
package/cli/init.js
CHANGED
|
@@ -130,9 +130,23 @@ function mergeSettings(existingPath, templatePath, force) {
|
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
function planOperations(targetDir, { force }) {
|
|
133
|
+
function planOperations(targetDir, { force, preserveCustomized = false }) {
|
|
134
134
|
const ops = [];
|
|
135
135
|
|
|
136
|
+
// #323 option 2: preserve mode requires hashing each existing file to
|
|
137
|
+
// distinguish CUSTOMIZED from MATCH. Only meaningful with --force; without
|
|
138
|
+
// --force, existing files are SKIP regardless of hash.
|
|
139
|
+
const isCustomized = (srcPath, destPath) => {
|
|
140
|
+
if (!preserveCustomized || !force) return false;
|
|
141
|
+
try {
|
|
142
|
+
const srcHash = crypto.createHash('sha256').update(fs.readFileSync(srcPath)).digest('hex');
|
|
143
|
+
const destHash = crypto.createHash('sha256').update(fs.readFileSync(destPath)).digest('hex');
|
|
144
|
+
return srcHash !== destHash;
|
|
145
|
+
} catch (_) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
136
150
|
for (const file of FILES) {
|
|
137
151
|
const destPath = path.join(targetDir, file.dest);
|
|
138
152
|
const srcPath = path.join(file.base || TEMPLATES_DIR, file.src);
|
|
@@ -154,11 +168,16 @@ function planOperations(targetDir, { force }) {
|
|
|
154
168
|
// Invalid JSON — fall through to normal SKIP/OVERWRITE
|
|
155
169
|
}
|
|
156
170
|
|
|
171
|
+
let action;
|
|
172
|
+
if (!exists) action = 'CREATE';
|
|
173
|
+
else if (isCustomized(srcPath, destPath)) action = 'PRESERVE';
|
|
174
|
+
else action = force ? 'OVERWRITE' : 'SKIP';
|
|
175
|
+
|
|
157
176
|
ops.push({
|
|
158
177
|
src: srcPath,
|
|
159
178
|
dest: destPath,
|
|
160
179
|
relativeDest: file.dest,
|
|
161
|
-
action
|
|
180
|
+
action,
|
|
162
181
|
executable: file.executable || false,
|
|
163
182
|
});
|
|
164
183
|
}
|
|
@@ -166,11 +185,15 @@ function planOperations(targetDir, { force }) {
|
|
|
166
185
|
// Wizard doc
|
|
167
186
|
const wizardDest = path.join(targetDir, 'CLAUDE_CODE_SDLC_WIZARD.md');
|
|
168
187
|
const wizardExists = fs.existsSync(wizardDest);
|
|
188
|
+
let wizardAction;
|
|
189
|
+
if (!wizardExists) wizardAction = 'CREATE';
|
|
190
|
+
else if (isCustomized(WIZARD_DOC, wizardDest)) wizardAction = 'PRESERVE';
|
|
191
|
+
else wizardAction = force ? 'OVERWRITE' : 'SKIP';
|
|
169
192
|
ops.push({
|
|
170
193
|
src: WIZARD_DOC,
|
|
171
194
|
dest: wizardDest,
|
|
172
195
|
relativeDest: 'CLAUDE_CODE_SDLC_WIZARD.md',
|
|
173
|
-
action:
|
|
196
|
+
action: wizardAction,
|
|
174
197
|
executable: false,
|
|
175
198
|
});
|
|
176
199
|
|
|
@@ -183,7 +206,7 @@ function ensureDir(filePath) {
|
|
|
183
206
|
|
|
184
207
|
function executeOperations(ops) {
|
|
185
208
|
for (const op of ops) {
|
|
186
|
-
if (op.action === 'SKIP') continue;
|
|
209
|
+
if (op.action === 'SKIP' || op.action === 'PRESERVE') continue;
|
|
187
210
|
ensureDir(op.dest);
|
|
188
211
|
if (op.action === 'MERGE') {
|
|
189
212
|
fs.writeFileSync(op.dest, op.mergedContent);
|
|
@@ -230,12 +253,18 @@ function updateGitignore(targetDir, { dryRun }) {
|
|
|
230
253
|
}
|
|
231
254
|
|
|
232
255
|
function printOps(ops) {
|
|
256
|
+
let preserveCount = 0;
|
|
233
257
|
for (const op of ops) {
|
|
234
258
|
const color = op.action === 'CREATE' ? GREEN
|
|
235
259
|
: op.action === 'SKIP' ? YELLOW
|
|
236
260
|
: op.action === 'MERGE' ? MAGENTA
|
|
261
|
+
: op.action === 'PRESERVE' ? YELLOW
|
|
237
262
|
: CYAN;
|
|
238
263
|
console.log(` ${color}${op.action}${RESET} ${op.relativeDest}`);
|
|
264
|
+
if (op.action === 'PRESERVE') preserveCount++;
|
|
265
|
+
}
|
|
266
|
+
if (preserveCount > 0) {
|
|
267
|
+
console.log(`\n ${YELLOW}PRESERVED ${preserveCount} customized file(s)${RESET} — review with \`init --dry-run\` to see what differs from the latest template.`);
|
|
239
268
|
}
|
|
240
269
|
}
|
|
241
270
|
|
|
@@ -254,7 +283,7 @@ function invalidateVersionCache({ dryRun }) {
|
|
|
254
283
|
return true;
|
|
255
284
|
}
|
|
256
285
|
|
|
257
|
-
function init(targetDir, { force = false, dryRun = false } = {}) {
|
|
286
|
+
function init(targetDir, { force = false, dryRun = false, preserveCustomized = false } = {}) {
|
|
258
287
|
if (!dryRun && !force) {
|
|
259
288
|
const pluginPaths = detectPluginInstall();
|
|
260
289
|
if (pluginPaths.length > 0) {
|
|
@@ -273,7 +302,7 @@ function init(targetDir, { force = false, dryRun = false } = {}) {
|
|
|
273
302
|
}
|
|
274
303
|
}
|
|
275
304
|
|
|
276
|
-
const ops = planOperations(targetDir, { force });
|
|
305
|
+
const ops = planOperations(targetDir, { force, preserveCustomized });
|
|
277
306
|
|
|
278
307
|
if (dryRun) {
|
|
279
308
|
console.log('Dry run — no files will be written:\n');
|
|
@@ -396,14 +425,37 @@ function check(targetDir, { json = false } = {}) {
|
|
|
396
425
|
}
|
|
397
426
|
}
|
|
398
427
|
if (updateInfo) {
|
|
399
|
-
|
|
400
|
-
|
|
428
|
+
const customizedCount = results.filter((r) => r.status === 'CUSTOMIZED').length;
|
|
429
|
+
for (const line of buildUpdateRecommendation(updateInfo, customizedCount)) {
|
|
430
|
+
console.log(line);
|
|
431
|
+
}
|
|
401
432
|
}
|
|
402
433
|
}
|
|
403
434
|
|
|
404
435
|
return { results, updateInfo, hasDrift, marketplace };
|
|
405
436
|
}
|
|
406
437
|
|
|
438
|
+
// #323: when `check` finds CUSTOMIZED files alongside an available update,
|
|
439
|
+
// the historical recommendation `init --force` would silently clobber them.
|
|
440
|
+
// This builds an update-recommendation block that's customization-aware:
|
|
441
|
+
// no-CUSTOMIZED → recommend --force as before. Has-CUSTOMIZED → warn and
|
|
442
|
+
// recommend `--dry-run` first so the user can preview the diff.
|
|
443
|
+
function buildUpdateRecommendation(updateInfo, customizedCount) {
|
|
444
|
+
if (!updateInfo) return [];
|
|
445
|
+
const lines = [
|
|
446
|
+
`\n ${YELLOW}UPDATE${RESET} v${updateInfo.current} -> v${updateInfo.latest}`,
|
|
447
|
+
];
|
|
448
|
+
if (customizedCount > 0) {
|
|
449
|
+
lines.push(` ${YELLOW}WARNING${RESET}: ${customizedCount} file(s) CUSTOMIZED — \`init --force\` will overwrite them`);
|
|
450
|
+
lines.push(` Preview first: npx agentic-sdlc-wizard init --dry-run`);
|
|
451
|
+
lines.push(` Or upgrade safely: npx agentic-sdlc-wizard init --force --preserve-customized`);
|
|
452
|
+
lines.push(` (\`--preserve-customized\` skips CUSTOMIZED files and updates only MATCH ones.)`);
|
|
453
|
+
} else {
|
|
454
|
+
lines.push(' Run: npx agentic-sdlc-wizard init --force');
|
|
455
|
+
}
|
|
456
|
+
return lines;
|
|
457
|
+
}
|
|
458
|
+
|
|
407
459
|
function checkFile(srcPath, destPath, relativeDest, shouldBeExecutable) {
|
|
408
460
|
if (!fs.existsSync(destPath)) {
|
|
409
461
|
return { file: relativeDest, status: 'MISSING' };
|
|
@@ -519,4 +571,4 @@ function checkMarketplacePaths() {
|
|
|
519
571
|
return results;
|
|
520
572
|
}
|
|
521
573
|
|
|
522
|
-
module.exports = { init, check, planOperations, detectPluginInstall, GITIGNORE_ENTRIES };
|
|
574
|
+
module.exports = { init, check, planOperations, detectPluginInstall, buildUpdateRecommendation, GITIGNORE_ENTRIES };
|
package/package.json
CHANGED
package/skills/update/SKILL.md
CHANGED
|
@@ -93,10 +93,11 @@ Parse CHANGELOG entries between the user's installed version and latest. Present
|
|
|
93
93
|
|
|
94
94
|
```
|
|
95
95
|
Installed: 1.42.0
|
|
96
|
-
Latest: 1.
|
|
96
|
+
Latest: 1.72.0
|
|
97
97
|
|
|
98
98
|
What changed:
|
|
99
|
-
- [1.
|
|
99
|
+
- [1.72.0] #323 closed — customization-aware `check` recommendation + new `--preserve-customized` flag. `init --force --preserve-customized` skips CUSTOMIZED files (action `PRESERVE`), still OVERWRITEs MATCH and CREATEs MISSING. Default `init --force` unchanged. 10 tests.
|
|
100
|
+
- [1.71.0] token-bloat phase 3 — `skills/sdlc/SKILL.md` Cross-Model Review trimmed (~70 → ~20 lines, -427 tokens). Full protocol + examples moved to canonical `CLAUDE_CODE_SDLC_WIZARD.md` "Cross-Model Review Loop" section.
|
|
100
101
|
- [1.70.0] token-bloat fix phase 2 — `hooks/tdd-pretool-check.sh` TDD CHECK JSON nudge fires once per CC `session_id` instead of every src/ edit. Saves ~0.5-1.5K tokens/session.
|
|
101
102
|
- [1.69.0] token-bloat fix phase 1 — `hooks/sdlc-prompt-check.sh` BASELINE block fires once per CC `session_id`. Saves ~12K tokens/session.
|
|
102
103
|
- [1.68.0–1.65.0] roadmap hygiene — five paperwork closes: #97 Anthropic Policy NO-GO + AAR-paper validating parallel; #99 AutoGPT NO-GO; #95 Nous NO-GO; #243 token-history liveness verified; #210 Node-24 false-green; #235 Thoughtworks AI Evals NO-GO. **6/6 external-product audits NO-GO** (continues #76, #77). Research write-ups in `.reviews/research-*.md`.
|