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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/AI_SETUP_LANES.md +28 -22
- package/CHANGELOG.md +28 -0
- package/CLAUDE_CODE_SDLC_WIZARD.md +29 -27
- package/README.md +16 -8
- package/cli/init.js +0 -1
- package/cli/templates/settings.json +0 -4
- package/hooks/codex-gate-check.sh +49 -6
- package/hooks/codex-review-stop-check.sh +6 -1
- package/hooks/hooks.json +0 -4
- package/hooks/instructions-loaded-check.sh +39 -19
- package/hooks/model-effort-check.sh +27 -21
- package/hooks/precompact-seam-check.sh +16 -171
- package/hooks/sdlc-prompt-check.sh +5 -63
- package/hooks/tdd-pretool-check.sh +16 -1
- package/package.json +1 -1
- package/skills/feedback/SKILL.md +1 -1
- package/skills/sdlc/SKILL.md +8 -10
- package/skills/setup/SKILL.md +4 -4
- package/skills/update/SKILL.md +5 -6
- package/hooks/goal-confidence-check.sh +0 -82
|
@@ -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
|
-
|
|
265
|
-
|
|
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 (
|
|
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
|
-
# `
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
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
|
|
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
|
|
53
|
-
# max is silent EXCEPT when it's settings-only — CC docs: max is
|
|
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' —
|
|
72
|
-
echo "
|
|
73
|
-
echo ""
|
|
74
|
-
echo "
|
|
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
|
|
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
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
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:
|
|
34
|
-
# without
|
|
35
|
-
#
|
|
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
|
|
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
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
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
|
-
|
|
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
package/skills/feedback/SKILL.md
CHANGED
|
@@ -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
|
package/skills/sdlc/SKILL.md
CHANGED
|
@@ -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
|
|
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,
|
|
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.
|
|
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.
|
|
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
|
-
**
|
|
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.
|
|
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
|
|
package/skills/setup/SKILL.md
CHANGED
|
@@ -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
|
|
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": "
|
|
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
|
|
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
|
|