codeforge-dev 1.8.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.
- package/.devcontainer/.env +3 -0
- package/.devcontainer/CHANGELOG.md +107 -0
- package/.devcontainer/CLAUDE.md +30 -9
- package/.devcontainer/README.md +61 -2
- package/.devcontainer/config/defaults/main-system-prompt.md +162 -128
- package/.devcontainer/config/defaults/rules/spec-workflow.md +75 -0
- package/.devcontainer/config/defaults/rules/workspace-scope.md +7 -0
- package/.devcontainer/config/defaults/settings.json +63 -66
- package/.devcontainer/config/file-manifest.json +30 -18
- package/.devcontainer/connect-external-terminal.sh +17 -17
- package/.devcontainer/devcontainer.json +143 -144
- package/.devcontainer/plugins/devs-marketplace/.claude-plugin/marketplace.json +104 -97
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-code-quality/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-code-quality/README.md +158 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-code-quality/hooks/hooks.json +39 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-code-quality/scripts/collect-edited-files.py +47 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-code-quality/scripts/format-on-stop.py +297 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-code-quality/scripts/lint-file.py +536 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-code-quality/scripts/syntax-validator.py +146 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/architect.md +81 -4
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/debug-logs.md +18 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/dependency-analyst.md +18 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/doc-writer.md +89 -4
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/explorer.md +18 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/generalist.md +142 -8
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/git-archaeologist.md +18 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/migrator.md +108 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/perf-profiler.md +24 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/refactorer.md +97 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/researcher.md +33 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/security-auditor.md +24 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/spec-writer.md +50 -12
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/test-writer.md +96 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/hooks/hooks.json +100 -95
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/advisory-test-runner.py +186 -13
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/spec-reminder.py +122 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/documentation-patterns/SKILL.md +1 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-check/SKILL.md +98 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-init/SKILL.md +99 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-init/references/backlog-template.md +23 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-init/references/roadmap-template.md +33 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-new/SKILL.md +110 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-new/references/template.md +129 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-refine/SKILL.md +194 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-update/SKILL.md +142 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/specification-writing/SKILL.md +19 -12
- package/.devcontainer/scripts/check-setup.sh +24 -25
- package/.devcontainer/scripts/setup-aliases.sh +88 -76
- package/.devcontainer/scripts/setup-config.sh +86 -83
- package/.devcontainer/scripts/setup-projects.sh +172 -131
- package/.devcontainer/scripts/setup-terminal.sh +48 -0
- package/.devcontainer/scripts/setup-update-claude.sh +49 -107
- package/.devcontainer/scripts/setup.sh +4 -17
- package/README.md +2 -2
- package/package.json +42 -42
|
@@ -4,117 +4,120 @@
|
|
|
4
4
|
CONFIG_DIR="${CONFIG_SOURCE_DIR:?CONFIG_SOURCE_DIR not set}"
|
|
5
5
|
MANIFEST="$CONFIG_DIR/file-manifest.json"
|
|
6
6
|
|
|
7
|
-
log()
|
|
7
|
+
log() { echo "[setup-config] $*"; }
|
|
8
8
|
warn() { echo "[setup-config] WARNING: $*"; }
|
|
9
|
-
err()
|
|
9
|
+
err() { echo "[setup-config] ERROR: $*" >&2; }
|
|
10
10
|
|
|
11
11
|
# Deprecation notice if legacy OVERWRITE_CONFIG is still set
|
|
12
12
|
if [ -n "${OVERWRITE_CONFIG+x}" ]; then
|
|
13
|
-
|
|
13
|
+
warn "OVERWRITE_CONFIG is deprecated. Use per-file 'overwrite' in config/file-manifest.json instead."
|
|
14
14
|
fi
|
|
15
15
|
|
|
16
16
|
# ── Legacy fallback ──────────────────────────────────────────────
|
|
17
17
|
legacy_copy() {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
18
|
+
local target_dir="${CLAUDE_CONFIG_DIR:?CLAUDE_CONFIG_DIR not set}"
|
|
19
|
+
warn "file-manifest.json not found, falling back to legacy copy"
|
|
20
|
+
mkdir -p "$target_dir"
|
|
21
|
+
for file in defaults/settings.json defaults/keybindings.json defaults/main-system-prompt.md; do
|
|
22
|
+
if [ -f "$CONFIG_DIR/$file" ]; then
|
|
23
|
+
local basename="${file##*/}"
|
|
24
|
+
cp "$CONFIG_DIR/$file" "$target_dir/$basename"
|
|
25
|
+
chown "$(id -un):$(id -gn)" "$target_dir/$basename" 2>/dev/null || true
|
|
26
|
+
log "Copied $basename (legacy)"
|
|
27
|
+
fi
|
|
28
|
+
done
|
|
29
|
+
log "Configuration complete (legacy)"
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
if [ ! -f "$MANIFEST" ]; then
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
legacy_copy
|
|
34
|
+
exit 0
|
|
35
35
|
fi
|
|
36
36
|
|
|
37
37
|
# ── Validate manifest JSON ──────────────────────────────────────
|
|
38
38
|
if ! jq empty "$MANIFEST" 2>/dev/null; then
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
err "Invalid JSON in file-manifest.json"
|
|
40
|
+
exit 1
|
|
41
41
|
fi
|
|
42
42
|
|
|
43
43
|
# ── Variable expansion ───────────────────────────────────────────
|
|
44
44
|
expand_vars() {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
local val="$1"
|
|
46
|
+
val="${val//\$\{CLAUDE_CONFIG_DIR\}/$CLAUDE_CONFIG_DIR}"
|
|
47
|
+
val="${val//\$\{WORKSPACE_ROOT\}/$WORKSPACE_ROOT}"
|
|
48
|
+
# Warn on any remaining unresolved ${...} tokens
|
|
49
|
+
if [[ "$val" =~ \$\{[^}]+\} ]]; then
|
|
50
|
+
warn "Unresolved variable in: $val"
|
|
51
|
+
fi
|
|
52
|
+
echo "$val"
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
# ── Change detection ─────────────────────────────────────────────
|
|
56
56
|
should_copy() {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
local src="$1" dest="$2"
|
|
58
|
+
[ ! -f "$dest" ] && return 0
|
|
59
|
+
local src_hash dest_hash
|
|
60
|
+
src_hash=$(sha256sum "$src" | cut -d' ' -f1)
|
|
61
|
+
dest_hash=$(sha256sum "$dest" | cut -d' ' -f1)
|
|
62
|
+
[ "$src_hash" != "$dest_hash" ]
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
# ── Process manifest ─────────────────────────────────────────────
|
|
66
66
|
log "Copying configuration files..."
|
|
67
67
|
|
|
68
68
|
# Single jq invocation to extract all fields (reduces N×5 subprocess calls to 1)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
69
|
+
# Note: empty destFilename uses "__NONE__" sentinel because bash read collapses
|
|
70
|
+
# consecutive tab delimiters, which shifts fields when destFilename is empty.
|
|
71
|
+
jq -r '.[] | [.src, .dest, (.destFilename // "__NONE__"), (.enabled // true | tostring), (.overwrite // "if-changed")] | @tsv' "$MANIFEST" |
|
|
72
|
+
while IFS=$'\t' read -r src dest dest_filename enabled overwrite; do
|
|
73
|
+
# Skip disabled entries
|
|
74
|
+
if [ "$enabled" = "false" ]; then
|
|
75
|
+
log "Skipping $src (disabled)"
|
|
76
|
+
continue
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# Resolve paths
|
|
80
|
+
src_path="$CONFIG_DIR/$src"
|
|
81
|
+
dest_dir=$(expand_vars "$dest")
|
|
82
|
+
[ "$dest_filename" = "__NONE__" ] && dest_filename=""
|
|
83
|
+
filename="${dest_filename:-${src##*/}}"
|
|
84
|
+
dest_path="$dest_dir/$filename"
|
|
85
|
+
|
|
86
|
+
# Validate source exists
|
|
87
|
+
if [ ! -f "$src_path" ]; then
|
|
88
|
+
warn "$src not found in config dir, skipping"
|
|
89
|
+
continue
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
# Ensure destination directory exists
|
|
93
|
+
mkdir -p "$dest_dir"
|
|
94
|
+
|
|
95
|
+
# Apply overwrite strategy
|
|
96
|
+
case "$overwrite" in
|
|
97
|
+
always)
|
|
98
|
+
cp "$src_path" "$dest_path"
|
|
99
|
+
log "Copied $src → $dest_path (always)"
|
|
100
|
+
;;
|
|
101
|
+
never)
|
|
102
|
+
if [ ! -f "$dest_path" ]; then
|
|
103
|
+
cp "$src_path" "$dest_path"
|
|
104
|
+
log "Copied $src → $dest_path (new)"
|
|
105
|
+
else
|
|
106
|
+
log "Skipping $src (exists, overwrite=never)"
|
|
107
|
+
fi
|
|
108
|
+
;;
|
|
109
|
+
if-changed | *)
|
|
110
|
+
if should_copy "$src_path" "$dest_path"; then
|
|
111
|
+
cp "$src_path" "$dest_path"
|
|
112
|
+
log "Copied $src → $dest_path (changed)"
|
|
113
|
+
else
|
|
114
|
+
log "Skipping $src (unchanged)"
|
|
115
|
+
fi
|
|
116
|
+
;;
|
|
117
|
+
esac
|
|
118
|
+
|
|
119
|
+
# Fix ownership
|
|
120
|
+
chown "$(id -un):$(id -gn)" "$dest_path" 2>/dev/null || true
|
|
121
|
+
done
|
|
119
122
|
|
|
120
123
|
log "Configuration complete"
|
|
@@ -17,146 +17,187 @@ EXCLUDED_DIRS=".claude .gh .tmp .devcontainer .config node_modules .git"
|
|
|
17
17
|
# --- Helpers ---
|
|
18
18
|
|
|
19
19
|
is_excluded() {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|