agentic-sdlc-wizard 1.85.0 → 1.87.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.
@@ -12,31 +12,19 @@ source "$HOOK_DIR/_find-sdlc-root.sh"
12
12
  dedupe_plugin_or_project "${BASH_SOURCE[0]}" || exit 0
13
13
 
14
14
  # CWD walk-up finds nearest SDLC project (#173: silent exit for non-SDLC dirs)
15
+ # #236(b): the missing-SDLC.md/TESTING.md warning that used to live here was
16
+ # removed — sdlc-prompt-check.sh already fires the same "SETUP NOT COMPLETE"
17
+ # warning, louder and on every prompt (this hook only fires at session
18
+ # start/resume), making this copy pure duplicate context.
15
19
  if find_sdlc_root; then
16
20
  PROJECT_DIR="$SDLC_ROOT"
17
21
  elif find_partial_sdlc_root; then
18
- # Partial setup — one file exists but not both. Warn about missing files
19
22
  PROJECT_DIR="$SDLC_ROOT"
20
23
  else
21
24
  # Not an SDLC project at all — exit silently
22
25
  exit 0
23
26
  fi
24
27
 
25
- MISSING=""
26
-
27
- if [ ! -f "$PROJECT_DIR/SDLC.md" ]; then
28
- MISSING="${MISSING:+${MISSING}, }SDLC.md"
29
- fi
30
-
31
- if [ ! -f "$PROJECT_DIR/TESTING.md" ]; then
32
- MISSING="${MISSING:+${MISSING}, }TESTING.md"
33
- fi
34
-
35
- if [ -n "$MISSING" ]; then
36
- echo "WARNING: Missing SDLC wizard files: ${MISSING}"
37
- echo "Invoke Skill tool, skill=\"setup-wizard\" to generate them."
38
- fi
39
-
40
28
  # Version update check (non-blocking, best-effort).
41
29
  # Fetches npm latest at most once per 24h (ROADMAP #196). Prints a stronger
42
30
  # multi-line nudge when the gap is ≥3 minor versions — the one-liner gets
@@ -258,11 +246,43 @@ fi
258
246
  # Claude Code version check (non-blocking, best-effort)
259
247
  # Gate on CLAUDE_PROJECT_DIR — only Claude Code sets this. Without it, we're
260
248
  # running under Codex/OpenCode where a CC update nudge is misleading (#375).
249
+ # #236(b): was an uncached npm network call on every session start with a
250
+ # bare `!=` comparison (fires in either direction, including when local is
251
+ # actually AHEAD of the cached/published "latest") — the exact bug class
252
+ # already fixed above for the wizard's own version check (#254 Bug 2). Now
253
+ # reuses the same 24h cache + semver_lt direction check.
261
254
  if [ -n "${CLAUDE_PROJECT_DIR:-}" ] && command -v claude > /dev/null 2>&1 && command -v npm > /dev/null 2>&1; then
262
255
  CC_LOCAL=$(claude --version 2>/dev/null | grep -o '[0-9][0-9.]*' | head -1) || true
263
- if [ -n "$CC_LOCAL" ]; then
264
- CC_LATEST=$(npm view @anthropic-ai/claude-code version 2>/dev/null) || true
265
- if [ -n "$CC_LATEST" ] && [ "$CC_LATEST" != "$CC_LOCAL" ]; then
256
+ if [ -n "$CC_LOCAL" ] && [[ "$CC_LOCAL" =~ $SEMVER_RE ]]; then
257
+ CC_CACHE_DIR="${SDLC_WIZARD_CACHE_DIR:-$HOME/.cache/sdlc-wizard}"
258
+ CC_CACHE_FILE="$CC_CACHE_DIR/latest-cc-version"
259
+ CC_LATEST=""
260
+
261
+ if [ -f "$CC_CACHE_FILE" ]; then
262
+ if stat -f %m "$CC_CACHE_FILE" > /dev/null 2>&1; then
263
+ CC_CACHE_MTIME=$(stat -f %m "$CC_CACHE_FILE")
264
+ else
265
+ CC_CACHE_MTIME=$(stat -c %Y "$CC_CACHE_FILE" 2>/dev/null || echo 0)
266
+ fi
267
+ CC_CACHE_AGE=$(( $(date +%s) - CC_CACHE_MTIME ))
268
+ if [ "$CC_CACHE_AGE" -lt 86400 ]; then
269
+ CC_CACHE_CONTENT=$(cat "$CC_CACHE_FILE" 2>/dev/null) || CC_CACHE_CONTENT=""
270
+ if [[ "$CC_CACHE_CONTENT" =~ $SEMVER_RE ]] && ! semver_lt "$CC_CACHE_CONTENT" "$CC_LOCAL"; then
271
+ CC_LATEST="$CC_CACHE_CONTENT"
272
+ fi
273
+ fi
274
+ fi
275
+
276
+ if [ -z "$CC_LATEST" ]; then
277
+ CC_NPM_RESULT=$(npm view @anthropic-ai/claude-code version 2>/dev/null) || true
278
+ if [[ "$CC_NPM_RESULT" =~ $SEMVER_RE ]]; then
279
+ CC_LATEST="$CC_NPM_RESULT"
280
+ mkdir -p "$CC_CACHE_DIR" 2>/dev/null || true
281
+ printf '%s' "$CC_LATEST" > "$CC_CACHE_FILE" 2>/dev/null || true
282
+ fi
283
+ fi
284
+
285
+ if [ -n "$CC_LATEST" ] && semver_lt "$CC_LOCAL" "$CC_LATEST"; then
266
286
  echo "Claude Code update available: ${CC_LOCAL} → ${CC_LATEST} (run: npm install -g @anthropic-ai/claude-code)"
