codeforge-dev 1.9.0 → 1.10.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.
Files changed (32) hide show
  1. package/.devcontainer/.env +3 -0
  2. package/.devcontainer/CHANGELOG.md +56 -0
  3. package/.devcontainer/CLAUDE.md +29 -8
  4. package/.devcontainer/README.md +61 -2
  5. package/.devcontainer/config/defaults/main-system-prompt.md +162 -128
  6. package/.devcontainer/config/defaults/rules/spec-workflow.md +10 -2
  7. package/.devcontainer/connect-external-terminal.sh +17 -17
  8. package/.devcontainer/devcontainer.json +143 -144
  9. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/architect.md +4 -3
  10. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/doc-writer.md +3 -3
  11. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/spec-writer.md +21 -11
  12. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/hooks/hooks.json +1 -1
  13. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/advisory-test-runner.py +186 -13
  14. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/spec-reminder.py +2 -1
  15. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/documentation-patterns/SKILL.md +1 -1
  16. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-check/SKILL.md +22 -10
  17. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-init/SKILL.md +7 -5
  18. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-init/references/backlog-template.md +19 -3
  19. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-init/references/roadmap-template.md +28 -8
  20. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-new/SKILL.md +15 -6
  21. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-new/references/template.md +24 -5
  22. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-refine/SKILL.md +194 -0
  23. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-update/SKILL.md +19 -1
  24. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/specification-writing/SKILL.md +19 -12
  25. package/.devcontainer/scripts/check-setup.sh +24 -25
  26. package/.devcontainer/scripts/setup-aliases.sh +88 -76
  27. package/.devcontainer/scripts/setup-projects.sh +172 -131
  28. package/.devcontainer/scripts/setup-terminal.sh +48 -0
  29. package/.devcontainer/scripts/setup-update-claude.sh +49 -107
  30. package/.devcontainer/scripts/setup.sh +4 -17
  31. package/README.md +2 -2
  32. package/package.json +1 -1
@@ -17,146 +17,187 @@ EXCLUDED_DIRS=".claude .gh .tmp .devcontainer .config node_modules .git"
17
17
  # --- Helpers ---
18
18
 
19
19
  is_excluded() {
20
- local name="$1"
21
- # Skip hidden directories (start with .)
22
- [[ "$name" == .* ]] && return 0
23
- # Skip explicitly excluded names
24
- for excluded in $EXCLUDED_DIRS; do
25
- [[ "$name" == "$excluded" ]] && return 0
26
- done
27
- return 1
20
+ local name="$1"
21
+ # Skip hidden directories (start with .)
22
+ [[ "$name" == .* ]] && return 0
23
+ # Skip explicitly excluded names
24
+ for excluded in $EXCLUDED_DIRS; do
25
+ [[ "$name" == "$excluded" ]] && return 0
26
+ done
27
+ return 1
28
+ }
29
+
30
+ has_project_markers() {
31
+ local dir="$1"
32
+ [ -d "$dir/.git" ] || [ -f "$dir/package.json" ] || [ -f "$dir/pyproject.toml" ] ||
33
+ [ -f "$dir/Cargo.toml" ] || [ -f "$dir/go.mod" ] || [ -f "$dir/deno.json" ] ||
34
+ [ -f "$dir/Makefile" ] || [ -f "$dir/CLAUDE.md" ]
28
35
  }
29
36
 
