codeforge-dev 1.9.0 → 1.11.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 +125 -0
- package/.devcontainer/CLAUDE.md +41 -11
- package/.devcontainer/README.md +73 -3
- package/.devcontainer/config/defaults/main-system-prompt.md +187 -201
- package/.devcontainer/config/defaults/rules/session-search.md +66 -0
- package/.devcontainer/config/defaults/rules/spec-workflow.md +48 -13
- package/.devcontainer/config/defaults/settings.json +2 -1
- package/.devcontainer/config/defaults/writing-system-prompt.md +143 -0
- package/.devcontainer/config/file-manifest.json +12 -0
- package/.devcontainer/connect-external-terminal.sh +17 -17
- package/.devcontainer/devcontainer.json +150 -144
- package/.devcontainer/features/ccms/README.md +50 -0
- package/.devcontainer/features/ccms/devcontainer-feature.json +21 -0
- package/.devcontainer/features/ccms/install.sh +105 -0
- package/.devcontainer/features/ccstatusline/install.sh +24 -2
- package/.devcontainer/plugins/devs-marketplace/.claude-plugin/marketplace.json +8 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/architect.md +5 -3
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/claude-guide.md +1 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/doc-writer.md +7 -7
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/generalist.md +1 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/spec-writer.md +22 -12
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/hooks/hooks.json +11 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/skill-suggester.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/advisory-test-runner.py +186 -13
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/git-state-injector.py +15 -4
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/inject-cwd.py +37 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/skill-suggester.py +24 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/spec-reminder.py +4 -2
- 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-build/SKILL.md +353 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-build/references/review-checklist.md +175 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-check/SKILL.md +28 -15
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-init/SKILL.md +16 -13
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-init/references/backlog-template.md +19 -3
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-init/references/milestones-template.md +32 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-new/SKILL.md +28 -20
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-new/references/template.md +35 -6
- 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-review/SKILL.md +229 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-update/SKILL.md +24 -2
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/specification-writing/SKILL.md +20 -13
- package/.devcontainer/plugins/devs-marketplace/plugins/codeforge-lsp/.claude-plugin/plugin.json +38 -5
- package/.devcontainer/plugins/devs-marketplace/plugins/workspace-scope-guard/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workspace-scope-guard/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workspace-scope-guard/scripts/__pycache__/guard-workspace-scope.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workspace-scope-guard/scripts/guard-workspace-scope.py +132 -0
- package/.devcontainer/scripts/check-setup.sh +24 -25
- package/.devcontainer/scripts/setup-aliases.sh +95 -90
- 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 +1 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-init/references/roadmap-template.md +0 -13
|
@@ -1,117 +1,122 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Setup cc/claude/ccraw aliases for claude with local system prompt support
|
|
3
|
+
#
|
|
4
|
+
# Idempotent: removes the entire managed block then re-writes it fresh.
|
|
5
|
+
# Safe to run on every container start via postStartCommand.
|
|
3
6
|
|
|
4
7
|
CLAUDE_DIR="${CLAUDE_CONFIG_DIR:?CLAUDE_CONFIG_DIR not set}"
|
|
5
8
|
|
|
6
9
|
echo "[setup-aliases] Configuring Claude aliases..."
|
|
7
10
|
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
# Resolve check-setup path once (used inside the block we write)
|
|
12
|
+
DEVCONTAINER_SCRIPTS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
|
|
14
|
+
BLOCK_START='# === CodeForge Claude aliases START (managed by setup-aliases.sh — do not edit) ==='
|
|
15
|
+
BLOCK_END='# === CodeForge Claude aliases END ==='
|
|
12
16
|
|
|
13
17
|
for rc in ~/.bashrc ~/.zshrc; do
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
18
|
+
if [ -f "$rc" ]; then
|
|
19
|
+
# --- 1. Backup before modifying ---
|
|
20
|
+
cp "$rc" "${rc}.bak.$(date +%s)" 2>/dev/null || true
|
|
21
|
+
# Clean old backups (keep last 3)
|
|
22
|
+
ls -t "${rc}.bak."* 2>/dev/null | tail -n +4 | xargs rm -f 2>/dev/null || true
|
|
23
|
+
|
|
24
|
+
# --- 2. Remove existing managed block (if present) ---
|
|
25
|
+
sed -i '/# === CodeForge Claude aliases START/,/# === CodeForge Claude aliases END/d' "$rc"
|
|
26
|
+
|
|
27
|
+
# --- 3. Legacy cleanup (pre-marker formats only) ---
|
|
28
|
+
# These remove remnants from versions that predated the block-marker system.
|
|
29
|
+
# After step 2, anything matching these patterns is orphaned from old formats.
|
|
30
|
+
|
|
31
|
+
# Old function forms (pre-v1.10.0)
|
|
32
|
+
if grep -q "^cc()" "$rc" 2>/dev/null; then
|
|
33
|
+
sed -i '/^cc() {/,/^}/d' "$rc"
|
|
34
|
+
echo "[setup-aliases] Removed legacy cc() function from $(basename "$rc")"
|
|
35
|
+
fi
|
|
36
|
+
if grep -q "^_claude_with_config()" "$rc" 2>/dev/null; then
|
|
37
|
+
sed -i '/^_claude_with_config() {/,/^}/d' "$rc"
|
|
38
|
+
echo "[setup-aliases] Removed legacy _claude_with_config() function from $(basename "$rc")"
|
|
39
|
+
fi
|
|
40
|
+
if grep -q "^claude() { _claude_with_config" "$rc" 2>/dev/null; then
|
|
41
|
+
sed -i '/^claude() { _claude_with_config/d' "$rc"
|
|
42
|
+
echo "[setup-aliases] Removed legacy claude() function from $(basename "$rc")"
|
|
43
|
+
fi
|
|
44
|
+
if grep -q "alias specwright=" "$rc" 2>/dev/null; then
|
|
45
|
+
sed -i '/alias specwright=/d' "$rc"
|
|
46
|
+
echo "[setup-aliases] Removed legacy specwright alias from $(basename "$rc")"
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Old alias/export form (v1.10.0 — no block markers)
|
|
50
|
+
sed -i '/# Claude Code environment and aliases/d' "$rc"
|
|
51
|
+
sed -i '/^export CLAUDE_CONFIG_DIR="/d' "$rc"
|
|
52
|
+
sed -i '/^export LANG=en_US\.UTF-8$/d' "$rc"
|
|
53
|
+
sed -i '/^export LC_ALL=en_US\.UTF-8$/d' "$rc"
|
|
54
|
+
# _CLAUDE_BIN if-block (4 patterns: if, elif, else, fi + assignments)
|
|
55
|
+
sed -i '/^if \[ -x "\$HOME\/\.local\/bin\/claude" \]/,/^fi$/d' "$rc"
|
|
56
|
+
sed -i '/^ _CLAUDE_BIN=/d' "$rc"
|
|
57
|
+
# Standalone aliases from old format
|
|
58
|
+
sed -i "/^alias cc='/d" "$rc"
|
|
59
|
+
sed -i "/^alias claude='/d" "$rc"
|
|
60
|
+
sed -i "/^alias ccraw='/d" "$rc"
|
|
61
|
+
sed -i "/^alias ccw='/d" "$rc"
|
|
62
|
+
sed -i '/^alias check-setup=/d' "$rc"
|
|
63
|
+
# cc-tools function from old format
|
|
64
|
+
if grep -q "^cc-tools()" "$rc" 2>/dev/null; then
|
|
65
|
+
sed -i '/^cc-tools() {/,/^}/d' "$rc"
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# --- 5. Write fresh managed block ---
|
|
69
|
+
cat >>"$rc" <<BLOCK_EOF
|
|
70
|
+
|
|
71
|
+
${BLOCK_START}
|
|
72
|
+
export CLAUDE_CONFIG_DIR="${CLAUDE_CONFIG_DIR}"
|
|
73
|
+
export LANG=en_US.UTF-8
|
|
74
|
+
export LC_ALL=en_US.UTF-8
|
|
75
|
+
|
|
76
|
+
# Prefer native binary over npm-installed version
|
|
77
|
+
if [ -x "\$HOME/.local/bin/claude" ]; then
|
|
78
|
+
_CLAUDE_BIN="\$HOME/.local/bin/claude"
|
|
79
|
+
elif [ -x /usr/local/bin/claude ]; then
|
|
80
|
+
_CLAUDE_BIN=/usr/local/bin/claude
|
|
81
|
+
else
|
|
82
|
+
_CLAUDE_BIN=claude
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
alias cc='CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 command "\$_CLAUDE_BIN" --system-prompt-file "\$CLAUDE_CONFIG_DIR/main-system-prompt.md" --permission-mode plan --allow-dangerously-skip-permissions'
|
|
86
|
+
alias claude='CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 command "\$_CLAUDE_BIN" --system-prompt-file "\$CLAUDE_CONFIG_DIR/main-system-prompt.md" --permission-mode plan --allow-dangerously-skip-permissions'
|
|
87
|
+
alias ccraw='command "\$_CLAUDE_BIN"'
|
|
88
|
+
alias ccw='CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 command "\$_CLAUDE_BIN" --system-prompt-file "\$CLAUDE_CONFIG_DIR/writing-system-prompt.md" --permission-mode plan --allow-dangerously-skip-permissions'
|
|
89
|
+
|
|
85
90
|
cc-tools() {
|
|
86
91
|
echo "CodeForge Available Tools"
|
|
87
92
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
88
93
|
printf " %-20s %s\n" "COMMAND" "STATUS"
|
|
89
94
|
echo " ────────────────────────────────────"
|
|
90
|
-
for cmd in claude cc ccraw ccusage ccburn claude-monitor
|
|
91
|
-
ruff biome dprint shfmt shellcheck hadolint
|
|
92
|
-
ast-grep tree-sitter pyright typescript-language-server
|
|
95
|
+
for cmd in claude cc ccw ccraw ccusage ccburn claude-monitor \\
|
|
96
|
+
ccms cargo ruff biome dprint shfmt shellcheck hadolint \\
|
|
97
|
+
ast-grep tree-sitter pyright typescript-language-server \\
|
|
93
98
|
agent-browser gh docker git jq tmux bun go; do
|
|
94
|
-
if command -v "
|
|
95
|
-
ver
|
|
96
|
-
printf " %-20s ✓ %s\n" "
|
|
99
|
+
if command -v "\$cmd" >/dev/null 2>&1; then
|
|
100
|
+
ver=\$("\$cmd" --version 2>/dev/null | head -1 || echo "installed")
|
|
101
|
+
printf " %-20s ✓ %s\n" "\$cmd" "\$ver"
|
|
97
102
|
else
|
|
98
|
-
printf " %-20s ✗ not found\n" "
|
|
103
|
+
printf " %-20s ✗ not found\n" "\$cmd"
|
|
99
104
|
fi
|
|
100
105
|
done
|
|
101
106
|
}
|
|
102
|
-
CCTOOLS_EOF
|
|
103
107
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
alias check-setup='bash ${DEVCONTAINER_SCRIPTS}/check-setup.sh'
|
|
109
|
+
${BLOCK_END}
|
|
110
|
+
BLOCK_EOF
|
|
107
111
|
|
|
108
|
-
|
|
109
|
-
|
|
112
|
+
echo "[setup-aliases] Added aliases to $(basename "$rc")"
|
|
113
|
+
fi
|
|
110
114
|
done
|
|
111
115
|
|
|
112
116
|
echo "[setup-aliases] Aliases configured:"
|
|
113
|
-
echo " cc -> claude with \$CLAUDE_CONFIG_DIR/system-prompt.md"
|
|
114
|
-
echo " claude -> claude with \$CLAUDE_CONFIG_DIR/system-prompt.md"
|
|
117
|
+
echo " cc -> claude with \$CLAUDE_CONFIG_DIR/main-system-prompt.md"
|
|
118
|
+
echo " claude -> claude with \$CLAUDE_CONFIG_DIR/main-system-prompt.md"
|
|
115
119
|
echo " ccraw -> vanilla claude without any config"
|
|
120
|
+
echo " ccw -> claude with \$CLAUDE_CONFIG_DIR/writing-system-prompt.md"
|
|
116
121
|
echo " cc-tools -> list all available CodeForge tools"
|
|
117
122
|
echo " check-setup -> verify CodeForge setup health"
|
|
@@ -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
|