267
287
  fi
268
288
  fi
@@ -1,18 +1,19 @@
1
1
  #!/bin/bash
2
2
  # SessionStart hook — effort/model nudge.
3
3
  #
4
- # Behavior (v1.84.0 update — model-aware floor):
4
+ # Behavior (#440 update — medium joins the floor):
5
5
  # CLAUDE_CODE_EFFORT_LEVEL env var takes precedence over effortLevel in settings.
6
6
  # CC docs: max is session-only in settings.json — only the env var persists it.
7
7
  # This hook cannot detect the active model (SessionStart payload has no model
8
8
  # field, per ROADMAP #180), so it uses a floor that's correct across models:
9
- # `high` is Sonnet 5's and Fable's tested default (escalate to xhigh for hard
10
- # tasks); xhigh is Opus 4.8's floor; max remains the sweet spot on Opus 4.6
11
- # (no xhigh support). Blanket max is WRONG for Sonnet 5/Opus 4.8 (wastes
12
- # tokens for no quality gain see AI_SETUP_LANES.md per-model table).
9
+ # `medium` is Sonnet 5's documented default (CodeRabbit testing: most of the
10
+ # upside at the lowest cost escalate high -> xhigh for hard tasks); `high`
11
+ # is Fable's default; xhigh is Opus 4.8's floor; max remains the sweet spot
12
+ # on Opus 4.6 (no xhigh support). Blanket max is WRONG for Sonnet 5/Opus 4.8
13
+ # (wastes tokens for no quality gain — see AI_SETUP_LANES.md per-model table).
13
14
  #
14
- # effort=high, xhigh, or max -> silent (all acceptable — model-dependent)
15
- # anything else -> LOUD WARNING
15
+ # effort=medium, high, xhigh, or max -> silent (all acceptable — model-dependent)
16
+ # anything else -> LOUD WARNING
16
17
  #
17
18
  # Non-blocking: always exits 0.
18
19
 
@@ -49,32 +50,37 @@ if [ -z "$effort" ]; then
49
50
  done
50
51
  fi
51
52
 
52
- # high/xhigh are always silent (persist fine via settings.json, no CC quirk).
53
- # max is silent EXCEPT when it's settings-only — CC docs: max is session-only
54
- # in settings.json, only the env var actually persists it.
55
- if [ "$effort" = "high" ] || [ "$effort" = "xhigh" ]; then
53
+ # medium/high/xhigh are always silent (persist fine via settings.json, no CC
54
+ # quirk). max is silent EXCEPT when it's settings-only — CC docs: max is
55
+ # session-only in settings.json, only the env var actually persists it.
56
+ if [ "$effort" = "medium" ] || [ "$effort" = "high" ] || [ "$effort" = "xhigh" ]; then
56
57
  exit 0
57
58
  fi
58
59
  if [ "$effort" = "max" ] && [ "$settings_max" -eq 0 ]; then
59
60
  exit 0
60
61
  fi
61
62
 
63
+ # #236(b): unset (no env var, no settings entry) is CC's own current default —
64
+ # not a problem state on its own (e.g. a deliberate Fable-session config).
65
+ # Only warn on an EXPLICITLY set low-effort value or the settings-only-max
66
+ # quirk below (CC silently ignores settings.json's "max" — genuinely
67
+ # non-obvious, worth keeping).
68
+ if [ -z "$effort" ]; then
69
+ exit 0
70
+ fi
71
+
62
72
  if [ "$settings_max" -eq 1 ] && [ -z "${CLAUDE_CODE_EFFORT_LEVEL:-}" ]; then
63
73
  effort_display="max (settings-only — CC ignores this)"
64
- elif [ -z "$effort" ]; then
65
- effort_display="unset"
66
74
  else
67
75
  effort_display="$effort"
68
76
  fi
69
77
 
70
- echo "=============================================================================="
71
- echo " WARNING: effort '$effort_display' — SDLC requires high, xhigh, or max."
72
- echo " Below high = degraded reasoning, shallow TDD, weak self-review."
73
- echo ""
74
- echo " Run: /effort xhigh (or /effort high see AI_SETUP_LANES.md)"
75
- echo " Avoid shell-rc env var persistence — silently overrides model switches."
76
- echo ""
78
+ echo "=============================================================="
79
+ echo " WARNING: effort '$effort_display' — below the SDLC floor (medium)."
80
+ echo " Degraded reasoning, shallow TDD, weak self-review."
81
+ echo " Run: /effort medium (Sonnet 5) or your model's floor — see AI_SETUP_LANES.md"
82
+ echo " Avoid shell-rc env persistence overrides model switches."
77
83
  echo " recommended models: $RECOMMENDED_MODELS"
78
- echo "=============================================================================="
84
+ echo "=============================================================="
79
85
 
80
86
  exit 0
@@ -1,18 +1,22 @@
1
1
  #!/bin/bash
2
- # PreCompact hook - block manual /compact at non-seam boundaries
2
+ # PreCompact hook - block manual /compact mid-git-operation
3
3
  # Fires on manual /compact only (auto-compact is threshold-driven; blocking
4
4
  # it could push past 100% context and lose everything). Matcher: "manual"
5
5
  # in .claude/settings.json.