30
37
  detect_tags() {
31
- local dir="$1"
32
- local tags=()
33
-
34
- [ -d "$dir/.git" ] && tags+=("git")
35
- [ -f "$dir/package.json" ] && tags+=("node")
36
- [ -f "$dir/pyproject.toml" ] && tags+=("python")
37
- [ -f "$dir/Cargo.toml" ] && tags+=("rust")
38
- [ -f "$dir/go.mod" ] && tags+=("go")
39
- [ -f "$dir/deno.json" ] && tags+=("deno")
40
- [ -f "$dir/Makefile" ] && tags+=("make")
41
- [ -f "$dir/CLAUDE.md" ] && tags+=("claude")
42
-
43
- # Always add "auto" tag to mark as script-managed
44
- tags+=("auto")
45
-
46
- # If no markers found (only "auto"), add "folder" tag
47
- if [ ${#tags[@]} -eq 1 ]; then
48
- tags=("folder" "${tags[@]}")
49
- fi
50
-
51
- # Output as JSON array
52
- printf '%s\n' "${tags[@]}" | jq -R . | jq -s .
38
+ local dir="$1"
39
+ local tags=()
40
+
41
+ [ -d "$dir/.git" ] && tags+=("git")
42
+ [ -f "$dir/package.json" ] && tags+=("node")
43
+ [ -f "$dir/pyproject.toml" ] && tags+=("python")
44
+ [ -f "$dir/Cargo.toml" ] && tags+=("rust")
45
+ [ -f "$dir/go.mod" ] && tags+=("go")
46
+ [ -f "$dir/deno.json" ] && tags+=("deno")
47
+ [ -f "$dir/Makefile" ] && tags+=("make")
48
+ [ -f "$dir/CLAUDE.md" ] && tags+=("claude")
49
+
50
+ # Always add "auto" tag to mark as script-managed
51
+ tags+=("auto")
52
+
53
+ # If no markers found (only "auto"), add "folder" tag
54
+ if [ ${#tags[@]} -eq 1 ]; then
55
+ tags=("folder" "${tags[@]}")
56
+ fi
57
+
58
+ # Output as JSON array
59
+ printf '%s\n' "${tags[@]}" | jq -R . | jq -s .
60
+ }
61
+
62
+ register_project() {
63
+ local projects_json="$1"
64
+ local name="$2"
65
+ local dir="$3"
66
+ local tags
67
+ tags=$(detect_tags "$dir")
68
+ echo "$projects_json" | jq \
69
+ --arg name "$name" \
70
+ --arg path "$dir" \
71
+ --argjson tags "$tags" \
72
+ '. += [{"name": $name, "rootPath": ($path | rtrimstr("/")), "tags": $tags, "enabled": true}]'
53
73
  }
54
74
 
55
75
  scan_and_update() {
56
- # Build the new auto-detected projects array
57
- local new_projects="[]"
58
-
59
- for dir in "$WORKSPACE_ROOT"/*/; do
60
- [ -d "$dir" ] || continue
61
- local name
62
- name=$(basename "$dir")
63
-
64
- is_excluded "$name" && continue
65
-
66
- local tags
67
- tags=$(detect_tags "$dir")
68
-
69
- new_projects=$(echo "$new_projects" | jq \
70
- --arg name "$name" \
71
- --arg path "$dir" \
72
- --argjson tags "$tags" \
73
- '. += [{"name": $name, "rootPath": ($path | rtrimstr("/")), "tags": $tags, "enabled": true}]')
74
- done
75
-
76
- # Read existing projects.json (or empty array if missing/invalid)
77
- local existing="[]"
78
- if [ -f "$PROJECTS_FILE" ] && jq empty "$PROJECTS_FILE" 2>/dev/null; then
79
- existing=$(cat "$PROJECTS_FILE")
80
- fi
81
-
82
- # Separate user entries (no "auto" tag) from auto entries
83
- local user_entries
84
- user_entries=$(echo "$existing" | jq '[.[] | select(.tags | index("auto") | not)]')
85
-
86
- # Merge: user entries + new auto-detected entries
87
- local merged
88
- merged=$(jq -n --argjson user "$user_entries" --argjson auto "$new_projects" '$user + $auto')
89
-
90
- # Only write if content changed (avoid unnecessary file writes that trigger Project Manager reloads)
91
- local current_hash new_hash
92
- current_hash=""
93
- [ -f "$PROJECTS_FILE" ] && current_hash=$(md5sum "$PROJECTS_FILE" 2>/dev/null | cut -d' ' -f1)
94
-
95
- local tmp_file
96
- tmp_file=$(mktemp)
97
- echo "$merged" | jq '.' > "$tmp_file"
98
- new_hash=$(md5sum "$tmp_file" | cut -d' ' -f1)
99
-
100
- if [ "$current_hash" != "$new_hash" ]; then
101
- mv "$tmp_file" "$PROJECTS_FILE"
102
- chmod 644 "$PROJECTS_FILE"
103
- local count
104
- count=$(echo "$merged" | jq 'length')
105
- echo "$LOG_PREFIX Updated projects.json ($count projects)"
106
- else
107
- rm -f "$tmp_file"
108
- fi
76
+ # Build the new auto-detected projects array (two-pass: depth 1 + depth 2)
77
+ local new_projects="[]"
78
+
79
+ for dir in "$WORKSPACE_ROOT"/*/; do
80
+ [ -d "$dir" ] || continue
81
+ local name
82
+ name=$(basename "$dir")
83
+
84
+ is_excluded "$name" && continue
85
+
86
+ if has_project_markers "$dir"; then
87
+ # Depth 1: directory has project markers — register it directly
88
+ new_projects=$(register_project "$new_projects" "$name" "$dir")
89
+ else
90
+ # Depth 2: no markers — treat as container dir, scan its children
91
+ for subdir in "${dir%/}"/*/; do
92
+ [ -d "$subdir" ] || continue
93
+ local subname
94
+ subname=$(basename "$subdir")
95
+ is_excluded "$subname" && continue
96
+ new_projects=$(register_project "$new_projects" "$subname" "$subdir")
97
+ done
98
+ fi
99
+ done
100
+
101
+ # Read existing projects.json (or empty array if missing/invalid)
102
+ local existing="[]"
103
+ if [ -f "$PROJECTS_FILE" ] && jq empty "$PROJECTS_FILE" 2>/dev/null; then
104
+ existing=$(cat "$PROJECTS_FILE")
105
+ fi
106
+
107
+ # Separate user entries (no "auto" tag) from auto entries
108
+ local user_entries
109
+ user_entries=$(echo "$existing" | jq '[.[] | select(.tags | index("auto") | not)]')
110
+
111
+ # Merge: user entries + new auto-detected entries
112
+ local merged
113
+ merged=$(jq -n --argjson user "$user_entries" --argjson auto "$new_projects" '$user + $auto')
114
+
115
+ # Only write if content changed (avoid unnecessary file writes that trigger Project Manager reloads)
116
+ local current_hash new_hash
117
+ current_hash=""
118
+ [ -f "$PROJECTS_FILE" ] && current_hash=$(md5sum "$PROJECTS_FILE" 2>/dev/null | cut -d' ' -f1)
119
+
120
+ local tmp_file
121
+ tmp_file=$(mktemp)
122
+ echo "$merged" | jq '.' >"$tmp_file"
123
+ new_hash=$(md5sum "$tmp_file" | cut -d' ' -f1)
124
+
125
+ if [ "$current_hash" != "$new_hash" ]; then
126
+ mv "$tmp_file" "$PROJECTS_FILE"
127
+ chmod 644 "$PROJECTS_FILE"
128
+ local count
129
+ count=$(echo "$merged" | jq 'length')
130
+ echo "$LOG_PREFIX Updated projects.json ($count projects)"
131
+ else
132
+ rm -f "$tmp_file"
133
+ fi
134
+ }
135
+
136
+ stop_watcher() {
137
+ if [ -f "$PID_FILE" ]; then
138
+ local old_pid
139
+ old_pid=$(cat "$PID_FILE" 2>/dev/null)
140
+ if [ -n "$old_pid" ]; then
141
+ # Kill the process group to stop both the subshell and inotifywait pipeline
142
+ kill -- -"$old_pid" 2>/dev/null || kill "$old_pid" 2>/dev/null || true
143
+ fi
144
+ rm -f "$PID_FILE"
145
+ fi
109
146
  }
110
147
 
111
148
  start_watcher() {
112
- # Guard: don't start if already running
113
- if [ -f "$PID_FILE" ]; then
114
- local old_pid
115
- old_pid=$(cat "$PID_FILE" 2>/dev/null)
116
- if [ -n "$old_pid" ] && kill -0 "$old_pid" 2>/dev/null; then
117
- echo "$LOG_PREFIX Watcher already running (PID $old_pid), skipping"
118
- return 0
119
- fi
120
- # Stale PID file — clean up
121
- rm -f "$PID_FILE"
122
- fi
123
-
124
- # Check if inotifywait is available
125
- if ! command -v inotifywait &>/dev/null; then
126
- echo "$LOG_PREFIX Installing inotify-tools..."
127
- if command -v sudo &>/dev/null; then
128
- sudo apt-get update -qq && sudo apt-get install -y -qq inotify-tools >/dev/null 2>&1
129
- else
130
- apt-get update -qq && apt-get install -y -qq inotify-tools >/dev/null 2>&1
131
- fi
132
-
133
- if ! command -v inotifywait &>/dev/null; then
134
- echo "$LOG_PREFIX WARNING: Could not install inotify-tools, watcher disabled"
135
- return 1
136
- fi
137
- echo "$LOG_PREFIX inotify-tools installed"
138
- fi
139
-
140
- # Fork background watcher
141
- (
142
- # Watch for directory create/delete/move events in /workspaces/
143
- # --quiet suppresses header, --monitor keeps watching indefinitely
144
- inotifywait -m -q -e create,delete,moved_to,moved_from \
145
- --format '%f %e' "$WORKSPACE_ROOT" 2>/dev/null |
146
- while read -r name event; do
147
- # Small delay to let filesystem settle (e.g., move operations)
148
- sleep 1
149
- scan_and_update
150
- done
151
-
152
- # Cleanup on exit
153
- rm -f "$PID_FILE"
154
- ) &
155
- local watcher_pid=$!
156
- echo "$watcher_pid" > "$PID_FILE"
157
- disown
158
-
159
- echo "$LOG_PREFIX Background watcher started (PID $watcher_pid)"
149
+ # Guard: don't start if already running
150
+ if [ -f "$PID_FILE" ]; then
151
+ local old_pid
152
+ old_pid=$(cat "$PID_FILE" 2>/dev/null)
153
+ if [ -n "$old_pid" ] && kill -0 "$old_pid" 2>/dev/null; then
154
+ echo "$LOG_PREFIX Watcher already running (PID $old_pid), skipping"
155
+ return 0
156
+ fi
157
+ # Stale PID file — clean up
158
+ stop_watcher
159
+ fi
160
+
161
+ # Check if inotifywait is available
162
+ if ! command -v inotifywait &>/dev/null; then
163
+ echo "$LOG_PREFIX Installing inotify-tools..."
164
+ if command -v sudo &>/dev/null; then
165
+ sudo apt-get update -qq && sudo apt-get install -y -qq inotify-tools >/dev/null 2>&1
166
+ else
167
+ apt-get update -qq && apt-get install -y -qq inotify-tools >/dev/null 2>&1
168
+ fi
169
+
170
+ if ! command -v inotifywait &>/dev/null; then
171
+ echo "$LOG_PREFIX WARNING: Could not install inotify-tools, watcher disabled"
172
+ return 1
173
+ fi
174
+ echo "$LOG_PREFIX inotify-tools installed"
175
+ fi
176
+
177
+ # Fork background watcher in its own process group for clean shutdown
178
+ set -m
179
+ (
180
+ # Watch for directory create/delete/move events recursively under /workspaces/
181
+ # -r watches subdirectories (catches events inside container dirs like projects/)
182
+ # --exclude filters noisy dirs that generate frequent irrelevant events
183
+ inotifywait -m -r -q -e create,delete,moved_to,moved_from \
184
+ --exclude '(node_modules|\.git/|\.tmp|__pycache__|\.venv)' \
185
+ --format '%w%f %e' "$WORKSPACE_ROOT" 2>/dev/null |
186
+ while read -r _path event; do
187
+ # Small delay to let filesystem settle (e.g., move operations)
188
+ sleep 1
189
+ scan_and_update
190
+ done
191
+
192
+ # Cleanup on exit
193
+ rm -f "$PID_FILE"
194
+ ) &
195
+ local watcher_pid=$!
196
+ set +m
197
+ echo "$watcher_pid" >"$PID_FILE"
198
+ disown
199
+
200
+ echo "$LOG_PREFIX Background watcher started (PID $watcher_pid)"
160
201
  }
161
202
 
162
203
  # --- Main ---
@@ -0,0 +1,48 @@
1
+ #!/bin/bash
2
+ # Configure VS Code Shift+Enter → newline for Claude Code terminal input
3
+ # Writes to ~/.config/Code/User/keybindings.json (same path /terminal-setup uses)
4
+
5
+ echo "[setup-terminal] Configuring Shift+Enter keybinding for Claude Code..."
6
+
7
+ KEYBINDINGS_DIR="$HOME/.config/Code/User"
8
+ KEYBINDINGS_FILE="$KEYBINDINGS_DIR/keybindings.json"
9
+
10
+ # === Create directory if needed ===
11
+ mkdir -p "$KEYBINDINGS_DIR"
12
+
13
+ # === Check if already configured ===
14
+ if [ -f "$KEYBINDINGS_FILE" ] && grep -q "workbench.action.terminal.sendSequence" "$KEYBINDINGS_FILE" 2>/dev/null; then
15
+ echo "[setup-terminal] Shift+Enter binding already present, skipping"
16
+ exit 0
17
+ fi
18
+
19
+ # === Merge or create keybindings ===
20
+ BINDING='{"key":"shift+enter","command":"workbench.action.terminal.sendSequence","args":{"text":"\\u001b\\r"},"when":"terminalFocus"}'
21
+
22
+ if [ -f "$KEYBINDINGS_FILE" ] && command -v jq &>/dev/null; then
23
+ # Merge into existing keybindings
24
+ if jq empty "$KEYBINDINGS_FILE" 2>/dev/null; then
25
+ jq ". + [$BINDING]" "$KEYBINDINGS_FILE" >"$KEYBINDINGS_FILE.tmp" &&
26
+ mv "$KEYBINDINGS_FILE.tmp" "$KEYBINDINGS_FILE"
27
+ echo "[setup-terminal] Merged binding into existing keybindings"
28
+ else
29
+ # Invalid JSON — overwrite
30
+ echo "[$BINDING]" | jq '.' >"$KEYBINDINGS_FILE"
31
+ echo "[setup-terminal] Replaced invalid keybindings file"
32
+ fi
33
+ else
34
+ # No existing file — write fresh
35
+ cat >"$KEYBINDINGS_FILE" <<'EOF'
36
+ [
37
+ {
38
+ "key": "shift+enter",
39
+ "command": "workbench.action.terminal.sendSequence",
40
+ "args": {
41
+ "text": "\u001b\r"
42
+ },
43
+ "when": "terminalFocus"
44
+ }
45
+ ]
46
+ EOF
47
+ echo "[setup-terminal] Created keybindings file at $KEYBINDINGS_FILE"
48
+ fi
@@ -3,7 +3,15 @@
3
3
  # Runs non-blocking in background by default via setup.sh
4
4
  # All failures are warnings — this script never blocks container startup
5
5
 
6
- echo "[update-claude] Checking for Claude Code updates..."
6
+ # Log to file (simple append — process substitution breaks under disown)
7
+ LOG_FILE="${TMPDIR:-/tmp}/claude-update.log"
8
+
9
+ log() { echo "[update-claude] $(date '+%Y-%m-%d %H:%M:%S') $*" | tee -a "$LOG_FILE"; }
10
+
11
+ log "Checking for Claude Code updates..."
12
+
13
+ # === Clear nesting guard (postStartCommand may inherit from VS Code extension) ===
14
+ unset CLAUDECODE
7
15
 
8
16
  # === TMPDIR ===
9
17
  _TMPDIR="${TMPDIR:-/tmp}"
@@ -11,124 +19,58 @@ _TMPDIR="${TMPDIR:-/tmp}"
11
19
  # === LOCK FILE (prevent concurrent updates) ===
12
20
  LOCK_FILE="${_TMPDIR}/claude-update.lock"
13
21
  if ! mkdir "$LOCK_FILE" 2>/dev/null; then
14
- echo "[update-claude] Another update is already running, skipping"
15
- exit 0
22
+ log "Another update is already running, skipping"
23
+ exit 0
16
24
  fi
17
25
 
18
26
  # === CLEANUP TRAP ===
19
27
  cleanup() {
20
- rm -f "${_TMPDIR}/claude-update" 2>/dev/null || true
21
- rm -f "${_TMPDIR}/claude-update-manifest.json" 2>/dev/null || true
22
- rm -rf "$LOCK_FILE" 2>/dev/null || true
28
+ rm -f "${_TMPDIR}/claude-update" 2>/dev/null || true
29
+ rm -f "${_TMPDIR}/claude-update-manifest.json" 2>/dev/null || true
30
+ rm -rf "$LOCK_FILE" 2>/dev/null || true
23
31
  }
24
32
  trap cleanup EXIT
25
33
 
26
34
  # === VERIFY CLAUDE IS INSTALLED ===
27
35
  if ! command -v claude &>/dev/null; then
28
- echo "[update-claude] Claude Code not found, skipping update"
29
- exit 0
36
+ log "Claude Code not found, skipping update"
37
+ exit 0
30
38
  fi
31
39
 
32
- # === DETECT INSTALL METHOD ===
33
- CLAUDE_PATH=$(command -v claude)
34
- if [[ "$CLAUDE_PATH" != "/usr/local/bin/claude" ]]; then
35
- echo "[update-claude] Non-native install detected ($CLAUDE_PATH), skipping"
36
- exit 0
37
- fi
38
-
39
- # === VALIDATE DEPENDENCIES ===
40
- for dep in curl jq sha256sum sudo; do
41
- if ! command -v "$dep" &>/dev/null; then
42
- echo "[update-claude] WARNING: $dep not available, skipping update"
43
- exit 0
44
- fi
45
- done
46
-
47
- # === GET CURRENT VERSION ===
48
- CURRENT_VERSION=$(claude --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "unknown")
49
- echo "[update-claude] Current version: ${CURRENT_VERSION}"
50
-
51
- # === FETCH LATEST VERSION ===
52
- BASE_URL="https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases"
53
-
54
- LATEST_VERSION=$(curl -fsSL "${BASE_URL}/stable" 2>/dev/null)
55
- if [ -z "$LATEST_VERSION" ]; then
56
- echo "[update-claude] WARNING: Failed to fetch latest version, skipping"
57
- exit 0
40
+ # === ENSURE NATIVE BINARY EXISTS ===
41
+ # 'claude install' puts the binary at ~/.local/bin/claude (symlink to ~/.local/share/claude/versions/*)
42
+ # Legacy manual installs used /usr/local/bin/claude check both, prefer ~/.local
43
+ if [ -x "$HOME/.local/bin/claude" ]; then
44
+ NATIVE_BIN="$HOME/.local/bin/claude"
45
+ elif [ -x "/usr/local/bin/claude" ]; then
46
+ NATIVE_BIN="/usr/local/bin/claude"
47
+ else
48
+ NATIVE_BIN=""
58
49
  fi
59
-
60
- echo "[update-claude] Latest version: ${LATEST_VERSION}"
61
-
62
- # === COMPARE VERSIONS ===
63
- if [ "$CURRENT_VERSION" = "$LATEST_VERSION" ]; then
64
- echo "[update-claude] Already up to date (${CURRENT_VERSION})"
65
- exit 0
50
+ if [ -z "$NATIVE_BIN" ]; then
51
+ log "Native binary not found, installing..."
52
+ if claude install 2>&1 | tee -a "$LOG_FILE"; then
53
+ log "Native binary installed successfully"
54
+ else
55
+ log "WARNING: 'claude install' failed, skipping"
56
+ exit 0
57
+ fi
58
+ # Skip update check on first install — next start will handle it
59
+ exit 0
66
60
  fi
67
61
 
68
- echo "[update-claude] Updating from ${CURRENT_VERSION} to ${LATEST_VERSION}..."
69
-
70
- # === DETECT PLATFORM ===
71
- ARCH=$(uname -m)
72
- case "${ARCH}" in
73
- x86_64)
74
- PLATFORM="linux-x64"
75
- ;;
76
- aarch64|arm64)
77
- PLATFORM="linux-arm64"
78
- ;;
79
- *)
80
- echo "[update-claude] WARNING: Unsupported architecture: ${ARCH}, skipping"
81
- exit 0
82
- ;;
83
- esac
84
-
85
- # Detect musl (Alpine Linux)
86
- if ldd --version 2>&1 | grep -qi musl; then
87
- PLATFORM="${PLATFORM}-musl"
62
+ # === CHECK FOR UPDATES ===
63
+ CURRENT_VERSION=$("$NATIVE_BIN" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "unknown")
64
+ log "Current version: ${CURRENT_VERSION}"
65
+
66
+ # Use the official update command (handles download, verification, and versioned install)
67
+ if "$NATIVE_BIN" update 2>&1 | tee -a "$LOG_FILE"; then
68
+ UPDATED_VERSION=$("$NATIVE_BIN" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "unknown")
69
+ if [ "$CURRENT_VERSION" != "$UPDATED_VERSION" ]; then
70
+ log "Updated Claude Code: ${CURRENT_VERSION} → ${UPDATED_VERSION}"
71
+ else
72
+ log "Already up to date (${CURRENT_VERSION})"
73
+ fi
74
+ else
75
+ log "WARNING: 'claude update' failed, skipping"
88
76
  fi
89
-
90
- echo "[update-claude] Platform: ${PLATFORM}"
91
-
92
- # === DOWNLOAD MANIFEST ===
93
- MANIFEST_URL="${BASE_URL}/${LATEST_VERSION}/manifest.json"
94
-
95
- if ! curl -fsSL "${MANIFEST_URL}" -o ${_TMPDIR}/claude-update-manifest.json 2>/dev/null; then
96
- echo "[update-claude] WARNING: Failed to download manifest, skipping"
97
- exit 0
98
- fi
99
-
100
- # === EXTRACT AND VERIFY CHECKSUM ===
101
- EXPECTED_CHECKSUM=$(jq -r ".platforms.\"${PLATFORM}\".checksum" ${_TMPDIR}/claude-update-manifest.json)
102
- if [ -z "${EXPECTED_CHECKSUM}" ] || [ "${EXPECTED_CHECKSUM}" = "null" ]; then
103
- echo "[update-claude] WARNING: Platform ${PLATFORM} not found in manifest, skipping"
104
- exit 0
105
- fi
106
-
107
- # === DOWNLOAD BINARY ===
108
- BINARY_URL="${BASE_URL}/${LATEST_VERSION}/${PLATFORM}/claude"
109
-
110
- if ! curl -fsSL "${BINARY_URL}" -o ${_TMPDIR}/claude-update 2>/dev/null; then
111
- echo "[update-claude] WARNING: Failed to download binary, skipping"
112
- exit 0
113
- fi
114
-
115
- # === VERIFY CHECKSUM ===
116
- ACTUAL_CHECKSUM=$(sha256sum ${_TMPDIR}/claude-update | cut -d' ' -f1)
117
-
118
- if [ "${ACTUAL_CHECKSUM}" != "${EXPECTED_CHECKSUM}" ]; then
119
- echo "[update-claude] WARNING: Checksum verification failed, skipping"
120
- echo "[update-claude] Expected: ${EXPECTED_CHECKSUM}"
121
- echo "[update-claude] Actual: ${ACTUAL_CHECKSUM}"
122
- exit 0
123
- fi
124
-
125
- # === INSTALL (atomic replace) ===
126
- chmod +x ${_TMPDIR}/claude-update
127
- if ! sudo mv ${_TMPDIR}/claude-update /usr/local/bin/claude; then
128
- echo "[update-claude] WARNING: Failed to install update (sudo mv failed), skipping"
129
- exit 0
130
- fi
131
-
132
- # === VERIFY UPDATE ===
133
- INSTALLED_VERSION=$(claude --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "unknown")
134
- echo "[update-claude] Updated Claude Code: ${CURRENT_VERSION} → ${INSTALLED_VERSION}"
@@ -21,8 +21,9 @@ fi
21
21
  : "${SETUP_PLUGINS:=true}"
22
22
  : "${SETUP_UPDATE_CLAUDE:=true}"
23
23
  : "${SETUP_PROJECTS:=true}"
24
+ : "${SETUP_TERMINAL:=true}"
24
25
 
25
- export CLAUDE_CONFIG_DIR CONFIG_SOURCE_DIR SETUP_CONFIG SETUP_ALIASES SETUP_AUTH SETUP_PLUGINS SETUP_UPDATE_CLAUDE SETUP_PROJECTS
26
+ export CLAUDE_CONFIG_DIR CONFIG_SOURCE_DIR SETUP_CONFIG SETUP_ALIASES SETUP_AUTH SETUP_PLUGINS SETUP_UPDATE_CLAUDE SETUP_PROJECTS SETUP_TERMINAL
26
27
 
27
28
  SETUP_START=$(date +%s)
28
29
  SETUP_RESULTS=()
@@ -64,22 +65,8 @@ run_script "$SCRIPT_DIR/setup-config.sh" "$SETUP_CONFIG"
64
65
  run_script "$SCRIPT_DIR/setup-aliases.sh" "$SETUP_ALIASES"
65
66
  run_script "$SCRIPT_DIR/setup-plugins.sh" "$SETUP_PLUGINS"
66
67
  run_script "$SCRIPT_DIR/setup-projects.sh" "$SETUP_PROJECTS"
67
-
68
- # Non-blocking: check for Claude Code updates in background
69
- if [ "$SETUP_UPDATE_CLAUDE" = "true" ]; then
70
- if [ -f "$SCRIPT_DIR/setup-update-claude.sh" ]; then
71
- echo " Claude Code update checking in background..."
72
- echo " (If 'claude' fails, wait a moment and retry)"
73
- bash "$SCRIPT_DIR/setup-update-claude.sh" &
74
- disown
75
- SETUP_RESULTS+=("setup-update-claude:ok")
76
- else
77
- SETUP_RESULTS+=("setup-update-claude:missing")
78
- fi
79
- else
80
- echo " setup-update-claude... skipped (disabled)"
81
- SETUP_RESULTS+=("setup-update-claude:disabled")
82
- fi
68
+ run_script "$SCRIPT_DIR/setup-terminal.sh" "$SETUP_TERMINAL"
69
+ run_script "$SCRIPT_DIR/setup-update-claude.sh" "$SETUP_UPDATE_CLAUDE"
83
70
 
84
71
  echo ""
85
72
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
package/README.md CHANGED
@@ -65,9 +65,9 @@ tree-sitter (JS/TS/Python), ast-grep, Pyright, TypeScript LSP
65
65
 
66
66
  tmux, agent-browser, claude-monitor, ccusage, ccburn, ccstatusline, ast-grep, tree-sitter, lsp-servers, biome, notify-hook, mcp-qdrant, mcp-reasoner, splitrail
67
67
 
68
- ### Agents (17) & Skills (16)
68
+ ### Agents (17) & Skills (17)
69
69
 
70
- The `code-directive` plugin includes specialized agents (architect, explorer, test-writer, security-auditor, etc.) and domain-specific coding reference skills (fastapi, svelte5, docker, testing, etc.).
70
+ The `code-directive` plugin includes 17 specialized agents (architect, explorer, test-writer, security-auditor, etc.) and 17 domain-specific coding reference skills (fastapi, svelte5, docker, testing, spec-refine, etc.).
71
71
 
72
72
  ## Quick Start
73
73
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeforge-dev",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "Complete development container that sets up Claude Code with modular devcontainer features, modern dev tools, and persistent configurations. Drop it into any project and get a production-ready AI development environment in minutes.",
5
5
  "main": "setup.js",
6
6
  "bin": {