agentic-sdlc-wizard 1.71.0 → 1.73.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.
@@ -13,7 +13,7 @@
13
13
  "name": "sdlc-wizard",
14
14
  "source": ".",
15
15
  "description": "SDLC enforcement for AI agents — TDD, planning, self-review, CI shepherd",
16
- "version": "1.71.0",
16
+ "version": "1.73.0",
17
17
  "author": {
18
18
  "name": "Stefan Ayala"
19
19
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sdlc-wizard",
3
- "version": "1.71.0",
3
+ "version": "1.73.0",
4
4
  "description": "SDLC enforcement for AI agents — TDD, planning, self-review, CI shepherd",
5
5
  "author": {
6
6
  "name": "Stefan Ayala",
package/CHANGELOG.md CHANGED
@@ -4,6 +4,91 @@ 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.73.0] - 2026-05-06
8
+
9
+ ### Fix: PreCompact hook no longer false-positives on stale `.git/REBASE_HEAD`
10
+
11
+ `hooks/precompact-seam-check.sh` was treating any presence of `.git/REBASE_HEAD` as "rebase in progress" and blocking manual `/compact`. But `REBASE_HEAD` is just a rebase-related ref (the stopped/replayed commit) that git can leave behind after a clean rebase finishes — the authoritative "rebase in progress" signal is the `rebase-merge/` or `rebase-apply/` directory (which is what `git status` keys on too). Hit live in this repo 2026-05-05 — yesterday's clean rebase left `REBASE_HEAD` behind, the user's manual `/compact` was blocked, and clearing it required `rm .git/REBASE_HEAD` by hand.
12
+
13
+ The OR-chain at line 227 now drops the `REBASE_HEAD` predicate; only the `rebase-{merge,apply}/` dir checks remain. Two new tests cover the fix:
14
+
15
+ - `test_precompact_silent_on_stale_rebase_head_alone` — positive: `rc=0` + empty stderr when only `REBASE_HEAD` exists
16
+ - `test_precompact_blocks_on_rebase_head_with_rebase_merge_dir` — negative control: still blocks on real in-flight rebase (REBASE_HEAD + rebase-merge dir together)
17
+
18
+ 156/156 hook tests green. Codex round 1 CERTIFIED 9/10 (one P2 comment-accuracy nit caught — fixed: `REBASE_HEAD` is the stopped/replayed commit, not the original branch tip, which is `ORIG_HEAD`).
19
+
20
+ PR #330.
21
+
22
+ ### GC: -460 LOC of stale review/plan artifacts (#236 bloat hunt)
23
+
24
+ `.reviews/` is gitignored, but 14 handoff/preflight/round-N review files for now-merged PRs were committed before that gitignore line landed. They held no ongoing reference value. `plans/CATCHUP.md` captured the v2.1.15 → v2.1.81 catch-up (March 2026) — historical context lives in CHANGELOG (v1.8.0 entry); the plan doc was dead weight.
25
+
26
+ Deleted (15 files):
27
+
28
+ - `.reviews/baseline-fires-once-001/{round-1,round-2}-review.md`
29
+ - `.reviews/skill-cross-model-trim-001/{round-1,round-2,round-3}-review.md`
30
+ - `.reviews/tdd-pretool-fires-once-001/{round-1,round-2}-review.md`
31
+ - `.reviews/preflight-{allowed-tools-permissions,baseline-fires-once,model-pin-opt-in,precompact-seam,skill-cross-model-trim,staleness-nudge,tdd-pretool-fires-once}-001.md`
32
+ - `plans/CATCHUP.md`
33
+
34
+ Kept (still load-bearing):
35
+
36
+ - `.reviews/research-95/97/99/206/235.md` (cited from ROADMAP rows)
37
+ - `.reviews/experiment-tracking.md` (asserted by `tests/test-workflow-triggers.sh:2189`)
38
+ - `plans/AUTO_SELF_UPDATE.md` (still annotated with #231 phase notes)
39
+
40
+ Hooks 156/156, cli 88/88, workflow 176/176, docs 35/35 — all green post-deletion.
41
+
42
+ PR #331.
43
+
44
+ ---
45
+
46
+ ## [1.72.0] - 2026-05-05
47
+
48
+ ### Closes #323: `init --force` no longer silently overwrites CUSTOMIZED files
49
+
50
+ 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:
51
+
52
+ #### Part 1 — customization-aware recommendation in `check` (PR #325)
53
+
54
+ 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.
55
+
56
+ #### Part 2 — new `init --preserve-customized` flag (PR #328)
57
+
58
+ Composes existing `--force` semantics — when both flags are set:
59
+
60
+ - **CUSTOMIZED** files (sha256 mismatch with template) → action `PRESERVE`, skipped during write, reported in summary footer
61
+ - **MATCH** files → `OVERWRITE` (refresh; effectively no-op since hashes already match)
62
+ - **MISSING** files → `CREATE` (new files added in latest version still get installed)
63
+
64
+ 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.
65
+
66
+ #### Sample output
67
+
68
+ ```
69
+ PRESERVE .claude/hooks/sdlc-prompt-check.sh
70
+ PRESERVE .claude/skills/sdlc/SKILL.md
71
+ OVERWRITE .claude/hooks/tdd-pretool-check.sh
72
+ CREATE .claude/skills/feedback/SKILL.md
73
+
74
+ PRESERVED 2 customized file(s) — review with `init --dry-run` to see what differs from the latest template.
75
+ ```
76
+
77
+ #### Test coverage
78
+
79
+ 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).
80
+
81
+ #### Deferred
82
+
83
+ 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.
84
+
85
+ #### Files
86
+
87
+ - `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
88
+ - `cli/bin/sdlc-wizard.js` — `--preserve-customized` flag + help text
89
+ - `tests/test-cli.sh` — 10 new tests across 3 PRs (78 → 88 green)
90
+ - `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)
91
+
7
92
  ## [1.71.0] - 2026-05-05