6
6
  #
7
7
  # Requires Claude Code v2.1.105+ (PreCompact event introduced April 13, 2026).
8
8
  #
9
- # Seam policy: compacting mid-cycle loses evidence the next round needs.
10
- # Block when:
11
- # (1) .reviews/handoff.json status is PENDING_REVIEW or PENDING_RECHECK
12
- # mid-Codex-round, compact after CERTIFIED
13
- # (2) git rebase/merge/cherry-pick in progress
14
- # finish in-progress git operation first
15
- # Allow otherwise.
9
+ # #236(b): the original design also blocked on .reviews/handoff.json being
10
+ # PENDING_REVIEW/PENDING_RECHECK (mid cross-model-review-round). Removed —
11
+ # every real firing on record over ~2.5 months was a false positive (a
12
+ # completed rebase's stale REBASE_HEAD, or an abandoned PENDING handoff),
13
+ # never a true positive, and ~150 of this file's 256 lines were self-heal
14
+ # machinery mitigating that false-positive rate (3 heal paths + dry-run
15
+ # scaffolding). The git-op check below has no such record.
16
+ #
17
+ # Seam policy: compacting mid-git-operation loses evidence the operation
18
+ # needs to finish correctly. Block when a rebase/merge/cherry-pick is in
19
+ # progress; allow otherwise.
16
20
 
17
21
  # Token-bloat fix: when both project + plugin register this hook, plugin yields.
18
22
  # Use parameter expansion (not `dirname`) so the PATH-restricted gh-missing test
@@ -30,164 +34,9 @@ ROOT="${CLAUDE_PROJECT_DIR:-$PWD}"
30
34
 
31
35
  HOLD_REASONS=""
32
36
 
