agentic-sdlc-wizard 1.37.1 → 1.39.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 +20 -0
- package/CLAUDE_CODE_SDLC_WIZARD.md +119 -2
- package/cli/bin/sdlc-wizard.js +17 -4
- package/cli/lib/repo-complexity.js +167 -0
- package/hooks/sdlc-prompt-check.sh +12 -0
- package/package.json +1 -1
- package/skills/sdlc/SKILL.md +2 -0
- package/skills/setup/SKILL.md +33 -11
- package/skills/update/SKILL.md +55 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,26 @@ 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.39.0] - 2026-04-24
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Dead plugin registration cleanup in /update-wizard** (Step 7.7). When a wizard-installed plugin marketplace in `~/.claude/settings.json` points to a directory that no longer exists (rename, disable, or removal), every Claude Code session emits `UserPromptSubmit hook error: Failed to run: Plugin directory does not exist: ...` until cleaned up. New step detects entries in `extraKnownMarketplaces` matching `sdlc-wizard*` whose `source.path` is missing, plus the corresponding `enabledPlugins["sdlc-wizard@<marketplace>"]` flag, and offers cleanup with a backup. Scope-guarded to wizard installs only — never touches third-party plugin registrations. Lives in update-skill (not setup) because dead registrations only appear after install when something disables or removes the plugin directory; update is the natural drift-detection seam.
|
|
12
|
+
|
|
13
|
+
- **Community feature-discovery scanner** — ROADMAP #207. New `tests/e2e/scan-community.sh` script extracts `/[a-z][a-z0-9-]*` slash-command mentions from transcript text (Reddit, HN, Discord, CC GitHub Discussions exports) and emits any not in the `tests/e2e/known-slash-commands.txt` allowlist. Output is JSON with `scan_date`, `input_files`, and `candidates: [{slash, count, sample}]` for triage. Maintainer pulls transcripts manually (per ROADMAP #231 Phase 3 plan: "scan-community → port to tests/e2e/scan-community.sh; maintainer runs weekly on Max"); the scanner itself is offline + deterministic. Allowlist seeded with wizard skills (`/sdlc`, `/setup`, `/update`, `/feedback`, `/code-review`, `/less-permission-prompts`, `/claude-automation-recommender`, `/schedule`, `/ultrareview`), CC native commands as of 2.1.118 (`/help`, `/clear`, `/model`, `/effort`, `/usage`, `/cost`, `/stats`, `/compact`, `/resume`, `/init`, `/mcp`, `/plugin`, `/agents`, `/hooks`, `/permissions`, `/sandbox`, `/fast`, `/exit`, `/login`, `/logout`, `/doctor`, `/install`, `/uninstall`, `/settings`), plus common URL-path false positives (`/dev`, `/usr`, `/var`, `/tmp`, `/etc`, `/bin`, `/lib`, `/opt`, `/home`, `/root`, `/proc`, `/sys`, `/run`, `/mnt`, `/media`, `/srv`). Length-≥4 filter drops `/a`, `/ab` style noise. New `tests/test-community-scanner.sh` (11 tests) covers detection, allowlist filtering (CC native + wizard skills), dedup + count, empty-input edge case, JSON shape, stdin input, multi-file aggregation, and sample-context inclusion. Procedure documented in `CLAUDE_CODE_SDLC_WIZARD.md` → "Community Feature-Discovery Scanner". Complements aistupidlevel.info degradation signal and CC changelog diffs — three signals together cover official + community feature surface.
|
|
14
|
+
|
|
15
|
+
## [1.38.0] - 2026-04-24
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **Prompt-hook-fires-once instrumentation** — ROADMAP #224. `hooks/sdlc-prompt-check.sh` now records one tab-separated record (`<ts>\t<pid>\tsdlc-prompt-check`) per post-dedupe invocation when the opt-in env var `SDLC_HOOK_FIRE_LOG` is set. Maintainer can count lines per user prompt to verify CC 2.1.118's double-fire fix in real sessions; >1 line per prompt indicates regression. Unwritable paths fail silently. Procedure documented in `CLAUDE_CODE_SDLC_WIZARD.md` → "Verifying Prompt-Hook-Fires-Once". 6 regression tests in `tests/test-prompt-hook-fires-once.sh` cover the instrumentation contract (counter increments, opt-in semantics, log shape, output stability, error tolerance).
|
|
20
|
+
|
|
21
|
+
- **Mixed-mode tier (Sonnet 4.6 coder + Opus 4.7 reviewer)** — ROADMAP #233. New `cli/lib/repo-complexity.js` heuristic classifies repos as `simple` or `complex` from filesystem signals (LOC, test count, hook count, workflow count, plus stakes flag for `.env` / `secrets/` / `credentials/`). Setup skill Step 9.5 expanded from binary y/N into a 3-way prompt:
|
|
22
|
+
- **`[N]`** No pin (default, recommended for most repos) — preserves Claude Code auto-mode
|
|
23
|
+
- **`[m]`** Mixed-mode pin `model: "sonnet[1m]"` — suggested for `simple` tier; coder runs on Sonnet, cross-model reviewer always stays at flagship (Opus 4.7 / gpt-5.5 xhigh)
|
|
24
|
+
- **`[f]`** Flagship pin `model: "opus[1m]"` + `CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=30` — suggested for `complex` / stakes-flagged tier; current pre-#233 default
|
|
25
|
+
Stakes flag (`.env` / `secrets/` / `credentials/`) forces `complex` regardless of size and detects at any depth (e.g. `config/.env`, `app/secrets/`); the coder is doing security-relevant work and the saving isn't worth the risk. Heuristic outputs are advisory — the user always picks the final tier. New `tests/test-repo-complexity.sh` (11 tests) with six fixture repos (`tests/fixtures/complexity/{simple,complex,stakes,nested-stakes,boundary-simple,boundary-complex}-repo`) covers tier classification, nested stakes detection, threshold-boundary cases (29 tests = simple, 30 = complex), JSON shape, missing-dir error path, and the `npx agentic-sdlc-wizard complexity` CLI subcommand. Cross-model review section in `skills/sdlc/SKILL.md` explicitly notes the reviewer **always** runs at flagship regardless of coder pin — weakening the review leg defeats the savings. Update skill Step 7.5 recognizes `sonnet[1m]` as a valid mixed-mode pin (no migration prompt). Wizard doc gets a new "Mixed-Mode Tier" subsection documenting the split, when to use each tier, the prove-it gate (pair-test on 3+ simple repos before recommending mixed-mode as default), and tradeoffs. **Reconciles with #198:** mixed-mode is opt-in per-project via Step 9.5; no-pin remains the default.
|
|
26
|
+
|
|
7
27
|
## [1.37.1] - 2026-04-24
|
|
8
28
|
|
|
9
29
|
### Fixed
|
|
@@ -940,6 +940,123 @@ Claude Code supports both 200K and 1M context windows. **`opus[1m]` is an opt-in
|
|
|
940
940
|
|
|
941
941
|
**Autocompact pairing (important):** If you opt into `opus[1m]`, also set `CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=30` — otherwise CC's default autocompact fires at ~76K and destroys the headroom you're paying for. Step 9.5 writes both together when you opt in.
|
|
942
942
|
|
|
943
|
+
### Mixed-Mode Tier (Sonnet coder + Opus reviewer, roadmap #233)
|
|
944
|
+
|
|
945
|
+
For trivial / blank / config-only / CRUD-style repos, full Opus 4.7 on every turn is overkill on the coder leg. The **mixed-mode tier** pins `model: "sonnet[1m]"` for in-session work while keeping the cross-model review layer (Codex / external reviewer) at the flagship — so the reviewer still catches what Sonnet missed.
|
|
946
|
+
|
|
947
|
+
**The split:**
|
|
948
|
+
|
|
949
|
+
| Layer | Mixed-mode tier | Flagship tier |
|
|
950
|
+
|-------|----------------|---------------|
|
|
951
|
+
| Coder (in-session CC) | `model: "sonnet[1m]"` | `model: "opus[1m]"` |
|
|
952
|
+
| Cross-model reviewer (Codex etc.) | gpt-5.5 xhigh (or Opus 4.7 max via Bash) | gpt-5.5 xhigh (or Opus 4.7 max via Bash) |
|
|
953
|
+
| Effort floor (CC session) | xhigh; max preferred | xhigh; max preferred |
|
|
954
|
+
|
|
955
|
+
The reviewer always stays at flagship — the whole point of mixed-mode is that adversarial review catches Sonnet's blind spots, so weakening the review leg defeats the savings.
|
|
956
|
+
|
|
957
|
+
**When mixed-mode is the right call:**
|
|
958
|
+
- Repo is small (LOC < 10K), few tests (< 30), few hooks (< 5), few workflows (< 5), no `.env` / secrets handling
|
|
959
|
+
- You're on API billing (not Max subscription) and 2× cost on simple repos actually matters
|
|
960
|
+
- Tasks are predominantly mechanical — typo fixes, config tweaks, small CRUD endpoints
|
|
961
|
+
- You're running the SDLC Wizard's setup flow against a sibling repo where the coder doesn't need flagship reasoning
|
|
962
|
+
|
|
963
|
+
**When to stay flagship:**
|
|
964
|
+
- Stakes-flagged repo: anywhere `.env` / `secrets/` / `credentials/` exists. Force flagship even if LOC is tiny — leaks are catastrophic
|
|
965
|
+
- Architecture work, debugging non-obvious bugs, security review, anything where the *coder's* judgment matters as much as the reviewer's
|
|
966
|
+
- Long shepherd sessions (plan → TDD → review → CI loop) — they cross 100K tokens regularly and Opus 4.7 fits the window better in a single thread
|
|
967
|
+
|
|
968
|
+
**Auto-detection:** the setup wizard runs `cli/lib/repo-complexity.js` against the target repo and suggests the tier. Stakes flag (`.env` / `secrets/`) forces complex regardless of size. The user always picks the final answer — the heuristic is a hint, not a gate.
|
|
969
|
+
|
|
970
|
+
**How to opt in (manual):**
|
|
971
|
+
```json
|
|
972
|
+
{
|
|
973
|
+
"model": "sonnet[1m]"
|
|
974
|
+
}
|
|
975
|
+
```
|
|
976
|
+
Don't add `CLAUDE_AUTOCOMPACT_PCT_OVERRIDE` — Sonnet's 1M window has different compaction characteristics than Opus's; let upstream defaults ride until we benchmark.
|
|
977
|
+
|
|
978
|
+
**Prove-It Gate (#233 acceptance criterion):** mixed-mode ships only if pair-tested on 3+ simple repos shows Sonnet-coder + Opus-reviewer produces ≥ same SDLC scores as full-Opus baseline. The first version of the heuristic ships v1.38.0; pair-test results land in CHANGELOG before recommending mixed-mode as the default for any tier.
|
|
979
|
+
|
|
980
|
+
**Tradeoffs (be honest):**
|
|
981
|
+
- Sonnet 4.6 will drop some fine-grained self-review moves (it's fast, less deliberate). The Opus reviewer catches them — but you'll see more "fix in round 2" cycles compared to Opus-coder runs.
|
|
982
|
+
- Mixed-mode disables auto-mode (same as flagship pin). The Sonnet pin is per-session — to switch back, remove the `model` line.
|
|
983
|
+
|
|
984
|
+
### Community Feature-Discovery Scanner (roadmap #207)
|
|
985
|
+
|
|
986
|
+
The weekly-update workflow watches Anthropic's official changelog + GitHub releases, but new CC slash-commands (e.g. a hypothetical `/insights`) often surface FIRST on Reddit, HN, or Discord weeks before they hit the changelog. `tests/e2e/scan-community.sh` ports the community-scan job out of CI (deleted per ROADMAP #231) into a maintainer-runnable script: pull transcripts manually, pipe through the scanner, triage the digest.
|
|
987
|
+
|
|
988
|
+
**What it does:** extracts every `/[a-z][a-z0-9-]*` mention (length ≥ 4) from input text, dedupes against `tests/e2e/known-slash-commands.txt`, and emits a JSON digest with each unknown slash-command's count and one sample line for context.
|
|
989
|
+
|
|
990
|
+
**Maintainer procedure:**
|
|
991
|
+
|
|
992
|
+
```bash
|
|
993
|
+
# 1. Capture transcripts. Save Reddit threads, HN comments, Discord exports,
|
|
994
|
+
# or CC GH Discussions to plain-text files. The scanner doesn't care about
|
|
995
|
+
# formatting — just the raw text.
|
|
996
|
+
mkdir -p /tmp/community-scan-$(date +%Y-%m-%d)
|
|
997
|
+
cd /tmp/community-scan-*
|
|
998
|
+
# (paste / curl content into reddit.txt, hn.txt, discord.txt, etc.)
|
|
999
|
+
|
|
1000
|
+
# 2. Run the scanner. Multiple files are aggregated into one digest.
|
|
1001
|
+
bash /path/to/sdlc-wizard/tests/e2e/scan-community.sh *.txt > digest.json
|
|
1002
|
+
|
|
1003
|
+
# 3. Triage. Anything in `candidates` is a slash-command the wizard's
|
|
1004
|
+
# allowlist doesn't recognize — could be a new CC native command, a
|
|
1005
|
+
# third-party plugin, or pure noise.
|
|
1006
|
+
jq '.candidates' digest.json
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
**Output shape:**
|
|
1010
|
+
|
|
1011
|
+
```json
|
|
1012
|
+
{
|
|
1013
|
+
"scan_date": "2026-04-24",
|
|
1014
|
+
"input_files": ["reddit.txt", "hn.txt"],
|
|
1015
|
+
"candidates": [
|
|
1016
|
+
{ "slash": "/insights", "count": 3, "sample": "Did you all see /insights in CC 2.2..." },
|
|
1017
|
+
{ "slash": "/newthing", "count": 1, "sample": "I tried /newthing on a long session..." }
|
|
1018
|
+
]
|
|
1019
|
+
}
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
**Updating the allowlist:** when triage confirms a candidate is real and the wizard now accounts for it (either as a wizard skill or by documenting CC's native command), append it to `tests/e2e/known-slash-commands.txt` so the next scan stops surfacing it. The file is the single source of truth — no rebuild, no migration.
|
|
1023
|
+
|
|
1024
|
+
**Why offline + deterministic:** the previous CI-based scan-community job burned $2-5/run via claude-code-action calls and produced one merged community-pattern PR in 30 days (ROADMAP #231 Phase 1 audit). Replacing it with a local regex scan + maintainer triage gives the same signal at zero API cost; the original `.github/prompts/analyze-community.md` prompt still exists for the LLM-summarization layer if a maintainer wants narrative analysis on top.
|
|
1025
|
+
|
|
1026
|
+
**Regression test:** `tests/test-community-scanner.sh` covers detection of new commands, allowlist filtering (CC native + wizard skills), dedup + count behavior, empty-input edge case, JSON shape, stdin input, multi-file aggregation, and sample-context inclusion (11 tests). The fixtures under `tests/fixtures/community-scanner/` are seeded with `/newthing`, `/alpha`, `/beta`, `/gamma` mock mentions; if the scanner regresses the test fails on the missed slash.
|
|
1027
|
+
|
|
1028
|
+
---
|
|
1029
|
+
|
|
1030
|
+
### Verifying Prompt-Hook-Fires-Once (roadmap #224)
|
|
1031
|
+
|
|
1032
|
+
CC 2.1.118 shipped a fix for `prompt` hooks double-firing when an agent-hook verifier subagent itself made tool calls. The bug would manifest as duplicate `SDLC BASELINE` injections per `UserPromptSubmit` — context bloat plus possible confusion. The dual-channel (project + plugin) double-print is already handled by `dedupe_plugin_or_project` in v1.37.1; this section is the runtime check for the *CC-internal* double-fire case.
|
|
1033
|
+
|
|
1034
|
+
`hooks/sdlc-prompt-check.sh` ships an opt-in instrumentation: when the env var `SDLC_HOOK_FIRE_LOG` is set, every post-dedupe invocation appends one tab-separated record (`<unix-ts>\t<pid>\tsdlc-prompt-check`) to that log. Counting lines per prompt tells you whether CC fired the hook once or twice.
|
|
1035
|
+
|
|
1036
|
+
**Maintainer procedure (real session):**
|
|
1037
|
+
|
|
1038
|
+
```bash
|
|
1039
|
+
# 1. Pick a fresh log path
|
|
1040
|
+
export SDLC_HOOK_FIRE_LOG="$(mktemp /tmp/sdlc-fire-log.XXXXXX)"
|
|
1041
|
+
|
|
1042
|
+
# 2. Restart Claude Code so the env propagates into spawned hooks
|
|
1043
|
+
# (or set it in your shell rc / .envrc and start a fresh session)
|
|
1044
|
+
|
|
1045
|
+
# 3. Run a normal SDLC session — including any task that triggers a verifier
|
|
1046
|
+
# subagent (e.g., /code-review, /sdlc with multi-step planning)
|
|
1047
|
+
|
|
1048
|
+
# 4. After N user prompts, count log lines:
|
|
1049
|
+
wc -l "$SDLC_HOOK_FIRE_LOG"
|
|
1050
|
+
# Expect: N lines. >N indicates the CC double-fire bug regressed.
|
|
1051
|
+
|
|
1052
|
+
# 5. Optional: tail the log live in another terminal to watch each fire:
|
|
1053
|
+
tail -f "$SDLC_HOOK_FIRE_LOG"
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
The instrumentation is opt-in — when the env var is unset, no log is written and no overhead is added. Unwritable log paths fail silently so a bad `SDLC_HOOK_FIRE_LOG` value never crashes the hook.
|
|
1057
|
+
|
|
1058
|
+
**Regression test:** `tests/test-prompt-hook-fires-once.sh` covers the instrumentation contract (counter increments per invocation, opt-in semantics, log line shape, output stability, unwritable-path tolerance). It does *not* spawn Claude Code — that's a maintainer-runtime check by design. The test asserts the recording mechanism works so the maintainer's real-session count is trustworthy.
|
|
1059
|
+
|
|
943
1060
|
---
|
|
944
1061
|
|
|
945
1062
|
## Example Workflow (End-to-End)
|
|
@@ -2715,7 +2832,7 @@ If deployment fails or post-deploy verification catches issues:
|
|
|
2715
2832
|
|
|
2716
2833
|
**SDLC.md:**
|
|
2717
2834
|
```markdown
|
|
2718
|
-
<!-- SDLC Wizard Version: 1.
|
|
2835
|
+
<!-- SDLC Wizard Version: 1.39.0 -->
|
|
2719
2836
|
<!-- Setup Date: [DATE] -->
|
|
2720
2837
|
<!-- 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 -->
|
|
2721
2838
|
<!-- Git Workflow: [PRs or Solo] -->
|
|
@@ -3777,7 +3894,7 @@ Walk through updates? (y/n)
|
|
|
3777
3894
|
Store wizard state in `SDLC.md` as metadata comments (invisible to readers, parseable by Claude):
|
|
3778
3895
|
|
|
3779
3896
|
```markdown
|
|
3780
|
-
<!-- SDLC Wizard Version: 1.
|
|
3897
|
+
<!-- SDLC Wizard Version: 1.39.0 -->
|
|
3781
3898
|
<!-- Setup Date: 2026-01-24 -->
|
|
3782
3899
|
<!-- 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 -->
|
|
3783
3900
|
<!-- Git Workflow: PRs -->
|
package/cli/bin/sdlc-wizard.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
const { version } = require('../../package.json');
|
|
5
5
|
const { init, check } = require('../init');
|
|
6
|
+
const { detectComplexity } = require('../lib/repo-complexity');
|
|
6
7
|
|
|
7
8
|
const args = process.argv.slice(2);
|
|
8
9
|
|
|
@@ -12,7 +13,8 @@ const flags = {
|
|
|
12
13
|
json: args.includes('--json'),
|
|
13
14
|
};
|
|
14
15
|
|
|
15
|
-
const
|
|
16
|
+
const positional = args.filter((a) => !a.startsWith('--'));
|
|
17
|
+
const command = positional[0];
|
|
16
18
|
|
|
17
19
|
if (args.includes('--version') || args.includes('-v')) {
|
|
18
20
|
console.log(version);
|
|
@@ -24,13 +26,14 @@ if (args.includes('--help') || args.includes('-h') || !command) {
|
|
|
24
26
|
agentic-sdlc-wizard v${version}
|
|
25
27
|
|
|
26
28
|
Usage:
|
|
27
|
-
sdlc-wizard init [options]
|
|
28
|
-
sdlc-wizard check [options]
|
|
29
|
+
sdlc-wizard init [options] Install SDLC wizard into current directory
|
|
30
|
+
sdlc-wizard check [options] Check installation health and updates
|
|
31
|
+
sdlc-wizard complexity [path] Print mixed-mode tier heuristic (roadmap #233)
|
|
29
32
|
|
|
30
33
|
Options:
|
|
31
34
|
--force Overwrite existing files (init only)
|
|
32
35
|
--dry-run Preview changes without writing (init only)
|
|
33
|
-
--json Output as JSON (check
|
|
36
|
+
--json Output as JSON (check / complexity)
|
|
34
37
|
--version Show version
|
|
35
38
|
--help Show this help
|
|
36
39
|
`.trim());
|
|
@@ -55,6 +58,16 @@ if (command === 'init') {
|
|
|
55
58
|
console.error(`Error: ${err.message}`);
|
|
56
59
|
process.exit(1);
|
|
57
60
|
}
|
|
61
|
+
} else if (command === 'complexity') {
|
|
62
|
+
try {
|
|
63
|
+
const target = positional[1] || process.cwd();
|
|
64
|
+
const result = detectComplexity(target);
|
|
65
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
66
|
+
process.exit(0);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.error(`Error: ${err.message}`);
|
|
69
|
+
process.exit(2);
|
|
70
|
+
}
|
|
58
71
|
} else {
|
|
59
72
|
console.error(`Unknown command: ${command}`);
|
|
60
73
|
console.error('Run "sdlc-wizard --help" for usage.');
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// Roadmap #233: repo complexity heuristic for mixed-mode tier selection.
|
|
2
|
+
//
|
|
3
|
+
// Output: { tier: 'simple' | 'complex', score: <number>, signals: [...] }
|
|
4
|
+
// - 'simple' → setup wizard suggests mixed-mode (Sonnet 4.6 coder + Opus 4.7 reviewer)
|
|
5
|
+
// - 'complex' → setup wizard suggests full flagship (Opus 4.7 everywhere)
|
|
6
|
+
// Cross-model review (Codex / external) always stays at the flagship tier
|
|
7
|
+
// regardless of coder selection — see CLAUDE_CODE_SDLC_WIZARD.md.
|
|
8
|
+
//
|
|
9
|
+
// Classification (matches CLAUDE_CODE_SDLC_WIZARD.md → "Mixed-Mode Tier"):
|
|
10
|
+
// simple = LOC < 10K AND tests < 30 AND hooks < 5 AND workflows < 5 AND no stakes
|
|
11
|
+
// complex = ANY high signal OR stakes flag (.env / secrets/ / credentials/ at any depth)
|
|
12
|
+
// `score` is an additive ladder kept for transparency (low=0, mid=1, high=2 per signal).
|
|
13
|
+
//
|
|
14
|
+
// Heuristic is intentionally cheap: a single sync filesystem walk, no parsing.
|
|
15
|
+
// It is a setup-time hint, not a runtime gate; users can override the result.
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const STAKES_FILES = new Set(['.env', '.env.local', '.env.production', '.env.development', '.envrc']);
|
|
21
|
+
const STAKES_DIRS = new Set(['secrets', 'credentials', '.secrets', '.credentials']);
|
|
22
|
+
const SKIP_DIRS = new Set([
|
|
23
|
+
'node_modules', '.git', 'dist', 'build', 'out', 'target',
|
|
24
|
+
'.next', '.nuxt', 'coverage', '.cache', 'vendor', '__pycache__',
|
|
25
|
+
]);
|
|
26
|
+
const SOURCE_EXTS = new Set([
|
|
27
|
+
'.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs',
|
|
28
|
+
'.py', '.go', '.rs', '.rb', '.java', '.kt', '.swift',
|
|
29
|
+
'.c', '.h', '.cpp', '.hpp', '.cc',
|
|
30
|
+
'.sh', '.bash', '.zsh',
|
|
31
|
+
]);
|
|
32
|
+
const TEST_PATTERNS = [/\.test\.[jt]sx?$/, /\.spec\.[jt]sx?$/, /_test\.go$/, /test_.*\.py$/, /.*_test\.py$/];
|
|
33
|
+
|
|
34
|
+
function isTestFile(name, parentDir) {
|
|
35
|
+
if (TEST_PATTERNS.some((p) => p.test(name))) return true;
|
|
36
|
+
return parentDir === 'tests' || parentDir === 'test' || parentDir === '__tests__' || parentDir === 'spec';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function walk(rootDir, repoRoot, onFile, onDir, visited) {
|
|
40
|
+
let entries;
|
|
41
|
+
try {
|
|
42
|
+
entries = fs.readdirSync(rootDir, { withFileTypes: true });
|
|
43
|
+
} catch (_) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
const full = path.join(rootDir, entry.name);
|
|
48
|
+
if (entry.isSymbolicLink()) continue; // don't follow symlinks (cycle / out-of-tree risk)
|
|
49
|
+
if (entry.isDirectory()) {
|
|
50
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
51
|
+
// Resolve real path for cycle detection.
|
|
52
|
+
let realPath;
|
|
53
|
+
try {
|
|
54
|
+
realPath = fs.realpathSync(full);
|
|
55
|
+
} catch (_) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (visited.has(realPath)) continue;
|
|
59
|
+
visited.add(realPath);
|
|
60
|
+
onDir && onDir(full, entry.name);
|
|
61
|
+
walk(full, repoRoot, onFile, onDir, visited);
|
|
62
|
+
} else if (entry.isFile()) {
|
|
63
|
+
onFile(full, entry.name, path.basename(rootDir));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function countLines(filePath) {
|
|
69
|
+
try {
|
|
70
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
71
|
+
if (!content) return 0;
|
|
72
|
+
return content.split('\n').length;
|
|
73
|
+
} catch (_) {
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function detectComplexity(repoPath) {
|
|
79
|
+
if (!fs.existsSync(repoPath)) {
|
|
80
|
+
throw new Error(`Repo path does not exist: ${repoPath}`);
|
|
81
|
+
}
|
|
82
|
+
const stat = fs.statSync(repoPath);
|
|
83
|
+
if (!stat.isDirectory()) {
|
|
84
|
+
throw new Error(`Repo path is not a directory: ${repoPath}`);
|
|
85
|
+
}
|
|
86
|
+
const repoRoot = path.resolve(repoPath);
|
|
87
|
+
|
|
88
|
+
let loc = 0;
|
|
89
|
+
let testFiles = 0;
|
|
90
|
+
let hookFiles = 0;
|
|
91
|
+
let workflowFiles = 0;
|
|
92
|
+
const stakesHits = [];
|
|
93
|
+
const visited = new Set([fs.realpathSync(repoRoot)]);
|
|
94
|
+
|
|
95
|
+
walk(
|
|
96
|
+
repoRoot,
|
|
97
|
+
repoRoot,
|
|
98
|
+
(filePath, name, parent) => {
|
|
99
|
+
const ext = path.extname(name).toLowerCase();
|
|
100
|
+
const rel = path.relative(repoRoot, filePath);
|
|
101
|
+
if (STAKES_FILES.has(name)) {
|
|
102
|
+
stakesHits.push(`stakes:file:${rel}`);
|
|
103
|
+
}
|
|
104
|
+
if (SOURCE_EXTS.has(ext) || ext === '.yml' || ext === '.yaml') {
|
|
105
|
+
if (isTestFile(name, parent)) testFiles++;
|
|
106
|
+
else if (SOURCE_EXTS.has(ext)) loc += countLines(filePath);
|
|
107
|
+
}
|
|
108
|
+
if (parent === 'hooks' && filePath.includes(`.claude${path.sep}hooks`) && (ext === '.sh' || ext === '.bash')) {
|
|
109
|
+
hookFiles++;
|
|
110
|
+
}
|
|
111
|
+
if (parent === 'workflows' && filePath.includes(`.github${path.sep}workflows`) && (ext === '.yml' || ext === '.yaml')) {
|
|
112
|
+
workflowFiles++;
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
(dirPath, name) => {
|
|
116
|
+
if (STAKES_DIRS.has(name)) {
|
|
117
|
+
const rel = path.relative(repoRoot, dirPath);
|
|
118
|
+
stakesHits.push(`stakes:dir:${rel || name}/`);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
visited
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// Bands match docs: LOC<10K / tests<30 / hooks<5 / workflows<5 = "simple band"
|
|
125
|
+
const signals = [];
|
|
126
|
+
let highHits = 0;
|
|
127
|
+
let score = 0;
|
|
128
|
+
|
|
129
|
+
function band(value, midThreshold, highThreshold, label) {
|
|
130
|
+
if (value >= highThreshold) {
|
|
131
|
+
score += 2;
|
|
132
|
+
highHits++;
|
|
133
|
+
signals.push(`${label}:${value} (high → +2)`);
|
|
134
|
+
} else if (value >= midThreshold) {
|
|
135
|
+
score += 1;
|
|
136
|
+
signals.push(`${label}:${value} (mid → +1)`);
|
|
137
|
+
} else {
|
|
138
|
+
signals.push(`${label}:${value} (low → +0)`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
band(loc, 1000, 10000, 'loc');
|
|
143
|
+
band(testFiles, 5, 30, 'tests');
|
|
144
|
+
band(hookFiles, 3, 5, 'hooks');
|
|
145
|
+
band(workflowFiles, 2, 5, 'workflows');
|
|
146
|
+
|
|
147
|
+
let tier = highHits > 0 ? 'complex' : 'simple';
|
|
148
|
+
if (stakesHits.length > 0) {
|
|
149
|
+
tier = 'complex';
|
|
150
|
+
signals.push(...stakesHits, 'override:stakes-forces-complex');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { tier, score, signals };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = { detectComplexity };
|
|
157
|
+
|
|
158
|
+
if (require.main === module) {
|
|
159
|
+
const target = process.argv[2] || '.';
|
|
160
|
+
try {
|
|
161
|
+
const result = detectComplexity(target);
|
|
162
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
163
|
+
} catch (err) {
|
|
164
|
+
process.stderr.write(`error: ${err.message}\n`);
|
|
165
|
+
process.exit(2);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -12,6 +12,18 @@ source "$HOOK_DIR/_find-sdlc-root.sh"
|
|
|
12
12
|
# plugin paths even when the script is sourced or invoked via aliases.
|
|
13
13
|
dedupe_plugin_or_project "${BASH_SOURCE[0]}" || exit 0
|
|
14
14
|
|
|
15
|
+
# Roadmap #224: opt-in fires-once instrumentation. CC 2.1.118 shipped a fix for
|
|
16
|
+
# prompt hooks double-firing when a verifier subagent itself made tool calls.
|
|
17
|
+
# When SDLC_HOOK_FIRE_LOG is set, append one tab-separated record per real
|
|
18
|
+
# invocation (post-dedupe). Maintainer can compare line count against prompt
|
|
19
|
+
# count to verify the CC fix in real sessions. See CLAUDE_CODE_SDLC_WIZARD.md →
|
|
20
|
+
# "Verifying Prompt-Hook-Fires-Once" for the procedure.
|
|
21
|
+
if [ -n "${SDLC_HOOK_FIRE_LOG:-}" ]; then
|
|
22
|
+
{
|
|
23
|
+
printf '%s\t%s\tsdlc-prompt-check\n' "$(date +%s)" "$$" >> "$SDLC_HOOK_FIRE_LOG"
|
|
24
|
+
} 2>/dev/null || true
|
|
25
|
+
fi
|
|
26
|
+
|
|
15
27
|
# CWD walk-up finds nearest SDLC project (#173: silent exit for non-SDLC dirs)
|
|
16
28
|
if find_sdlc_root; then
|
|
17
29
|
PROJECT_DIR="$SDLC_ROOT"
|
package/package.json
CHANGED
package/skills/sdlc/SKILL.md
CHANGED
|
@@ -230,6 +230,8 @@ PLANNING -> DOCS -> TDD RED -> TDD GREEN -> Tests Pass -> Self-Review
|
|
|
230
230
|
|
|
231
231
|
**The core insight:** The review PROTOCOL is universal across domains. Only the review INSTRUCTIONS change. Code review is the default template below. For non-code domains (research, persuasion, medical content), adapt the `review_instructions` and `verification_checklist` fields while keeping the same handoff/dialogue/convergence loop.
|
|
232
232
|
|
|
233
|
+
**Reviewer always at the flagship tier (roadmap #233):** if the project pins `model: "sonnet[1m]"` (mixed-mode) or any non-flagship coder, the cross-model reviewer **still runs at the flagship**: `codex exec -c 'model_reasoning_effort="xhigh"'` (gpt-5.5) or an Opus 4.7 max equivalent. The whole point of mixed-mode is that adversarial review catches Sonnet's blind spots — weakening the reviewer leg defeats the savings. Don't downscale the review just because the coder is downscaled.
|
|
234
|
+
|
|
233
235
|
### Step 0: Write Preflight Self-Review Doc
|
|
234
236
|
|
|
235
237
|
Before submitting to an external reviewer, document what YOU already checked. This is proven to reduce reviewer findings to 0-1 per round (evidence: anticheat repo preflight discipline).
|
package/skills/setup/SKILL.md
CHANGED
|
@@ -198,24 +198,45 @@ Write the shape as:
|
|
|
198
198
|
|
|
199
199
|
Present suggestions and let the user confirm.
|
|
200
200
|
|
|
201
|
-
### Step 9.5: Context Window Configuration (Opt-In)
|
|
201
|
+
### Step 9.5: Context Window + Mixed-Mode Configuration (Opt-In)
|
|
202
202
|
|
|
203
|
-
The CLI ships `cli/templates/settings.json` with **no** `model` or `env` pin by default. This preserves Claude Code's built-in model auto-selection (Sonnet for cheap tasks, Opus for hard ones) and the upstream autocompact threshold. Power users
|
|
203
|
+
The CLI ships `cli/templates/settings.json` with **no** `model` or `env` pin by default. This preserves Claude Code's built-in model auto-selection (Sonnet for cheap tasks, Opus for hard ones) and the upstream autocompact threshold. Power users can opt into a pin during setup; mixed-mode users (Sonnet coder + Opus reviewer) can pin Sonnet here too.
|
|
204
204
|
|
|
205
|
-
**Why this is opt-in (issue #198):** A top-level `"model"` in `settings.json` tells Claude Code "the user has explicitly chosen a model" and disables auto-mode for the session. That is a real tradeoff —
|
|
205
|
+
**Why this is opt-in (issue #198):** A top-level `"model"` in `settings.json` tells Claude Code "the user has explicitly chosen a model" and disables auto-mode for the session. That is a real tradeoff — pinning is only worth it when you actually need the 1M headroom or you've decided mixed-mode tier-splitting is better than per-turn auto-selection.
|
|
206
|
+
|
|
207
|
+
**Run the complexity heuristic first (roadmap #233):**
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
npx agentic-sdlc-wizard complexity .
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The output is JSON: `{ tier: "simple" | "complex", score, signals }`. Use the result to suggest a default in the prompt below — do NOT override the user's choice. The heuristic flags any `.env` / `secrets/` / `credentials/` at any depth as a stakes signal that forces `complex` regardless of size.
|
|
206
214
|
|
|
207
215
|
**Ask the user exactly once in Step 9.5:**
|
|
208
216
|
|
|
209
|
-
>
|
|
217
|
+
> Detected repo complexity: **{tier}** ({score}, signals: {loc, tests, hooks, workflows, stakes-flag if any}).
|
|
218
|
+
>
|
|
219
|
+
> How do you want to configure the model for this repo?
|
|
210
220
|
>
|
|
211
|
-
> - **No (default):** Leaves auto-mode enabled. Claude Code picks the model per turn
|
|
212
|
-
> - **
|
|
221
|
+
> - **[N] No pin (default, recommended for most repos):** Leaves auto-mode enabled. Claude Code picks the model per turn. Compaction follows upstream defaults. Simplest, lowest friction.
|
|
222
|
+
> - **[m] Mixed-mode** *(suggested for **simple** tier — roadmap #233):* Pins `model: "sonnet[1m]"` for the coder (Sonnet 4.6 with 1M context). The cross-model review layer (Codex / external reviewer) **always stays at the flagship** (Opus 4.7 max or gpt-5.5 xhigh) regardless. Saves cost/quota on simple repos; reviewer catches what Sonnet misses. Requires comfort with losing per-turn auto-selection.
|
|
223
|
+
> - **[f] Flagship full** *(suggested for **complex** / stakes-flagged tier):* Pins `model: "opus[1m]"` (Opus 4.7 with 1M context) and sets `CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=30`. Long SDLC sessions cross 100K tokens regularly; the 1M window gives headroom and 30% autocompact fires at ~300K. Requires Claude Code v2.1.111+.
|
|
213
224
|
>
|
|
214
|
-
> `[
|
|
225
|
+
> `[N/m/f]`
|
|
226
|
+
|
|
227
|
+
**If the user answers `N` (default):** Make no edits to `.claude/settings.json`. Auto-mode stays on. Done.
|
|
228
|
+
|
|
229
|
+
**If the user answers `m` (mixed-mode):** Edit `.claude/settings.json` and add:
|
|
230
|
+
|
|
231
|
+
```json
|
|
232
|
+
{
|
|
233
|
+
"model": "sonnet[1m]"
|
|
234
|
+
}
|
|
235
|
+
```
|
|
215
236
|
|
|
216
|
-
|
|
237
|
+
Do NOT add `CLAUDE_AUTOCOMPACT_PCT_OVERRIDE` for Sonnet — Sonnet's 1M window has different compaction characteristics than Opus; let the upstream default ride. Tell the user explicitly: "Cross-model reviews still run at the flagship — `codex exec -c 'model_reasoning_effort=\"xhigh\"'` (gpt-5.5) or any future Opus-tier reviewer. Mixed-mode is coder-only."
|
|
217
238
|
|
|
218
|
-
**If the user answers
|
|
239
|
+
**If the user answers `f` (flagship):** Edit `.claude/settings.json` and add both fields at the top level:
|
|
219
240
|
|
|
220
241
|
```json
|
|
221
242
|
{
|
|
@@ -226,9 +247,10 @@ The CLI ships `cli/templates/settings.json` with **no** `model` or `env` pin by
|
|
|
226
247
|
}
|
|
227
248
|
```
|
|
228
249
|
|
|
229
|
-
Mention the escape hatch
|
|
250
|
+
Mention the escape hatch in all three cases:
|
|
230
251
|
- To opt out later: remove the `model` line (and optionally the `env` block) from `.claude/settings.json`, or run `/model` and pick "Default (recommended)".
|
|
231
|
-
-
|
|
252
|
+
- To switch tiers later: edit `.claude/settings.json` and replace the `model` value, or re-run `/setup-wizard` Step 9.5.
|
|
253
|
+
- For CI pipelines with short tasks (flagship only), consider `CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=60` — compact early to stay fast.
|
|
232
254
|
|
|
233
255
|
This is project-scoped and shared with the team via git.
|
|
234
256
|
|
package/skills/update/SKILL.md
CHANGED
|
@@ -46,9 +46,11 @@ Parse all CHANGELOG entries between the user's installed version and the latest.
|
|
|
46
46
|
|
|
47
47
|
```
|
|
48
48
|
Installed: 1.24.0
|
|
49
|
-
Latest: 1.
|
|
49
|
+
Latest: 1.39.0
|
|
50
50
|
|
|
51
51
|
What changed:
|
|
52
|
+
- [1.39.0] Community feature-discovery scanner — ROADMAP #207. `tests/e2e/scan-community.sh` extracts unknown `/slash-command` mentions from transcript text (Reddit / HN / Discord exports), dedupes against `tests/e2e/known-slash-commands.txt` allowlist, emits JSON digest of candidates with count + sample. Replaces the deleted CI scan-community job (per #231 Phase 3) with a maintainer-runnable offline scan. 11 quality tests.
|
|
53
|
+
- [1.38.0] Mixed-mode tier (Sonnet 4.6 coder + Opus 4.7 reviewer) for simple repos — ROADMAP #233. New `cli/lib/repo-complexity.js` heuristic + `npx agentic-sdlc-wizard complexity .` CLI command. Setup Step 9.5 expanded from binary y/N to 3-way (no-pin / mixed / flagship). Cross-model review always stays at flagship regardless of coder pin. Reconciles with #198: mixed-mode is opt-in per-project; no-pin remains the default. Plus ROADMAP #224 prompt-hook-fires-once instrumentation (opt-in `SDLC_HOOK_FIRE_LOG`).
|
|
52
54
|
- [1.37.1] Token-bloat fix: dedupe 2× SDLC BASELINE print when both project + plugin register the same hook (~300 tokens doubled per prompt). 5 hooks gain `dedupe_plugin_or_project()` helper. Codex 2-round 100/100.
|
|
53
55
|
- [1.37.0] `monthly-research.yml` workflow deleted (ROADMAP #231 Phase 1) — 0 merged artifacts in 30d while burning $11-23/month; research happens inline now. `model-effort-check.sh` loud WARNING below xhigh (#217) — max preferred, xhigh floor; duplicate effort nudge in `instructions-loaded-check.sh` removed; single source of truth. Both changes Codex-certified.
|
|
54
56
|
- [1.36.1] Repo renamed `agentic-ai-sdlc-wizard` → `claude-sdlc-wizard` (matches sibling pattern; npm package unchanged); `npm pkg fix` metadata cleanup; slug migration across docs/tests/configs
|
|
@@ -133,9 +135,11 @@ If the user is upgrading from a pre-#198 version, check their `.claude/settings.
|
|
|
133
135
|
|
|
134
136
|
2. **If only one of the two fields matches** (e.g. `model: "opus[1m]"` but custom autocompact, or vice versa) — treat as intentional customization. Do not prompt.
|
|
135
137
|
|
|
136
|
-
3. **If `model` is
|
|
138
|
+
3. **If `model` is `"sonnet[1m]"` (mixed-mode tier, roadmap #233, v1.38.0+)** — treat as user's explicit mixed-mode choice. Do not prompt; this is the supported mixed-mode pin. Mention in the upgrade summary: "Detected mixed-mode tier (Sonnet coder + flagship reviewer). Cross-model review still uses Opus / gpt-5.5 — see CLAUDE_CODE_SDLC_WIZARD.md → 'Mixed-Mode Tier'."
|
|
137
139
|
|
|
138
|
-
4. **If
|
|
140
|
+
4. **If `model` is some other value** (e.g. `"sonnet"`, `"opus"`) — treat as user's explicit choice. Do not touch.
|
|
141
|
+
|
|
142
|
+
5. **If neither field is set** — user is already on the new default. No action.
|
|
139
143
|
|
|
140
144
|
When removing: edit the file in place, drop the `model` key (and the `env.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE` key if nothing else is in `env`, otherwise leave `env` alone). Never touch other keys the user added.
|
|
141
145
|
|
|
@@ -163,6 +167,54 @@ If the user's `.claude/settings.json` has a top-level `allowedTools` array, offe
|
|
|
163
167
|
|
|
164
168
|
When migrating: preserve every entry byte-for-byte; only the container key changes. Do not reorder, dedup, or expand wildcards. Other top-level keys (hooks, env, model, custom user fields) are never touched.
|
|
165
169
|
|
|
170
|
+
### Step 7.7: Dead Plugin Registration Cleanup (Global Settings)
|
|
171
|
+
|
|
172
|
+
Wizard installs sometimes leave dead plugin registrations in the user's **global** `~/.claude/settings.json` after the underlying plugin directory is renamed, disabled, or removed. Symptom: every Claude Code session emits `UserPromptSubmit hook error: Failed to run: Plugin directory does not exist: <path> ... run /plugin to reinstall`. The error is harmless but bleeds into every prompt across every project until cleaned up.
|
|
173
|
+
|
|
174
|
+
This step is **global-settings-only** (`~/.claude/settings.json`, not the project's `.claude/settings.json`). The update skill normally avoids global settings; this is the one exception, and only when the plugin marketplace name matches an exact wizard-owned identifier.
|
|
175
|
+
|
|
176
|
+
**Wizard-owned marketplace allowlist** (exact match, no wildcard — wildcards risk eating third-party plugins like `sdlc-wizard-tools` if such a thing ever ships):
|
|
177
|
+
|
|
178
|
+
- `sdlc-wizard-local`
|
|
179
|
+
- `sdlc-wizard-wrap`
|
|
180
|
+
|
|
181
|
+
If `cli/init.js` later registers additional wizard marketplace names, append them to this list verbatim.
|
|
182
|
+
|
|
183
|
+
**Detection:**
|
|
184
|
+
|
|
185
|
+
1. Read `~/.claude/settings.json` and parse as JSON.
|
|
186
|
+
2. For each `extraKnownMarketplaces[key]` where `key` is in the allowlist above:
|
|
187
|
+
- Verify `entry.source.source === "directory"` AND `typeof entry.source.path === "string"`. If either guard fails, skip — not the shape the wizard installs (matches the type-check at `cli/init.js`).
|
|
188
|
+
- Resolve the `source.path` (expand `~` if literal). If the resolved path **does not exist** on disk, mark the marketplace **dead**.
|
|
189
|
+
3. For every dead marketplace `<name>`, look for `enabledPlugins["sdlc-wizard@<name>"]` — also flag for removal.
|
|
190
|
+
4. Repeat for **all** allowlist entries; collect the full set of dead `(marketplace, enabledPlugins)` pairs before prompting. Multiple dead registrations are common (e.g. both `sdlc-wizard-local` and `sdlc-wizard-wrap` if the user reinstalled twice).
|
|
191
|
+
|
|
192
|
+
**Cleanup (always ask first, all-or-nothing per user response):**
|
|
193
|
+
|
|
194
|
+
> Your `~/.claude/settings.json` references wizard plugin marketplaces that don't exist on disk:
|
|
195
|
+
>
|
|
196
|
+
> - `extraKnownMarketplaces.sdlc-wizard-local.source.path` → `<resolved-path>` (missing)
|
|
197
|
+
> - `enabledPlugins["sdlc-wizard@sdlc-wizard-local"]` is `true`
|
|
198
|
+
> - (list all dead pairs from detection)
|
|
199
|
+
>
|
|
200
|
+
> This causes `Plugin directory does not exist` errors on every prompt in every Claude Code session until cleaned up.
|
|
201
|
+
>
|
|
202
|
+
> Drop the listed entries from `~/.claude/settings.json`? `[y/N]`
|
|
203
|
+
|
|
204
|
+
If the user says yes:
|
|
205
|
+
1. **Back up with timestamp**: `cp ~/.claude/settings.json ~/.claude/settings.json.bak.$(date +%Y%m%dT%H%M%S)` so two cleanups on the same day don't overwrite each other's backups.
|
|
206
|
+
2. **Build a single `jq` filter** that drops every dead marketplace and every dead `enabledPlugins` key in one pass: `jq 'del(.enabledPlugins["sdlc-wizard@sdlc-wizard-local"]) | del(.extraKnownMarketplaces["sdlc-wizard-local"]) | del(.enabledPlugins["sdlc-wizard@sdlc-wizard-wrap"]) | del(.extraKnownMarketplaces["sdlc-wizard-wrap"])'` (include only the keys actually marked dead in detection).
|
|
207
|
+
3. Write to a temp file, validate with `jq empty` (round-trip parse), only then replace `~/.claude/settings.json` with `mv`. If validation fails, restore from the backup.
|
|
208
|
+
4. **Formatting note**: `jq` rewrites the whole file and normalizes formatting. The wizard does NOT preserve comments, trailing commas, or other JSONC features — Claude Code's `settings.json` is strict JSON, so this is safe today, but say so to the user. If they care about preserving the exact diff, give them the manual `del()` filter and let them decide whether to apply it.
|
|
209
|
+
|
|
210
|
+
If the user says no: skip silently. Some users have a recovery plan (re-enable the renamed dir, reinstall, etc.).
|
|
211
|
+
|
|
212
|
+
**Idempotency:** Re-running Step 7.7 after a successful cleanup must be a no-op. Detection only flags marketplaces whose `source.path` is missing AND whose name is in the allowlist; both must be true, so a clean settings.json reports zero dead pairs.
|
|
213
|
+
|
|
214
|
+
**Scope guard:** only touch entries whose marketplace name matches the exact allowlist. Third-party plugin registrations (`legal@knowledge-work-plugins`, `claude-md-management@claude-plugins-official`, etc.) and unrelated `sdlc`-prefixed marketplaces (e.g. `danielscholl/claude-sdlc`) are never the wizard's business. Path-existence alone never qualifies a marketplace for cleanup — only allowlist + missing-path together do.
|
|
215
|
+
|
|
216
|
+
**Why this lives in the update skill, not setup:** setup runs once at install time, when the plugin paths are valid by definition. Dead registrations only appear later, when something disables/renames/deletes the plugin directory. Update is the natural seam to detect drift and offer cleanup.
|
|
217
|
+
|
|
166
218
|
### Step 8: Apply Selected Changes
|
|
167
219
|
|
|
168
220
|
For each file the user approved:
|