8
93
 
9
94
  ### 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.71.0 -->
2979
+ <!-- SDLC Wizard Version: 1.73.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.71.0 -->
4073
+ <!-- SDLC Wizard Version: 1.73.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 -->
@@ -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 Overwrite existing files (init only)
35
- --dry-run Preview changes without writing (init only)
36
- --json Output as JSON (check / complexity)
37
- --version Show version
38
- --help Show this help
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: exists ? (force ? 'OVERWRITE' : 'SKIP') : 'CREATE',
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: wizardExists ? (force ? 'OVERWRITE' : 'SKIP') : 'CREATE',
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
- console.log(`\n ${YELLOW}UPDATE${RESET} v${updateInfo.current} -> v${updateInfo.latest}`);
400
- console.log(' Run: npx agentic-sdlc-wizard init --force');
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 };
@@ -218,7 +218,14 @@ esac
218
218
  # Unknown value (e.g. typo "bogus") → also falls through to real check
219
219
  # rather than silently bypassing safety. The safer-than-the-typo path.
220
220
  if [ "$DRY_RUN_GIT_HANDLED" -eq 0 ] && [ -d "$GITDIR" ]; then
221
- if [ -e "$GITDIR/REBASE_HEAD" ] || [ -d "$GITDIR/rebase-merge" ] || [ -d "$GITDIR/rebase-apply" ]; then
221
+ # Only the rebase-{merge,apply}/ dir is authoritative for "rebase in progress."
222
+ # .git/REBASE_HEAD is a rebase-related ref (the stopped/replayed commit) that
223
+ # is not authoritative on its own — it can persist as a stale marker after a
224
+ # rebase finishes cleanly: git removes the dir but leaves REBASE_HEAD around
225
+ # for diff/log lookups. Including it in the OR-chain caused false-positive
226
+ # HOLDs that blocked manual /compact for users whose previous rebase had
227
+ # completed (hit live 2026-05-05). `git status` keys on the dirs too.
228
+ if [ -d "$GITDIR/rebase-merge" ] || [ -d "$GITDIR/rebase-apply" ]; then
222
229
  HOLD_REASONS="${HOLD_REASONS} - Git rebase in progress. Compacting mid-rebase loses the operation's context.
223
230
  Resolve: finish or abort the rebase before /compact."$'\n'
224
231
  fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-sdlc-wizard",
3
- "version": "1.71.0",
3
+ "version": "1.73.0",
4
4
  "description": "SDLC enforcement for Claude Code — hooks, skills, and wizard setup in one command",
5
5
  "bin": {
6
6
  "sdlc-wizard": "cli/bin/sdlc-wizard.js"
@@ -93,12 +93,12 @@ 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.71.0
96
+ Latest: 1.73.0
97
97
 
98
98
  What changed:
99
- - [1.71.0] token-bloat fix phase 3 — `skills/sdlc/SKILL.md` Cross-Model Review section trimmed from ~70 lines to ~20 (4995 → 4568 tokens). Decision-making + 4-step protocol summary + convergence rule kept; full JSON examples / codex commands moved to `CLAUDE_CODE_SDLC_WIZARD.md` "Cross-Model Review Loop" canonical section (which also gained Anti-patterns + Multi-reviewer + Non-code-domain subsections). Saves ~427 tokens per SDLC skill auto-invoke. Codex round 1 caught 3 test assertions broken by initial trim; round 2 fixes restored constraints in tighter prose.
100
- - [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
- - [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.
99
+ - [1.73.0] precompact stale REBASE_HEAD fix + bloat sweep — `hooks/precompact-seam-check.sh` no longer false-positive HOLDs `/compact` when a finished rebase left REBASE_HEAD behind without `rebase-{merge,apply}/` dirs (hit live 2026-05-05). 15 tracked review/plan artifacts deleted (-460 LOC).
100
+ - [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.
101
+ - [1.71.0–1.69.0] token-bloat sweep #236three phases: BASELINE block fires once per `session_id` (-12K/session), TDD CHECK fires once per `session_id` (-0.5-1.5K/session), `skills/sdlc/SKILL.md` Cross-Model Review trimmed (full protocol moved to canonical wizard doc).
102
102
  - [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`.
103
103
  - [1.64.0] XDLC ecosystem cross-references — README, wizard doc, and ROADMAP now cross-reference all three sibling packages (`agentic-sdlc-wizard`, `codex-sdlc-wizard`, `claude-gdlc-wizard`). New "Ecosystem (Sibling Projects)" section in README. 3 new doc-consistency tests prevent drift.
104
104
  - [1.63.0] cache-cost observability closeout (#204 absorbed by #220) — `tests/test-token-spike.sh` gains explicit cache-miss regression test + negative-control test. SDLC skill + wizard doc gain "Cache-Cost Surprises" sections covering 10-20× silent cost blowups (mid-session CLAUDE.md edits, idle pruning, upstream cache bugs) and detection via `hooks/token-spike-check.sh`'s `costly_tokens` metric.