33
- # #240: Dry-run / simulation env vars. Let consumers verify hook behavior
34
- # without mutating real .reviews/handoff.json or .git/ state.
35
- # SDLC_DRY_RUN_HANDOFF_STATUS=<value> overrides handoff.json status
36
- # lookup (skip the file read entirely)
37
- # SDLC_DRY_RUN_GIT_STATE=rebase|merge|cherry-pick — simulates an in-flight
38
- # git operation
39
- # When set, dry-run values short-circuit the real-state checks below. The
40
- # hook still emits the same HOLD/silent decision so consumers can smoke-test
41
- # every code path. No filesystem writes — purely read-only simulation.
42
-
43
- # Check 1: Codex review mid-cycle
44
- # Self-heal paths (ordered by preference):
45
- # (a) #209: handoff has pr_number + gh reports PR MERGED → implicit CERTIFIED (silent)
46
- # (c) #257: handoff has no pr_number BUT every SHA cited in fixes_applied[]
47
- # is in HEAD's ancestry AND .reviews/latest-review.md contains CERTIFIED
48
- # (without "NOT CERTIFIED") → implicit CERTIFIED (silent). Catches the
49
- # solo-developer pattern: write fixes, commit them, run targeted
50
- # recheck, see CERTIFIED in latest-review.md, ship — and forget to
51
- # update handoff.json status. The visible signals (commits landed +
52
- # review file) already say "done" so blocking is high false-positive.
53
- # (b) #229: handoff has no pr_number, no SHA-ancestry heal, but mtime >
54
- # SDLC_HANDOFF_STALE_DAYS days → implicit CERTIFIED with WARN
55
- # (forgotten artifact; blocking forever is worse UX). Default: 14 days.
56
- HANDOFF="$ROOT/.reviews/handoff.json"
57
- # Validate SDLC_HANDOFF_STALE_DAYS as non-negative integer. Anything else
58
- # (empty, "foo", "-3", "10.5") silently falls back to 14 — we don't want a
59
- # typo in the user's env to leak a bash arithmetic error to stderr every
60
- # time the hook runs (caught by Codex P2 review of PR #227).
61
- STALE_DAYS_RAW="${SDLC_HANDOFF_STALE_DAYS:-14}"
62
- case "$STALE_DAYS_RAW" in
63
- ''|*[!0-9]*) STALE_DAYS=14 ;;
64
- *) STALE_DAYS="$STALE_DAYS_RAW" ;;
65
- esac
66
- STALE_WARN=""
67
- # #240: dry-run override skips the real handoff.json read.
68
- if [ -n "${SDLC_DRY_RUN_HANDOFF_STATUS:-}" ]; then
69
- STATUS="$SDLC_DRY_RUN_HANDOFF_STATUS"
70
- case "$STATUS" in
71
- PENDING_REVIEW|PENDING_RECHECK)
72
- HOLD_REASONS="${HOLD_REASONS} - Codex review is ${STATUS}. Round-1 evidence lives in this context — compacting now loses what round-2 needs to re-verify.
73
- Resolve: wait for CERTIFIED (or escalate) before /compact."$'\n'
74
- ;;
75
- esac
76
- elif [ -f "$HANDOFF" ]; then
77
- STATUS=$(grep -o '"status"[[:space:]]*:[[:space:]]*"[^"]*"' "$HANDOFF" 2>/dev/null | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
78
- case "$STATUS" in
79
- PENDING_REVIEW|PENDING_RECHECK)
80
- PR_NUMBER=$(grep -o '"pr_number"[[:space:]]*:[[:space:]]*[0-9][0-9]*' "$HANDOFF" 2>/dev/null | head -1 | grep -o '[0-9][0-9]*$')
81
- HEALED=0
82
- if [ -n "$PR_NUMBER" ]; then
83
- # Path (a): PR-linked self-heal (#209). Applies regardless of mtime —
84
- # an OPEN PR with old mtime is still a live review.
85
- if command -v gh >/dev/null 2>&1; then
86
- PR_STATE=$(gh pr view "$PR_NUMBER" --json state --jq .state 2>/dev/null)
87
- [ "$PR_STATE" = "MERGED" ] && HEALED=1
88
- fi
89
- else
90
- # Path (c) #257: SHA-ancestry self-heal. Look for git SHAs cited
91
- # in fixes_applied[]; if every cited SHA is reachable from HEAD
92
- # AND .reviews/latest-review.md says CERTIFIED, the review IS
93
- # closed, the user just forgot to bump status. Silent heal.
94
- REVIEW_MD="$ROOT/.reviews/latest-review.md"
95
- if [ -f "$REVIEW_MD" ] \
96
- && grep -qE '\bCERTIFIED\b' "$REVIEW_MD" 2>/dev/null \
97
- && ! grep -qE '\bNOT CERTIFIED\b' "$REVIEW_MD" 2>/dev/null; then
98
- # Extract the fixes_applied[] block via bracket-depth
99
- # tracking — naive `/\]/` matching breaks on `]` inside
100
- # string literals (e.g. "[x] FIXED in <sha>" markdown
101
- # checkboxes), which would let phantom SHAs after the
102
- # broken-early bracket leak past path (c) and false-heal.
103
- # Codex P1 round 1.
104
- FIXES_BLOCK=$(awk '
105
- BEGIN { in_block = 0; depth = 0; started = 0 }
106
- /"fixes_applied"/ { in_block = 1 }
107
- in_block {
108
- print
109
- in_string = 0
110
- escaped = 0
111
- for (i = 1; i <= length($0); i++) {
112
- c = substr($0, i, 1)
113
- # Honor JSON backslash escapes: \" inside a
114
- # string is a literal quote, NOT a string
115
- # terminator. Without this, a fixes_applied
116
- # entry containing `\"]` falsely flips the
117
- # in_string flag and exits the array early —
118
- # letting later phantom SHAs leak past path
119
- # (c) and false-heal (Codex round 2 P1).
120
- if (escaped) { escaped = 0; continue }
121
- if (c == "\\") { escaped = 1; continue }
122
- if (c == "\"") { in_string = !in_string; continue }
123
- if (in_string) continue
124
- if (c == "[") { depth++; started = 1 }
125
- else if (c == "]") { depth-- }
126
- }
127
- if (started && depth <= 0) in_block = 0
128
- }
129
- ' "$HANDOFF" 2>/dev/null)
130
- if [ -n "$FIXES_BLOCK" ] && [ -d "$ROOT/.git" ]; then
131
- # Strip UUIDs (8-4-4-4-12 hex pattern) BEFORE extracting
132
- # SHA candidates. UUIDs have a fixed shape; their hex
133
- # segments would otherwise match \b[0-9a-f]{7,40}\b and
134
- # fail the ancestry check, false-blocking certified
135
- # reviews that cite UUIDs in fixes_applied (mission
136
- # UUIDs, Linear/Jira ticket IDs, etc.). Codex P2 round 1.
137
- # POSIX-compatible: no `\b` (BSD sed doesn't support it).
138
- # The hyphenated 8-4-4-4-12 shape is specific enough
139
- # that false-stripping a real SHA is implausible.
140
- CLEANED=$(echo "$FIXES_BLOCK" | sed -E 's/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}//g')
141
- SHAS=$(echo "$CLEANED" | grep -oE '\b[0-9a-f]{7,40}\b' | sort -u)
142
- if [ -n "$SHAS" ]; then
143
- # Every cited SHA must be reachable from HEAD —
144
- # phantom SHAs (e.g. typos, references to other
145
- # repos) correctly fail ancestry and block the heal.
146
- ALL_IN_HEAD=1
147
- for sha in $SHAS; do
148
- if ! git -C "$ROOT" merge-base --is-ancestor "$sha" HEAD 2>/dev/null; then
149
- ALL_IN_HEAD=0
150
- break
151
- fi
152
- done
153
- [ "$ALL_IN_HEAD" -eq 1 ] && HEALED=1
154
- fi
155
- fi
156
- fi
157
- # Path (b): stale-handoff auto-expire (#229). Only when no pr_number
158
- # AND path (c) didn't already heal. We must not short-circuit
159
- # PR-linked reviews.
160
- if [ "$HEALED" -ne 1 ]; then
161
- # Try GNU stat first (Linux: `-c %Y` gives mtime, BSD stat errors out
162
- # so `||` fires). Then BSD stat (macOS: `-f %m` gives mtime). The
163
- # reverse order fails on Linux because `stat -f` on GNU means
164
- # `--file-system` and dumps filesystem info to stdout (non-error).
165
- MTIME=$(stat -c %Y "$HANDOFF" 2>/dev/null || stat -f %m "$HANDOFF" 2>/dev/null)
166
- # Numeric guard: if stat fails both flavors or returns junk, skip
167
- # the stale-check entirely and fall through to HOLD (safe default).
168
- case "$MTIME" in
169
- ''|*[!0-9]*) ;;
170
- *)
171
- NOW=$(date +%s)
172
- AGE_DAYS=$(( (NOW - MTIME) / 86400 ))
173
- if [ "$AGE_DAYS" -ge "$STALE_DAYS" ]; then
174
- HEALED=1
175
- STALE_WARN="WARN: handoff.json is ${STATUS} and ${AGE_DAYS}d old with no pr_number — treating as stale CERTIFIED (override: set SDLC_HANDOFF_STALE_DAYS or close out the review)."
176
- fi
177
- ;;
178
- esac
179
- fi
180
- fi
181
- if [ "$HEALED" -ne 1 ]; then
182
- HOLD_REASONS="${HOLD_REASONS} - Codex review is ${STATUS}. Round-1 evidence lives in this context — compacting now loses what round-2 needs to re-verify.
183
- Resolve: wait for CERTIFIED (or escalate) before /compact."$'\n'
184
- fi
185
- ;;
186
- esac
187
- fi
188
-
189
- # Check 2: in-progress git operation
190
- # #240: dry-run override simulates a git op without needing a real .git/.
37
+ # #240: SDLC_DRY_RUN_GIT_STATE=rebase|merge|cherry-pick simulates an
38
+ # in-flight git operation without needing a real .git/ state, so consumers
39
+ # can smoke-test every code path (read-only, no writes).
191
40
  GITDIR="$ROOT/.git"
192
41
  # Step 1: when dry-run var matches a known scenario, simulate it.
193
42
  # Otherwise (unset, empty, or unknown value) fall through to the real
@@ -241,16 +90,12 @@ fi
241
90
 
242
91
  if [ -n "$HOLD_REASONS" ]; then
243
92
  {
244
- echo "HOLD: manual /compact at a non-seam. Compacting mid-cycle loses evidence the next round needs."
93
+ echo "HOLD: manual /compact mid-git-operation."
245
94
  echo ""
246
95
  echo "$HOLD_REASONS"
247
- echo "Natural seams: commit boundary, Codex CERTIFIED, PR merge, ROADMAP item DONE."
248
96
  echo "Override: resolve the blocker above, or temporarily disable this hook in .claude/settings.json."
249
97
  } >&2
250
98
  exit 2
251
99
  fi
252
100
 
253
- # Stale-handoff unblock: emit a one-line WARN so the user knows the hook
254
- # self-healed over an abandoned PENDING artifact (but still allow /compact).
255
- [ -n "$STALE_WARN" ] && echo "$STALE_WARN" >&2
256
101
  exit 0
@@ -35,19 +35,12 @@ else
35
35
  exit 0
36
36
  fi
37
37
 
38
- # Effort auto-bump (ROADMAP #195). Watches this UserPromptSubmit payload for
39
- # LOW-confidence / FAILED-repeatedly / CONFUSED phrases, logs a timestamped
40
- # signal, and emits a loud '/effort xhigh' nudge when 2 signals land inside
41
- # a 30-minute window. Enforces the SDLC confidence table mid-session so
42
- # Claude stops burning budget at 'high' after confidence drops.
43
- EFFORT_CACHE_DIR="${SDLC_WIZARD_CACHE_DIR:-$HOME/.cache/sdlc-wizard}"
44
- EFFORT_SIGNALS="$EFFORT_CACHE_DIR/effort-signals.log"
45
- PROMPT_TEXT=""
38
+ # #236(b): the ROADMAP #195 effort auto-bump detector that used to live here
39
+ # (phrase-matching low-confidence signals, loud '/effort xhigh' nudge after
40
+ # ≥2 in 30 min) was removed never fired in ~2 months of live use, the
41
+ # largest unproven component in this file. SESSION_ID is still needed below
42
+ # for the BASELINE fires-once sentinel, so stdin is still read for it.
46
43
  SESSION_ID=""
47
- # Read stdin once regardless of jq availability — session_id extraction
48
- # is jq-independent (Codex round 1 P1: BASELINE gate failed when jq was
49
- # missing or broken). Prompt extraction still needs jq because prompt
50
- # content can contain arbitrary multi-line text + escapes.
51
44
  if [ ! -t 0 ]; then
52
45
  STDIN_JSON=$(cat)
53
46
  if [ -n "$STDIN_JSON" ]; then
@@ -59,57 +52,6 @@ if [ ! -t 0 ]; then
59
52
  | grep -o '"session_id"[[:space:]]*:[[:space:]]*"[^"]*"' \
60
53
  | head -1 \
61
54
  | sed 's/.*"\([^"]*\)"$/\1/')
62
- if command -v jq > /dev/null 2>&1; then
63
- PROMPT_TEXT=$(printf '%s' "$STDIN_JSON" | jq -r '.prompt // empty' 2>/dev/null) || PROMPT_TEXT=""
64
- fi
65
- fi
66
- fi
67
- if [ -n "$PROMPT_TEXT" ]; then
68
- LOWER=$(printf '%s' "$PROMPT_TEXT" | tr '[:upper:]' '[:lower:]')
69
- SIGNAL_REASON=""
70
- # Every trigger requires first-person ownership or a structured-label
71
- # form, so educational/quoted mentions ("How do I name a low confidence
72
- # badge?", "What does 'failed again' mean?") don't fire.
73
- case "$LOWER" in
74
- *"i'm stuck"*|*"i am stuck"*|*"im stuck"*|\
75
- *"i'm confused"*|*"i am confused"*|*"im confused"*|\
76
- *"i tried twice"*|*"i've tried twice"*|*"ive tried twice"*|\
77
- *"i can't figure"*|*"i cant figure"*|\
78
- *"i'm not sure why"*|*"i am not sure why"*|*"im not sure why"*|\
79
- *"my confidence is low"*|*"my confidence: low"*|*"confidence: low"*|\
80
- *"it's still failing"*|*"its still failing"*|\
81
- *"it keeps failing"*|*"this keeps failing"*|\
82
- *"it failed again"*|*"this failed again"*|\
83
- *"failed twice"*|*"failed 2x"*)
84
- SIGNAL_REASON="low"
85
- ;;
86
- esac
87
- if [ -n "$SIGNAL_REASON" ]; then
88
- # Group the write so redirection errors (e.g., unwritable HOME,
89
- # cache-dir-is-a-file) land on /dev/null instead of leaking to stderr.
90
- {
91
- if mkdir -p "$EFFORT_CACHE_DIR" && [ -d "$EFFORT_CACHE_DIR" ]; then
92
- # Prune entries older than 1h on every write to cap log size.
93
- if [ -f "$EFFORT_SIGNALS" ]; then
94
- PRUNE_THRESH=$(( $(date +%s) - 3600 ))
95
- awk -v t="$PRUNE_THRESH" '$1+0 >= t' "$EFFORT_SIGNALS" > "${EFFORT_SIGNALS}.tmp" \
96
- && mv "${EFFORT_SIGNALS}.tmp" "$EFFORT_SIGNALS"
97
- fi
98
- printf '%s\t%s\n' "$(date +%s)" "$SIGNAL_REASON" >> "$EFFORT_SIGNALS"
99
- fi
100
- } 2>/dev/null || true
101
- fi
102
- fi
103
- if [ -f "$EFFORT_SIGNALS" ]; then
104
- NOW=$(date +%s)
105
- THRESH=$(( NOW - 1800 ))
106
- RECENT=$(awk -v t="$THRESH" '$1+0 >= t' "$EFFORT_SIGNALS" 2>/dev/null | wc -l | tr -d ' ')
107
- if [ "${RECENT:-0}" -ge 2 ]; then
108
- echo ""
109
- echo "!! EFFORT BUMP REQUIRED: ${RECENT} low-confidence signals in last 30 min !!"
110
- echo " Run /effort xhigh NOW — spinning at 'high' after confidence drops wastes budget."
111
- echo " (Auto-enforcement of the SDLC confidence table. ROADMAP #195.)"
112
- echo ""
113
55
  fi
114
56
  fi
115
57
 
@@ -55,7 +55,22 @@ fi
55
55
  # slash BEFORE "src", so a cwd-relative file_path like "src/app.js" (no
56
56
  # leading slash) never matched and the whole gate silently no-op'd. Second
57
57
  # clause catches the relative form.
58
- if [[ "$FILE_PATH" == *"/src/"* || "$FILE_PATH" == "src/"* ]]; then
58
+ # #236(b): SDLC_TDD_SRC_PATTERN overrides the gated path(s) via env var (set
59
+ # it in the hook's "command" string in .claude/settings.json) instead of
60
+ # editing this shared/distributed script — a "|"-separated list of
61
+ # substrings, e.g. "hooks/|cli/". Falls back to the generic /src/ pattern
62
+ # when unset, so every other install is unaffected.
63
+ SRC_MATCH=0
64
+ if [ -n "${SDLC_TDD_SRC_PATTERN:-}" ]; then
65
+ IFS='|' read -ra _tdd_patterns <<< "$SDLC_TDD_SRC_PATTERN"
66
+ for _p in "${_tdd_patterns[@]}"; do
67
+ [[ "$FILE_PATH" == *"$_p"* ]] && SRC_MATCH=1 && break
68
+ done
69
+ else
70
+ [[ "$FILE_PATH" == *"/src/"* || "$FILE_PATH" == "src/"* ]] && SRC_MATCH=1
71
+ fi
72
+
73
+ if [ "$SRC_MATCH" -eq 1 ]; then
59
74
  # #436 gate: block implementation-first edits when no test file has been
60
75
  # touched yet this session. Degrades to allow without session_id.
61
76
  if [ -n "$SESSION_ID" ]; then
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-sdlc-wizard",
3
- "version": "1.85.0",
3
+ "version": "1.87.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"
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: feedback
3
3
  description: Submit feedback, bug reports, feature requests, or share SDLC patterns you've discovered. Privacy-first — always asks before scanning.
4
- argument-hint: [optional: bug | feature | pattern | improvement]
4
+ argument-hint: "[optional: bug | feature | pattern | improvement]"
5
5
  effort: medium
6
6
  ---
7
7
  # Feedback — Community Contribution Loop
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: sdlc
3
3
  description: Full SDLC workflow for implementing features, fixing bugs, refactoring code, testing, releasing, publishing, and deploying. Use this skill when implementing, fixing, refactoring, testing, adding features, building new code, or releasing/publishing/deploying.
4
- argument-hint: [task description]
4
+ argument-hint: "[task description]"
5
5
  ---
6
6
  # SDLC Skill - Full Development Workflow
7
7
 
@@ -9,7 +9,7 @@ argument-hint: [task description]
9
9
 
10
10
  This skill is loaded from **`.claude/skills/sdlc/SKILL.md`** in the active repo (symlinked to `skills/sdlc/SKILL.md` in the wizard's source tree). Claude Code prefers repo-local skills over global (`~/.claude/skills/sdlc/SKILL.md`) when both exist with the same name — the repo-local copy is the project's authoritative workflow contract. Use global skills only for cross-repo personal tooling (e.g. `feedback`, `revise-claude-md`); use repo-local for implementation, tests, release, and verification in this repo.
11
11
 
12
- If unsure which copy is active, compare `head -5 .claude/skills/sdlc/SKILL.md` against `head -5 ~/.claude/skills/sdlc/SKILL.md`. The repo-local copy wins. Don't mix guidance from both — pick the source for this repo and stay there.
12
+ If unsure which copy is active, compare `head -5` of both the repo-local copy wins. Don't mix guidance from both.
13
13
 
14
14
  ## Task
15
15
  $ARGUMENTS
@@ -119,7 +119,7 @@ Native `/goal <condition>` (**v2.1.143+**). Haiku evaluator re-checks transcript
119
119
 
120
120
  ## Recommended Model
121
121
 
122
- **Recommended: Sonnet 5 `high`→`xhigh`** — beats Opus 4.6, ~5x less quota. Escalate to **Opus 4.8 `xhigh`** when stuck. **Opus 4.6 `max`** valid for consistency. See `AI_SETUP_LANES.md`.
122
+ **Recommended: Sonnet 5 `medium`→`high`→`xhigh`** — beats Opus 4.6 on benchmarks, generally lower quota (savings narrow at high/xhigh). Escalate to **Opus 4.8 `xhigh`** when stuck. **Opus 4.6 `max`** valid for consistency. See `AI_SETUP_LANES.md`.
123
123
 
124
124
  **Effort is model-aware, not blanket `max`** — `max` overthinks on Sonnet 5/Opus 4.8. Set via `/effort` per session, not a shell-rc env var (overrides post-switch — see SDLC.md). `/model` persists; picker `s` does not.
125
125
 
@@ -137,20 +137,18 @@ The loop goes back to PLANNING, not TDD RED. Run `/code-review`; issues at confi
137
137
 
138
138
  ## Cross-Model Review (REQUIRED for High-Stakes)
139
139
 
140
- **When to run:** high-stakes changes (auth, payments, data), releases/publishes, complex refactors. **When to skip (log justification):** trivial, hotfixes, risk < review cost. **Prerequisites:** Codex CLI + OpenAI API key. **Reviewer:** `gpt-5.5` xhigh — adversarial diversity.
140
+ **When to run:** high-stakes changes (auth, payments, data), releases/publishes, complex refactors. **When to skip (log justification):** trivial, hotfixes, risk < review cost. **Prerequisites:** Codex CLI + OpenAI API key. **Reviewer:** `gpt-5.6-sol` xhigh — adversarial diversity.
141
141
 
142
142
  PROTOCOL is universal across domains; only `review_instructions` and `verification_checklist` change.
143
143
 
144
144
  1. **Preflight** (`.reviews/preflight-{review_id}.md`) — what you already checked: `/code-review` passed, tests passing, manual verifications, known limits. Reduces reviewer findings to 0-1/round.
145
145
  2. **Mission-first handoff** (`.reviews/handoff.json`) — required keys: `"review_id"`, `"status": "PENDING_REVIEW"`, `"round": 1`, `"mission"`/`"success"`/`"failure"` (without them you get "looks good"), `"files_changed"`, `"verification_checklist"` (verification checklist with file:line refs — NOT generic), `"review_instructions"`, `"preflight_path"`. Optional `"pr_number":` opts into PreCompact self-heal (#209: MERGED → implicit CERTIFIED).
146
- 3. **Run reviewer:** `codex exec -c 'model_reasoning_effort="xhigh"' -s danger-full-access -o .reviews/latest-review.md "<prompt>" < /dev/null`. Always `xhigh`. Bash tool requires `run_in_background: true` + `dangerouslyDisableSandbox: true`; always append `< /dev/null`. **Why:** `< /dev/null` prevents codex stdin-hang at S/0% CPU; `run_in_background: true` avoids the Bash 10-min (`600000` ms) `timeout` cap that force-kills foreground codex (multi-artifact bundles take 5–30 min). xhigh 1–30 min; wrapper's `STALL_SECONDS=1800` controls it. Heartbeat: `scripts/codex-review-with-progress.sh`. Foreground burned 70 min on a 7-min review (#364).
147
- 4. **Dialogue loop:** per-finding response (`{"finding": "1", "action": "FIXED|DISPUTED|ACCEPTED", "summary": "..."}` in `.reviews/response.json`). Bump round, set status `PENDING_RECHECK`, add `fixes_applied` (numbered, file:line). Recheck prompt: "TARGETED RECHECK. FIXED → verify certify condition. DISPUTED → ACCEPT if sound, REJECT with reasoning. ACCEPTED → verify applied. No new findings unless P0." **NEVER unilaterally dismiss** — always run the recheck. It's a conversation: the reviewer may accept your dispute or counter with evidence you missed.
146
+ 3. **Run reviewer:** `codex exec -c 'model_reasoning_effort="xhigh"' -s danger-full-access -o .reviews/latest-review.md "<prompt>" < /dev/null`. Always `xhigh`. Bash tool requires `run_in_background: true` + `dangerouslyDisableSandbox: true`; always append `< /dev/null`. **Why:** `< /dev/null` prevents codex stdin-hang at S/0% CPU; `run_in_background: true` avoids the Bash 10-min (`600000` ms) `timeout` cap that force-kills foreground codex (multi-artifact bundles take 5–30 min). xhigh 1–30 min; wrapper's `STALL_SECONDS=1800` controls it. Foreground burned 70 min on a 7-min review (#364).
147
+ 4. **Dialogue loop:** per-finding response (`{"finding": "1", "action": "FIXED|DISPUTED|ACCEPTED", "summary": "..."}` in `.reviews/response.json`). Bump round, set status `PENDING_RECHECK`, add `fixes_applied` (numbered, file:line). Recheck prompt: "TARGETED RECHECK. FIXED → verify certify condition. DISPUTED → ACCEPT if sound, REJECT with reasoning. ACCEPTED → verify applied. No new findings unless P0." **NEVER unilaterally dismiss** — always run the recheck. It's a conversation: the reviewer may accept your dispute or counter with evidence you missed. **On CERTIFIED, write `"commit_sha": "<git rev-parse HEAD>"` into `handoff.json`** — the gate hook (#437) treats a missing/mismatched SHA as stale, not just the status string.
148
148
 
149
149
  **Convergence:** 2 rounds sweet spot, 3 max, escalate after — except large migrations: judge by finding trend, not count.
150
150
 
151
- **Enforcement:** `hooks/goal-confidence-check.sh` warns when `/goal` skips the 95%-confidence or DLC-binding gates (#360).
152
-
153
- **Multi-reviewer:** respond to each independently (no shared anchoring). **Non-code domains:** add `"audience"`/`"stakes"` keys.
151
+ **Multi-reviewer:** respond to each independently. **Non-code domains:** add `"audience"`/`"stakes"` keys.
154
152
 
155
153
  ### Release Review Focus
156
154
 
@@ -275,7 +273,7 @@ Don't fix only the symptom. Add a gate so it can't happen again. Example: PR #14
275
273
  - Auto-compact fires at ~95%; `/usage` shows what's driving token spend
276
274
  - After committing a PR, `/clear` before next feature
277
275
  - `--bare` mode (v2.1.81+) skips ALL hooks/skills/LSP/plugins. Scripted headless only — never normal development.
278
- - Custom subagents (`.claude/agents/`) run autonomously and return results. Skills guide behavior; agents do work. Use for parallel tasks or fresh context. Examples: `sdlc-reviewer`, `ci-debug`, `test-writer`.
276
+ - Custom subagents (`.claude/agents/`) run autonomously and return results. Skills guide behavior; agents do work. Use for parallel tasks or fresh context.
279
277
 
280
278
  ## Design System Check (UI Changes Only)
281
279
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: setup-wizard
3
3
  description: Setup wizard — scans codebase, builds confidence per data point, only asks what it can't figure out, generates SDLC files. Use for first-time setup or re-running setup.
4
- argument-hint: [optional: regenerate | verify-only]
4
+ argument-hint: "[optional: regenerate | verify-only]"
5
5
  effort: high
6
6
  ---
7
7
  # Setup Wizard - Confidence-Driven Project Configuration
@@ -245,7 +245,7 @@ The output is JSON: `{ tier: "simple" | "complex", score, signals }`. Use the re
245
245
  > How do you want to configure the model for this repo?
246
246
  >
247
247
  > - **[N] No pin (default):** Auto-mode. CC picks model per turn. Simplest, no advisor.
248
- > - **[s] Sonnet 5 + Fable** *(Setup A — recommended if you want a pin):* Pins `model: "sonnet"`, `advisorModel: "fable"`. Native 1M context, no `[1m]` suffix needed. Beats Opus 4.6 on every coding benchmark at ~5x less Max quota per turn. Effort: `high`, escalate to `xhigh` for hard tasks.
248
+ > - **[s] Sonnet 5 + Fable** *(Setup A — recommended if you want a pin):* Pins `model: "sonnet"`, `advisorModel: "fable"`. Native 1M context, no `[1m]` suffix needed. Beats Opus 4.6 on every coding benchmark, generally lower Max quota. Effort: `medium`, escalate `high` `xhigh` for hard tasks.
249
249
  > - **[o] OpusPlan Hybrid** *(Setup C — cost-conscious, still want Opus reasoning):* Pins `model: "opusplan"`. Opus 4.8 plans (Shift+Tab), Sonnet 5 executes. Max-bundled. No API credit drain (#390).
250
250
  > - **[b] Opus 4.6 Stability** *(Setup B — legacy flagship, proven consistency for high-stakes/complex repos):* Pins `model: "claude-opus-4-6"`. Opus 4.6 max everywhere. Max-bundled (auto-upgrades to 1M on Max plans).
251
251
  >
@@ -259,14 +259,14 @@ The output is JSON: `{ tier: "simple" | "complex", score, signals }`. Use the re
259
259
  {
260
260
  "model": "sonnet",
261
261
  "advisorModel": "fable",
262
- "effortLevel": "high",
262
+ "effortLevel": "medium",
263
263
  "env": {
264
264
  "CLAUDE_AUTOCOMPACT_PCT_OVERRIDE": "75"
265
265
  }
266
266
  }
267
267
  ```
268
268
 
269
- Tell the user: "Sonnet 5 + Fable — the wizard's recommended pin (Setup A). Effort defaults to `high`, escalate via `/effort xhigh` for hard tasks. Requires CC v2.1.170+ — run `! claude update` if needed."
269
+ Tell the user: "Sonnet 5 + Fable — the wizard's recommended pin (Setup A). Effort: `medium`, escalate `/effort high` → `xhigh` for hard tasks. Requires CC v2.1.170+ — run `! claude update` if needed."
270
270
 
271
271
  **If the user answers `o` (opusplan):** Edit `.claude/settings.json` and add:
272
272