From 7d3e79570a4d07dd288e0100d742c51c5038b20f Mon Sep 17 00:00:00 2001 From: AnExiledDev Date: Fri, 27 Feb 2026 00:07:58 +0000 Subject: [PATCH 1/6] Create .codeforge/ user customization directory Centralizes all user-facing config files into .codeforge/, separating user customization from .devcontainer/ framework internals. Adds checksum-based modification detection so updates preserve user changes. - Move config files from .devcontainer/config/defaults/ to .codeforge/config/ - Move file-manifest.json to .codeforge/ - Move terminal scripts to .codeforge/scripts/ - Add checksum-based sync (SHA-256) for --force and --reset - Add auto-migration script for existing installations - Add codeforge config apply CLI subcommand - Add codeforge alias to setup-aliases.sh - Update setup.sh with CODEFORGE_DIR env var and deprecation guard - Update setup-config.sh to read from .codeforge/ - Bump version to 2.0.0 - Update all documentation and docs site references --- .codeforge/.codeforge-preserve | 11 + .../config}/ccstatusline-settings.json | 0 .../config}/keybindings.json | 0 .../config}/main-system-prompt.md | 0 .../config}/orchestrator-system-prompt.md | 0 .../config}/rules/session-search.md | 0 .../config}/rules/spec-workflow.md | 0 .../config}/rules/workspace-scope.md | 0 .../config}/settings.json | 0 .../config}/writing-system-prompt.md | 0 .../config => .codeforge}/file-manifest.json | 20 +- .../scripts}/connect-external-terminal.ps1 | 0 .../scripts}/connect-external-terminal.sh | 0 .devcontainer/.env.example | 5 +- .devcontainer/CHANGELOG.md | 25 ++ .devcontainer/CLAUDE.md | 53 +-- .devcontainer/README.md | 14 +- .../features/ccstatusline/install.sh | 6 +- .devcontainer/features/tmux/README.md | 6 +- .../agent-system/agents/claude-guide.md | 8 +- .devcontainer/scripts/setup-aliases.sh | 1 + .devcontainer/scripts/setup-config.sh | 16 +- .devcontainer/scripts/setup-migrate-claude.sh | 11 +- .../scripts/setup-migrate-codeforge.sh | 60 +++ .devcontainer/scripts/setup.sh | 16 +- .gitignore | 9 +- CLAUDE.md | 6 +- docs/astro.config.mjs | 203 ++++++----- .../docs/customization/configuration.md | 16 +- docs/src/content/docs/customization/index.md | 2 +- .../content/docs/customization/keybindings.md | 2 +- docs/src/content/docs/customization/rules.md | 8 +- .../docs/customization/system-prompts.md | 10 +- .../docs/getting-started/installation.md | 15 +- .../content/docs/reference/architecture.md | 27 +- docs/src/content/docs/reference/changelog.md | 182 ++++++++- .../src/content/docs/reference/environment.md | 2 +- docs/src/content/docs/reference/index.md | 14 +- .../content/docs/reference/troubleshooting.md | 6 +- package.json | 3 +- setup.js | 345 +++++++++++++++++- test.js | 72 +++- 42 files changed, 950 insertions(+), 224 deletions(-) create mode 100644 .codeforge/.codeforge-preserve rename {.devcontainer/config/defaults => .codeforge/config}/ccstatusline-settings.json (100%) rename {.devcontainer/config/defaults => .codeforge/config}/keybindings.json (100%) rename {.devcontainer/config/defaults => .codeforge/config}/main-system-prompt.md (100%) rename {.devcontainer/config/defaults => .codeforge/config}/orchestrator-system-prompt.md (100%) rename {.devcontainer/config/defaults => .codeforge/config}/rules/session-search.md (100%) rename {.devcontainer/config/defaults => .codeforge/config}/rules/spec-workflow.md (100%) rename {.devcontainer/config/defaults => .codeforge/config}/rules/workspace-scope.md (100%) rename {.devcontainer/config/defaults => .codeforge/config}/settings.json (100%) rename {.devcontainer/config/defaults => .codeforge/config}/writing-system-prompt.md (100%) rename {.devcontainer/config => .codeforge}/file-manifest.json (69%) rename {.devcontainer => .codeforge/scripts}/connect-external-terminal.ps1 (100%) rename {.devcontainer => .codeforge/scripts}/connect-external-terminal.sh (100%) create mode 100755 .devcontainer/scripts/setup-migrate-codeforge.sh diff --git a/.codeforge/.codeforge-preserve b/.codeforge/.codeforge-preserve new file mode 100644 index 0000000..af1d1c6 --- /dev/null +++ b/.codeforge/.codeforge-preserve @@ -0,0 +1,11 @@ +# .codeforge-preserve +# List additional files to preserve during --force updates. +# One file path per line, relative to .codeforge/. +# Lines starting with # are comments. +# +# Default preserved files (built-in, no need to list here): +# config/settings.json +# config/main-system-prompt.md +# config/keybindings.json +# config/ccstatusline-settings.json +# file-manifest.json diff --git a/.devcontainer/config/defaults/ccstatusline-settings.json b/.codeforge/config/ccstatusline-settings.json similarity index 100% rename from .devcontainer/config/defaults/ccstatusline-settings.json rename to .codeforge/config/ccstatusline-settings.json diff --git a/.devcontainer/config/defaults/keybindings.json b/.codeforge/config/keybindings.json similarity index 100% rename from .devcontainer/config/defaults/keybindings.json rename to .codeforge/config/keybindings.json diff --git a/.devcontainer/config/defaults/main-system-prompt.md b/.codeforge/config/main-system-prompt.md similarity index 100% rename from .devcontainer/config/defaults/main-system-prompt.md rename to .codeforge/config/main-system-prompt.md diff --git a/.devcontainer/config/defaults/orchestrator-system-prompt.md b/.codeforge/config/orchestrator-system-prompt.md similarity index 100% rename from .devcontainer/config/defaults/orchestrator-system-prompt.md rename to .codeforge/config/orchestrator-system-prompt.md diff --git a/.devcontainer/config/defaults/rules/session-search.md b/.codeforge/config/rules/session-search.md similarity index 100% rename from .devcontainer/config/defaults/rules/session-search.md rename to .codeforge/config/rules/session-search.md diff --git a/.devcontainer/config/defaults/rules/spec-workflow.md b/.codeforge/config/rules/spec-workflow.md similarity index 100% rename from .devcontainer/config/defaults/rules/spec-workflow.md rename to .codeforge/config/rules/spec-workflow.md diff --git a/.devcontainer/config/defaults/rules/workspace-scope.md b/.codeforge/config/rules/workspace-scope.md similarity index 100% rename from .devcontainer/config/defaults/rules/workspace-scope.md rename to .codeforge/config/rules/workspace-scope.md diff --git a/.devcontainer/config/defaults/settings.json b/.codeforge/config/settings.json similarity index 100% rename from .devcontainer/config/defaults/settings.json rename to .codeforge/config/settings.json diff --git a/.devcontainer/config/defaults/writing-system-prompt.md b/.codeforge/config/writing-system-prompt.md similarity index 100% rename from .devcontainer/config/defaults/writing-system-prompt.md rename to .codeforge/config/writing-system-prompt.md diff --git a/.devcontainer/config/file-manifest.json b/.codeforge/file-manifest.json similarity index 69% rename from .devcontainer/config/file-manifest.json rename to .codeforge/file-manifest.json index 55950c3..52d26bd 100644 --- a/.devcontainer/config/file-manifest.json +++ b/.codeforge/file-manifest.json @@ -1,61 +1,61 @@ [ { - "src": "defaults/settings.json", + "src": "config/settings.json", "dest": "${CLAUDE_CONFIG_DIR}", "enabled": true, "overwrite": "if-changed" }, { - "src": "defaults/keybindings.json", + "src": "config/keybindings.json", "dest": "${CLAUDE_CONFIG_DIR}", "enabled": true, "overwrite": "if-changed" }, { - "src": "defaults/main-system-prompt.md", + "src": "config/main-system-prompt.md", "dest": "${CLAUDE_CONFIG_DIR}", "enabled": true, "overwrite": "if-changed" }, { - "src": "defaults/rules/spec-workflow.md", + "src": "config/rules/spec-workflow.md", "dest": "${CLAUDE_CONFIG_DIR}/rules", "enabled": true, "overwrite": "if-changed" }, { - "src": "defaults/rules/workspace-scope.md", + "src": "config/rules/workspace-scope.md", "dest": "${CLAUDE_CONFIG_DIR}/rules", "enabled": true, "overwrite": "if-changed" }, { - "src": "defaults/rules/session-search.md", + "src": "config/rules/session-search.md", "dest": "${CLAUDE_CONFIG_DIR}/rules", "enabled": true, "overwrite": "if-changed" }, { - "src": "defaults/writing-system-prompt.md", + "src": "config/writing-system-prompt.md", "dest": "${CLAUDE_CONFIG_DIR}", "enabled": true, "overwrite": "if-changed" }, { - "src": "defaults/orchestrator-system-prompt.md", + "src": "config/orchestrator-system-prompt.md", "dest": "${CLAUDE_CONFIG_DIR}", "enabled": true, "overwrite": "if-changed" }, { - "src": "defaults/ccstatusline-settings.json", + "src": "config/ccstatusline-settings.json", "dest": "${HOME}/.config/ccstatusline", "destFilename": "settings.json", "enabled": true, "overwrite": "if-changed" }, { - "src": "defaults/ccstatusline-settings.json", + "src": "config/ccstatusline-settings.json", "dest": "/usr/local/share/ccstatusline", "destFilename": "settings.template.json", "enabled": true, diff --git a/.devcontainer/connect-external-terminal.ps1 b/.codeforge/scripts/connect-external-terminal.ps1 similarity index 100% rename from .devcontainer/connect-external-terminal.ps1 rename to .codeforge/scripts/connect-external-terminal.ps1 diff --git a/.devcontainer/connect-external-terminal.sh b/.codeforge/scripts/connect-external-terminal.sh similarity index 100% rename from .devcontainer/connect-external-terminal.sh rename to .codeforge/scripts/connect-external-terminal.sh diff --git a/.devcontainer/.env.example b/.devcontainer/.env.example index 1533e10..268415b 100644 --- a/.devcontainer/.env.example +++ b/.devcontainer/.env.example @@ -3,9 +3,10 @@ # Paths (defaults shown — uncomment to override) # CLAUDE_CONFIG_DIR=$HOME/.claude -# CONFIG_SOURCE_DIR=/custom/path/to/config +# CODEFORGE_DIR=/workspaces/.codeforge +# CONFIG_SOURCE_DIR is deprecated — use CODEFORGE_DIR instead -# Setup: copy config files to CLAUDE_CONFIG_DIR (per config/file-manifest.json) +# Setup: copy config files to CLAUDE_CONFIG_DIR (per .codeforge/file-manifest.json) SETUP_CONFIG=true # Setup: add cc/claude/ccraw aliases to shell rc files diff --git a/.devcontainer/CHANGELOG.md b/.devcontainer/CHANGELOG.md index 81f64b3..4ea3055 100644 --- a/.devcontainer/CHANGELOG.md +++ b/.devcontainer/CHANGELOG.md @@ -197,6 +197,31 @@ #### VS Code Extensions - **Todo+** (`fabiospampinato.vscode-todo-plus`) — removed from devcontainer extensions +## [v2.0.0] - 2026-02-26 + +### Added + +#### .codeforge/ User Customization Directory +- New `.codeforge/` directory centralizes all user-customizable configuration files +- Checksum-based modification detection preserves user changes during updates +- `codeforge config apply` CLI command deploys config files to `~/.claude/` (same as container start) +- Auto-migration from `.devcontainer/config/defaults/` to `.codeforge/config/` for existing users +- `.codeforge/.codeforge-preserve` for listing additional files to preserve during updates + +### Changed + +#### Configuration +- Config files moved from `.devcontainer/config/defaults/` to `.codeforge/config/` +- File manifest moved from `.devcontainer/config/file-manifest.json` to `.codeforge/file-manifest.json` +- Terminal connection scripts moved from `.devcontainer/` to `.codeforge/scripts/` +- `CONFIG_SOURCE_DIR` env var deprecated in favor of `CODEFORGE_DIR` +- `--force` updates now use checksum comparison for `.codeforge/` files (writes `.default` instead of `.codeforge-new`) +- `--reset` preserves `.codeforge/` user modifications (only `.devcontainer/` is wiped) + +#### Migration +- v2 migration marker moved to `.codeforge/.markers/v2-migrated` +- Container start auto-migrates `.devcontainer/config/defaults/` to `.codeforge/config/` if needed + ## [v1.14.2] - 2026-02-24 ### Added diff --git a/.devcontainer/CLAUDE.md b/.devcontainer/CLAUDE.md index a295e71..b3e7920 100644 --- a/.devcontainer/CLAUDE.md +++ b/.devcontainer/CLAUDE.md @@ -8,34 +8,39 @@ CodeForge devcontainer for AI-assisted development with Claude Code. .devcontainer/ ├── devcontainer.json # Container definition ├── .env # Setup flags (SETUP_CONFIG, SETUP_ALIASES, etc.) -├── config/ -│ ├── file-manifest.json # Declarative config file deployment -│ └── defaults/ # Source files deployed on start via file-manifest -│ ├── settings.json # Model, permissions, plugins, env vars -│ ├── keybindings.json # Keyboard shortcuts -│ ├── main-system-prompt.md -│ ├── orchestrator-system-prompt.md -│ ├── writing-system-prompt.md -│ ├── ccstatusline-settings.json # Status bar widget layout -│ └── rules/ # Deployed to .claude/rules/ ├── features/ # Custom devcontainer features ├── plugins/devs-marketplace/ # Local plugin marketplace └── scripts/ # Setup scripts (run via postStartCommand) + +.codeforge/ +├── file-manifest.json # Declarative config file deployment +├── config/ # Source files deployed on start via file-manifest +│ ├── settings.json # Model, permissions, plugins, env vars +│ ├── keybindings.json # Keyboard shortcuts +│ ├── main-system-prompt.md +│ ├── orchestrator-system-prompt.md +│ ├── writing-system-prompt.md +│ ├── ccstatusline-settings.json # Status bar widget layout +│ └── rules/ # Deployed to .claude/rules/ +├── scripts/ # Terminal connection scripts +│ ├── connect-external-terminal.sh +│ └── connect-external-terminal.ps1 +└── .codeforge-preserve # Lists additional files to preserve during updates ``` ## Key Configuration | File | Purpose | |------|---------| -| `config/defaults/settings.json` | Model, tokens, permissions, plugins, env vars | -| `config/defaults/main-system-prompt.md` | System prompt defining assistant behavior | -| `config/defaults/orchestrator-system-prompt.md` | Orchestrator mode prompt (delegation-first) | -| `config/defaults/ccstatusline-settings.json` | Status bar widget layout (deployed to ~/.config/ccstatusline/) | -| `config/file-manifest.json` | Controls which config files deploy and when | +| `.codeforge/config/settings.json` | Model, tokens, permissions, plugins, env vars | +| `.codeforge/config/main-system-prompt.md` | System prompt defining assistant behavior | +| `.codeforge/config/orchestrator-system-prompt.md` | Orchestrator mode prompt (delegation-first) | +| `.codeforge/config/ccstatusline-settings.json` | Status bar widget layout (deployed to ~/.config/ccstatusline/) | +| `.codeforge/file-manifest.json` | Controls which config files deploy and when | | `devcontainer.json` | Container definition: image, features, mounts | | `.env` | Boolean flags controlling setup steps | -Config files deploy via `file-manifest.json` on every container start. Most deploy to `~/.claude/`; ccstatusline config deploys to `~/.config/ccstatusline/`. Each entry supports `overwrite`: `"if-changed"` (default, sha256), `"always"`, or `"never"`. Supported variables: `${CLAUDE_CONFIG_DIR}`, `${WORKSPACE_ROOT}`, `${HOME}`. +Config files deploy via `.codeforge/file-manifest.json` on every container start. Most deploy to `~/.claude/`; ccstatusline config deploys to `~/.config/ccstatusline/`. Each entry supports `overwrite`: `"if-changed"` (default, sha256), `"always"`, or `"never"`. Supported variables: `${CLAUDE_CONFIG_DIR}`, `${WORKSPACE_ROOT}`, `${HOME}`. ## Worktrees @@ -70,6 +75,8 @@ git worktree add /workspaces/projects/.worktrees/ -b | Command | Purpose | |---------|---------| | `cc` / `claude` | Run Claude Code with auto-configuration | +| `codeforge config apply` | Deploy config files to `~/.claude/` (same as container start) | +| `codeforge` | CLI for CodeForge management commands | | `ccraw` | Vanilla Claude Code (bypasses config) | | `ccw` | Claude Code with writing system prompt | | `cc-orc` | Claude Code in orchestrator mode (delegation-first) | @@ -102,11 +109,11 @@ Declared in `settings.json` under `enabledPlugins`, auto-activated on start: ## Rules System -Rules in `config/defaults/rules/` deploy to `.claude/rules/` on every container start. They load into ALL sessions automatically. +Rules in `.codeforge/config/rules/` deploy to `.claude/rules/` on every container start. They load into ALL sessions automatically. **Current rules:** `spec-workflow.md`, `workspace-scope.md`, `session-search.md` -**Adding rules:** Create `.md` in `config/defaults/rules/`, add a manifest entry in `file-manifest.json`. +**Adding rules:** Create `.md` in `.codeforge/config/rules/`, add a manifest entry in `.codeforge/file-manifest.json`. ## Environment @@ -129,17 +136,17 @@ The `~/.claude/` directory is backed by a Docker named volume (`codeforge-claude ## Modifying Behavior -1. **Change model**: Edit `config/defaults/settings.json` → `"model"` field -2. **Change system prompt**: Edit `config/defaults/main-system-prompt.md` -3. **Add config file**: Add entry to `config/file-manifest.json` +1. **Change model**: Edit `.codeforge/config/settings.json` → `"model"` field +2. **Change system prompt**: Edit `.codeforge/config/main-system-prompt.md` +3. **Add config file**: Place in `.codeforge/config/`, add entry to `.codeforge/file-manifest.json` 4. **Add features**: Add to `"features"` in `devcontainer.json` 5. **Disable features**: Set `"version": "none"` in the feature's config 6. **Disable setup steps**: Set flags to `false` in `.env` -7. **Customize status bar**: Edit `config/defaults/ccstatusline-settings.json` (see below) +7. **Customize status bar**: Edit `.codeforge/config/ccstatusline-settings.json` (see below) ## Status Bar Widgets -The status bar is configured in `config/defaults/ccstatusline-settings.json` (deploys to `~/.config/ccstatusline/settings.json`). Each widget is a JSON object in a line array. +The status bar is configured in `.codeforge/config/ccstatusline-settings.json` (deploys to `~/.config/ccstatusline/settings.json`). Each widget is a JSON object in a line array. ### Widget Properties diff --git a/.devcontainer/README.md b/.devcontainer/README.md index e5e5623..9483cf2 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -132,7 +132,7 @@ GitHub CLI credentials are automatically persisted across container rebuilds. Th ### The `cc` Command -The `cc` command is an alias that launches Claude Code with the project's system prompt and plan-mode permissions. For Agent Teams split-pane support, use the **"Claude Teams (tmux)"** terminal profile in VS Code (dropdown next to the `+` button) or connect via `connect-external-terminal.sh`. +The `cc` command is an alias that launches Claude Code with the project's system prompt and plan-mode permissions. For Agent Teams split-pane support, use the **"Claude Teams (tmux)"** terminal profile in VS Code (dropdown next to the `+` button) or connect via `.codeforge/scripts/connect-external-terminal.sh`. ```bash cc # Start Claude Code in current directory @@ -225,7 +225,7 @@ Copy `.devcontainer/.env.example` to `.devcontainer/.env` and customize: ### Claude Code Settings -Default settings are in `.devcontainer/config/defaults/settings.json`. File copying is controlled by `config/file-manifest.json`, which specifies per-file overwrite behavior (`"if-changed"`, `"always"`, or `"never"`). +Default settings are in `.codeforge/config/settings.json`. File copying is controlled by `.codeforge/file-manifest.json`, which specifies per-file overwrite behavior (`"if-changed"`, `"always"`, or `"never"`). To add a custom config file, append an entry to `file-manifest.json`: ```json @@ -243,7 +243,7 @@ Key defaults: ### Keybindings -Default keybindings are in `.devcontainer/config/defaults/keybindings.json` (empty by default — Claude Code defaults apply). Customize by adding entries to the `bindings` array. +Default keybindings are in `.codeforge/config/keybindings.json` (empty by default — Claude Code defaults apply). Customize by adding entries to the `bindings` array. **VS Code Terminal Passthrough**: `Ctrl+P` and `Ctrl+F` are configured to pass through to the terminal (via `terminal.integrated.commandsToSkipShell`) so Claude Code receives them. Other VS Code shortcuts that conflict with Claude Code: @@ -260,7 +260,7 @@ For conflicting shortcuts, use Meta (Alt) variants or add custom keybindings. ### System Prompt -The default system prompt is in `.devcontainer/config/defaults/main-system-prompt.md`. Override it by creating a `.claude/main-system-prompt.md` in your project directory. +The default system prompt is in `.codeforge/config/main-system-prompt.md`. Override it by creating a `.claude/main-system-prompt.md` in your project directory. ## Custom Features @@ -426,9 +426,9 @@ The `setup-projects.sh` script auto-detects projects under `/workspaces/` and ma - **Authentication required**: Run `claude` once to authenticate before using `cc` - **Plan mode default**: The container starts in "plan" mode, which prompts for approval before making changes -- **Config is managed by manifest**: `config/file-manifest.json` controls which files are copied and when — default `overwrite: "if-changed"` uses sha256 comparison. Persistent changes go in `.devcontainer/config/defaults/settings.json` +- **Config is managed by manifest**: `.codeforge/file-manifest.json` controls which files are copied and when — default `overwrite: "if-changed"` uses sha256 comparison. Persistent changes go in `.codeforge/config/settings.json` - **GitHub auth persists**: Run `gh auth login` once or configure `.secrets`; credentials survive container rebuilds -- **Agent Teams needs tmux**: Split panes only work inside tmux. Use the "Claude Teams (tmux)" VS Code terminal profile or `connect-external-terminal.sh` from WezTerm/iTerm2 +- **Agent Teams needs tmux**: Split panes only work inside tmux. Use the "Claude Teams (tmux)" VS Code terminal profile or `.codeforge/scripts/connect-external-terminal.sh` from WezTerm/iTerm2 ## Troubleshooting @@ -439,7 +439,7 @@ Common issues and solutions. For detailed troubleshooting, see [docs/troubleshoo | `cc: command not found` | Run `source ~/.bashrc` or open a new terminal | | `claude` fails during startup | Background update may be in progress — wait 10s and retry | | GitHub push fails | Run `gh auth status` to check authentication | -| Plugin not loading | Check `enabledPlugins` in `config/defaults/settings.json` | +| Plugin not loading | Check `enabledPlugins` in `.codeforge/config/settings.json` | | Feature not installed | Check `devcontainer.json` for `"version": "none"` | | Tool version/status | Run `cc-tools` to list all tools with version info | | Full health check | Run `check-setup` to verify setup status | diff --git a/.devcontainer/features/ccstatusline/install.sh b/.devcontainer/features/ccstatusline/install.sh index 988cecd..4d123fa 100755 --- a/.devcontainer/features/ccstatusline/install.sh +++ b/.devcontainer/features/ccstatusline/install.sh @@ -69,7 +69,7 @@ else fi # Widget config is managed by file-manifest.json (deployed by setup-config.sh) -# Source: .devcontainer/config/defaults/ccstatusline-settings.json +# Source: .codeforge/config/ccstatusline-settings.json # Deployed to: ~/.config/ccstatusline/settings.json (if-changed) # Template: /usr/local/share/ccstatusline/settings.template.json (always) echo "[ccstatusline] Widget config managed by file-manifest.json" @@ -237,7 +237,7 @@ echo " ccstatusline Installation Complete" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "Configuration:" -echo " • Source: .devcontainer/config/defaults/ccstatusline-settings.json" +echo " • Source: .codeforge/config/ccstatusline-settings.json" echo " • Deployed to: ~/.config/ccstatusline/settings.json (by file-manifest)" echo " • Template: /usr/local/share/ccstatusline/settings.template.json" echo " • User: ${USERNAME}" @@ -249,7 +249,7 @@ echo "━━━━━━━━━━━━━━━━━━━━━━━━ echo "" echo "1. Widget config is deployed automatically on container start" echo "" -echo "2. To customize: edit .devcontainer/config/defaults/ccstatusline-settings.json" +echo "2. To customize: edit .codeforge/config/ccstatusline-settings.json" echo " Changes deploy on next container start (if-changed)" echo "" echo "3. Test manually:" diff --git a/.devcontainer/features/tmux/README.md b/.devcontainer/features/tmux/README.md index dd9ea4d..7b75089 100644 --- a/.devcontainer/features/tmux/README.md +++ b/.devcontainer/features/tmux/README.md @@ -23,17 +23,17 @@ The VS Code integrated terminal does **not** support tmux split panes. To use sp ### Option 1: Helper Scripts (Recommended) -Use the provided helper scripts in the `.devcontainer/` folder: +Use the provided helper scripts in the `.codeforge/scripts/` folder: **Linux/macOS:** ```bash -cd /path/to/your/project/.devcontainer +cd /path/to/your/project/.codeforge/scripts ./connect-external-terminal.sh ``` **Windows (PowerShell):** ```powershell -cd C:\path\to\your\project\.devcontainer +cd C:\path\to\your\project\.codeforge\scripts .\connect-external-terminal.ps1 ``` diff --git a/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/claude-guide.md b/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/claude-guide.md index 3447b14..e670065 100644 --- a/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/claude-guide.md +++ b/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/claude-guide.md @@ -72,9 +72,9 @@ Direct model interaction via the Claude API (formerly Anthropic API). Covers Mes .claude/main-system-prompt.md # Active system prompt CLAUDE.md # Project instructions -# DevContainer configuration -.devcontainer/config/defaults/settings.json # Default settings -.devcontainer/config/defaults/main-system-prompt.md # Default system prompt +# User-customizable configuration +.codeforge/config/settings.json # Default settings +.codeforge/config/main-system-prompt.md # Default system prompt # Plugin directory .devcontainer/plugins/devs-marketplace/plugins/ # All plugins @@ -142,7 +142,7 @@ If the question involves configuration or SDK usage, provide a complete, runnabl **Agent approach**: 1. WebFetch the Claude Code documentation for environment variable reference -2. Read local `.devcontainer/config/defaults/settings.json` to show which are currently configured +2. Read local `.codeforge/config/settings.json` to show which are currently configured 3. Summarize the most important variables with their effects **Output includes**: Answer with a categorized list of environment variables (model selection, behavior, performance, experimental features), Documentation References to the official docs, Related Features noting the `settings.json` `env` field as an alternative to shell environment variables. diff --git a/.devcontainer/scripts/setup-aliases.sh b/.devcontainer/scripts/setup-aliases.sh index e7cf287..91d8ca8 100755 --- a/.devcontainer/scripts/setup-aliases.sh +++ b/.devcontainer/scripts/setup-aliases.sh @@ -117,6 +117,7 @@ cc-tools() { } alias check-setup='bash ${DEVCONTAINER_SCRIPTS}/check-setup.sh' +alias codeforge='node \${WORKSPACE_ROOT}/setup.js' ${BLOCK_END} BLOCK_EOF diff --git a/.devcontainer/scripts/setup-config.sh b/.devcontainer/scripts/setup-config.sh index 551059c..42116e3 100755 --- a/.devcontainer/scripts/setup-config.sh +++ b/.devcontainer/scripts/setup-config.sh @@ -3,13 +3,21 @@ # Copyright (c) 2026 Marcus Krueger # Copy configuration files to workspace based on file-manifest.json -CONFIG_DIR="${CONFIG_SOURCE_DIR:?CONFIG_SOURCE_DIR not set}" -MANIFEST="$CONFIG_DIR/file-manifest.json" - log() { echo "[setup-config] $*"; } warn() { echo "[setup-config] WARNING: $*"; } err() { echo "[setup-config] ERROR: $*" >&2; } +CODEFORGE_DIR="${CODEFORGE_DIR:-${WORKSPACE_ROOT:?}/.codeforge}" +CONFIG_DIR="$CODEFORGE_DIR" +MANIFEST="$CODEFORGE_DIR/file-manifest.json" + +# Legacy fallback: if .codeforge/ doesn't exist but old config dir does, warn and use old path +if [ ! -d "$CODEFORGE_DIR" ] && [ -d "${WORKSPACE_ROOT}/.devcontainer/config/defaults" ]; then + warn ".codeforge/ not found — falling back to .devcontainer/config/defaults (deprecated)" + CONFIG_DIR="${WORKSPACE_ROOT}/.devcontainer/config" + MANIFEST="$CONFIG_DIR/file-manifest.json" +fi + # Deprecation notice if legacy OVERWRITE_CONFIG is still set if [ -n "${OVERWRITE_CONFIG+x}" ]; then warn "OVERWRITE_CONFIG is deprecated. Use per-file 'overwrite' in config/file-manifest.json instead." @@ -20,7 +28,7 @@ legacy_copy() { local target_dir="${CLAUDE_CONFIG_DIR:?CLAUDE_CONFIG_DIR not set}" warn "file-manifest.json not found, falling back to legacy copy" mkdir -p "$target_dir" - for file in defaults/settings.json defaults/keybindings.json defaults/main-system-prompt.md; do + for file in config/settings.json config/keybindings.json config/main-system-prompt.md; do if [ -f "$CONFIG_DIR/$file" ]; then local basename="${file##*/}" cp "$CONFIG_DIR/$file" "$target_dir/$basename" diff --git a/.devcontainer/scripts/setup-migrate-claude.sh b/.devcontainer/scripts/setup-migrate-claude.sh index 3edf5bb..6ad2198 100755 --- a/.devcontainer/scripts/setup-migrate-claude.sh +++ b/.devcontainer/scripts/setup-migrate-claude.sh @@ -15,6 +15,7 @@ _USER_HOME=$(getent passwd "$_USERNAME" 2>/dev/null | cut -d: -f6) _USER_HOME="${_USER_HOME:-/home/$_USERNAME}" NEW_DIR="${CLAUDE_CONFIG_DIR:-${_USER_HOME}/.claude}" MARKER="$NEW_DIR/.migrated-from-workspaces" +CODEFORGE_MARKER="${CODEFORGE_DIR:-${WORKSPACE_ROOT:-/workspaces}/.codeforge}/.markers/v2-migrated" # Nothing to migrate if old directory doesn't exist if [ ! -d "$OLD_DIR" ]; then @@ -26,8 +27,8 @@ if [ -z "$(ls -A "$OLD_DIR" 2>/dev/null)" ]; then exit 0 fi -# Idempotency: skip if migration already completed -if [ -f "$MARKER" ]; then +# Idempotency: skip if migration already completed (check both old and new markers) +if [ -f "$MARKER" ] || [ -f "$CODEFORGE_MARKER" ]; then exit 0 fi @@ -59,8 +60,12 @@ if cp -a "$OLD_DIR/." "$NEW_DIR/"; then exit 1 fi - # Mark migration complete + # Mark migration complete (write to both old and new marker locations) date -Iseconds > "$MARKER" + _codeforge_markers_dir="$(dirname "$CODEFORGE_MARKER")" + if [ -d "$_codeforge_markers_dir" ] || mkdir -p "$_codeforge_markers_dir" 2>/dev/null; then + date -Iseconds > "$CODEFORGE_MARKER" + fi # Rename old directory to .bak if mv "$OLD_DIR" "${OLD_DIR}.bak" 2>/dev/null; then diff --git a/.devcontainer/scripts/setup-migrate-codeforge.sh b/.devcontainer/scripts/setup-migrate-codeforge.sh new file mode 100755 index 0000000..f37fad9 --- /dev/null +++ b/.devcontainer/scripts/setup-migrate-codeforge.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-3.0-only +# Copyright (c) 2026 Marcus Krueger +# One-time migration: .devcontainer/config/ → .codeforge/ +# Migrates config files, manifest, and terminal scripts from the legacy +# .devcontainer/config/ layout to the new .codeforge/ directory structure. + +WORKSPACE_ROOT="${WORKSPACE_ROOT:?WORKSPACE_ROOT not set}" +CODEFORGE_DIR="${CODEFORGE_DIR:-${WORKSPACE_ROOT}/.codeforge}" +OLD_CONFIG_DIR="${WORKSPACE_ROOT}/.devcontainer/config" +OLD_DEFAULTS_DIR="${OLD_CONFIG_DIR}/defaults" +MARKER="$CODEFORGE_DIR/.markers/v2-migrated" + +log() { echo "[setup-migrate-codeforge] $*"; } +warn() { echo "[setup-migrate-codeforge] WARNING: $*"; } + +# Already migrated — skip +if [ -d "$CODEFORGE_DIR" ]; then + log ".codeforge/ already exists — skipping migration" + exit 0 +fi + +# Nothing to migrate if old defaults dir doesn't exist +if [ ! -d "$OLD_DEFAULTS_DIR" ]; then + log "No legacy .devcontainer/config/defaults/ found — skipping migration" + exit 0 +fi + +log "Migrating .devcontainer/config/ → .codeforge/ ..." + +# Create directory structure +mkdir -p "$CODEFORGE_DIR/config/rules" \ + "$CODEFORGE_DIR/scripts" \ + "$CODEFORGE_DIR/.markers" \ + "$CODEFORGE_DIR/.checksums" + +# Copy config files from .devcontainer/config/defaults/ → .codeforge/config/ +if [ -d "$OLD_DEFAULTS_DIR" ]; then + cp -a "$OLD_DEFAULTS_DIR/." "$CODEFORGE_DIR/config/" + log "Copied config files from defaults/ → .codeforge/config/" +fi + +# Copy file-manifest.json, rewriting src paths from defaults/ to config/ +if [ -f "$OLD_CONFIG_DIR/file-manifest.json" ]; then + sed 's|"defaults/|"config/|g' "$OLD_CONFIG_DIR/file-manifest.json" > "$CODEFORGE_DIR/file-manifest.json" + log "Copied file-manifest.json (rewrote defaults/ → config/)" +fi + +# Copy terminal scripts from .devcontainer/ → .codeforge/scripts/ +for script in connect-external-terminal.sh connect-external-terminal.ps1; do + if [ -f "${WORKSPACE_ROOT}/.devcontainer/${script}" ]; then + cp "${WORKSPACE_ROOT}/.devcontainer/${script}" "$CODEFORGE_DIR/scripts/${script}" + log "Copied ${script} → .codeforge/scripts/" + fi +done + +# Write migration marker +date -Iseconds > "$MARKER" + +log "Migration complete — .codeforge/ is ready" diff --git a/.devcontainer/scripts/setup.sh b/.devcontainer/scripts/setup.sh index ffee82e..135273c 100755 --- a/.devcontainer/scripts/setup.sh +++ b/.devcontainer/scripts/setup.sh @@ -41,9 +41,22 @@ if [ "$CONFIG_SOURCE_DIR" = "/workspaces/.claude" ]; then fi fi +# Deprecation guard: CONFIG_SOURCE_DIR may still point to .devcontainer/config +# (pre-v2.0 default). Redirect to .codeforge. +if [ "$CONFIG_SOURCE_DIR" = "$DEVCONTAINER_DIR/config" ] || [ "$CONFIG_SOURCE_DIR" = "/workspaces/.devcontainer/config" ]; then + echo "[setup] WARNING: CONFIG_SOURCE_DIR pointing to .devcontainer/config is deprecated (moved to .codeforge in v2.0)" + echo "[setup] Redirecting to .codeforge." + CODEFORGE_DIR="${WORKSPACE_ROOT:?}/.codeforge" + if [ -f "$ENV_FILE" ]; then + sed -i 's|^CONFIG_SOURCE_DIR=.*\.devcontainer/config.*|# CONFIG_SOURCE_DIR removed (v2.0: now uses .codeforge)|' "$ENV_FILE" + echo "[setup] .env updated — CONFIG_SOURCE_DIR line commented out." + fi +fi + # Apply defaults for any unset variables : "${CLAUDE_CONFIG_DIR:=$HOME/.claude}" : "${CONFIG_SOURCE_DIR:=$DEVCONTAINER_DIR/config}" +: "${CODEFORGE_DIR:=${WORKSPACE_ROOT:?}/.codeforge}" : "${SETUP_CONFIG:=true}" : "${SETUP_ALIASES:=true}" : "${SETUP_AUTH:=true}" @@ -53,7 +66,7 @@ fi : "${SETUP_TERMINAL:=true}" : "${SETUP_POSTSTART:=true}" -export CLAUDE_CONFIG_DIR CONFIG_SOURCE_DIR SETUP_CONFIG SETUP_ALIASES SETUP_AUTH SETUP_PLUGINS SETUP_UPDATE_CLAUDE SETUP_PROJECTS SETUP_TERMINAL SETUP_POSTSTART +export CLAUDE_CONFIG_DIR CONFIG_SOURCE_DIR CODEFORGE_DIR SETUP_CONFIG SETUP_ALIASES SETUP_AUTH SETUP_PLUGINS SETUP_UPDATE_CLAUDE SETUP_PROJECTS SETUP_TERMINAL SETUP_POSTSTART # Fix named volume ownership — Docker creates named volumes as root:root # regardless of remoteUser. This is the only setup script requiring sudo. @@ -124,6 +137,7 @@ run_poststart_hooks() { } run_script "$SCRIPT_DIR/setup-migrate-claude.sh" "true" +run_script "$SCRIPT_DIR/setup-migrate-codeforge.sh" "true" run_script "$SCRIPT_DIR/setup-auth.sh" "$SETUP_AUTH" run_script "$SCRIPT_DIR/setup-config.sh" "$SETUP_CONFIG" run_script "$SCRIPT_DIR/setup-aliases.sh" "$SETUP_ALIASES" diff --git a/.gitignore b/.gitignore index 47550ad..ace9696 100644 --- a/.gitignore +++ b/.gitignore @@ -63,17 +63,24 @@ node_modules/ # Claude Code directory (user-specific) .claude/ -# All hidden directories except devcontainer +# All hidden directories except devcontainer and codeforge .* !.devcontainer/ !.devcontainer/**/.claude-plugin/ !.devcontainer/ !.devcontainer/**/.claude-plugin/**/.claude-plugin/ +!.codeforge/ !.git/ !.github/ !.gitignore !.gitattributes !.npmignore + +# .codeforge per-installation state (not tracked) +.codeforge/.checksums/ +.codeforge/.markers/ +# Un-ignore .codeforge-preserve (user-editable, should be tracked) +!.codeforge/.codeforge-preserve .devcontainer/config/claude/output-styles/strict-development.md .devcontainer/config/claude/mcp.json diff --git a/CLAUDE.md b/CLAUDE.md index a7d4a2a..7c70590 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,5 +23,9 @@ All user-facing changes MUST be reflected in documentation: - **Plugin changes** → update the plugin's `README.md` - **Feature changes** → update `features/README.md` and the feature's `devcontainer-feature.json` if applicable - **Config system changes** → update `.devcontainer/CLAUDE.md` -- **New config files in `config/defaults/`** → add entry to `config/file-manifest.json` +- **New config files in `.codeforge/config/`** → add entry to `.codeforge/file-manifest.json` - **Docs site** → update relevant pages in `docs/` if the docs site exists + +### User Configuration + +All user-customizable configuration files belong in `.codeforge/`. New config files go in `.codeforge/config/`, with a corresponding entry in `.codeforge/file-manifest.json`. diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 718806f..f27b677 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -70,98 +70,117 @@ export default defineConfig({ }, ], plugins: [ - starlightSidebarTopics( - [ - { - label: 'Getting Started', - link: '/getting-started/', - icon: 'rocket', - items: [ - { label: 'Overview', slug: 'getting-started' }, - { label: 'Requirements', slug: 'getting-started/requirements' }, - { label: 'Installation', slug: 'getting-started/installation' }, - { label: 'Your First Session', slug: 'getting-started/first-session' }, - ], - }, - { - label: 'Plugins', - link: '/plugins/', - icon: 'puzzle', - items: [ - { label: 'Plugin Overview', slug: 'plugins' }, - { - label: 'Core Plugins', - items: [ - { label: 'Agent System', slug: 'plugins/agent-system' }, - { label: 'Skill Engine', slug: 'plugins/skill-engine' }, - { label: 'Spec Workflow', slug: 'plugins/spec-workflow' }, - { label: 'Ticket Workflow', slug: 'plugins/ticket-workflow' }, - ], - }, - { - label: 'Quality & Safety', - items: [ - { label: 'Auto Code Quality', slug: 'plugins/auto-code-quality' }, - { label: 'Dangerous Command Blocker', slug: 'plugins/dangerous-command-blocker' }, - { label: 'Workspace Scope Guard', slug: 'plugins/workspace-scope-guard' }, - { label: 'Protected Files Guard', slug: 'plugins/protected-files-guard' }, - ], - }, - { - label: 'Session & Integration', - items: [ - { label: 'Session Context', slug: 'plugins/session-context' }, - { label: 'Git Workflow', slug: 'plugins/git-workflow' }, - { label: 'Prompt Snippets', slug: 'plugins/prompt-snippets' }, - { label: 'Notify Hook', slug: 'plugins/notify-hook' }, - { label: 'CodeForge LSP', slug: 'plugins/codeforge-lsp' }, - { label: 'Frontend Design', slug: 'plugins/frontend-design' }, - ], - }, - ], - }, - { - label: 'Features', - link: '/features/', - icon: 'star', - items: [ - { label: 'Features Overview', slug: 'features' }, - { label: 'AI Agents', slug: 'features/agents' }, - { label: 'Skills', slug: 'features/skills' }, - { label: 'CLI Tools', slug: 'features/tools' }, - { label: 'Code Intelligence', slug: 'features/code-intelligence' }, - ], - }, - { - label: 'Customization', - link: '/customization/', - icon: 'setting', - items: [ - { label: 'Customization Overview', slug: 'customization' }, - { label: 'Configuration', slug: 'customization/configuration' }, - { label: 'System Prompts', slug: 'customization/system-prompts' }, - { label: 'Rules', slug: 'customization/rules' }, - { label: 'Hooks', slug: 'customization/hooks' }, - { label: 'Keybindings', slug: 'customization/keybindings' }, - { label: 'Optional Features', slug: 'customization/optional-features' }, - ], - }, - { - label: 'Reference', - link: '/reference/', - icon: 'open-book', - items: [ - { label: 'Reference Overview', slug: 'reference' }, - { label: 'Changelog', slug: 'reference/changelog' }, - { label: 'Commands', slug: 'reference/commands' }, - { label: 'Environment Variables', slug: 'reference/environment' }, - { label: 'Architecture', slug: 'reference/architecture' }, - { label: 'Port Forwarding', slug: 'reference/port-forwarding' }, - { label: 'Troubleshooting', slug: 'reference/troubleshooting' }, - ], - }, - ], - ), + starlightSidebarTopics([ + { + label: "Getting Started", + link: "/getting-started/", + icon: "rocket", + items: [ + { label: "Overview", slug: "getting-started" }, + { label: "Requirements", slug: "getting-started/requirements" }, + { label: "Installation", slug: "getting-started/installation" }, + { + label: "Your First Session", + slug: "getting-started/first-session", + }, + ], + }, + { + label: "Plugins", + link: "/plugins/", + icon: "puzzle", + items: [ + { label: "Plugin Overview", slug: "plugins" }, + { + label: "Core Plugins", + items: [ + { label: "Agent System", slug: "plugins/agent-system" }, + { label: "Skill Engine", slug: "plugins/skill-engine" }, + { label: "Spec Workflow", slug: "plugins/spec-workflow" }, + { label: "Ticket Workflow", slug: "plugins/ticket-workflow" }, + ], + }, + { + label: "Quality & Safety", + items: [ + { + label: "Auto Code Quality", + slug: "plugins/auto-code-quality", + }, + { + label: "Dangerous Command Blocker", + slug: "plugins/dangerous-command-blocker", + }, + { + label: "Workspace Scope Guard", + slug: "plugins/workspace-scope-guard", + }, + { + label: "Protected Files Guard", + slug: "plugins/protected-files-guard", + }, + ], + }, + { + label: "Session & Integration", + items: [ + { label: "Session Context", slug: "plugins/session-context" }, + { label: "Git Workflow", slug: "plugins/git-workflow" }, + { label: "Prompt Snippets", slug: "plugins/prompt-snippets" }, + { label: "Notify Hook", slug: "plugins/notify-hook" }, + { label: "CodeForge LSP", slug: "plugins/codeforge-lsp" }, + { label: "Frontend Design", slug: "plugins/frontend-design" }, + ], + }, + ], + }, + { + label: "Features", + link: "/features/", + icon: "star", + items: [ + { label: "Features Overview", slug: "features" }, + { label: "AI Agents", slug: "features/agents" }, + { label: "Skills", slug: "features/skills" }, + { label: "CLI Tools", slug: "features/tools" }, + { + label: "Code Intelligence", + slug: "features/code-intelligence", + }, + ], + }, + { + label: "Customization", + link: "/customization/", + icon: "setting", + items: [ + { label: "Customization Overview", slug: "customization" }, + { label: "Configuration", slug: "customization/configuration" }, + { label: "System Prompts", slug: "customization/system-prompts" }, + { label: "Rules", slug: "customization/rules" }, + { label: "Hooks", slug: "customization/hooks" }, + { label: "Keybindings", slug: "customization/keybindings" }, + { + label: "Optional Features", + slug: "customization/optional-features", + }, + ], + }, + { + label: "Reference", + link: "/reference/", + icon: "open-book", + items: [ + { label: "Reference Overview", slug: "reference" }, + { label: "Changelog", slug: "reference/changelog" }, + { label: "Commands", slug: "reference/commands" }, + { label: "Environment Variables", slug: "reference/environment" }, + { label: "Architecture", slug: "reference/architecture" }, + { label: "Port Forwarding", slug: "reference/port-forwarding" }, + { label: "Troubleshooting", slug: "reference/troubleshooting" }, + ], + }, + ]), starlightImageZoom(), starlightLinksValidator({ errorOnRelativeLinks: false, diff --git a/docs/src/content/docs/customization/configuration.md b/docs/src/content/docs/customization/configuration.md index 0098b28..a4f030f 100644 --- a/docs/src/content/docs/customization/configuration.md +++ b/docs/src/content/docs/customization/configuration.md @@ -9,7 +9,7 @@ CodeForge configuration is spread across three files that each control a differe ## settings.json -The primary configuration file lives at `.devcontainer/config/defaults/settings.json`. It is deployed to `~/.claude/settings.json` on every container start and controls Claude Code's runtime behavior. +The primary configuration file lives at `.codeforge/config/settings.json`. It is deployed to `~/.claude/settings.json` on every container start and controls Claude Code's runtime behavior. ### Core Settings @@ -116,24 +116,24 @@ The `statusLine` block configures the terminal status bar: ## file-manifest.json -The file manifest at `.devcontainer/config/file-manifest.json` controls which configuration files are deployed to `~/.claude/` and how they are updated. Each entry specifies a source file, a destination, and an overwrite strategy: +The file manifest at `.codeforge/file-manifest.json` controls which configuration files are deployed to `~/.claude/` and how they are updated. Each entry specifies a source file, a destination, and an overwrite strategy: ```json [ { - "src": "defaults/settings.json", + "src": "config/settings.json", "dest": "${CLAUDE_CONFIG_DIR}", "enabled": true, "overwrite": "if-changed" }, { - "src": "defaults/main-system-prompt.md", + "src": "config/main-system-prompt.md", "dest": "${CLAUDE_CONFIG_DIR}", "enabled": true, "overwrite": "if-changed" }, { - "src": "defaults/rules/spec-workflow.md", + "src": "config/rules/spec-workflow.md", "dest": "${CLAUDE_CONFIG_DIR}/rules", "enabled": true, "overwrite": "if-changed" @@ -157,8 +157,8 @@ If you customize a deployed file (like `settings.json` or the system prompt) and To deploy a new file to `~/.claude/` automatically: -1. Place the file in `.devcontainer/config/defaults/` -2. Add an entry to `file-manifest.json` +1. Place the file in `.codeforge/config/` +2. Add an entry to `.codeforge/file-manifest.json` 3. Rebuild the container ## devcontainer.json @@ -251,7 +251,7 @@ The `.secrets` file is listed in `.gitignore`. Never commit it to version contro When the same setting is defined at multiple levels, the most specific value wins: 1. **Environment variables** (per-session or shell profile) -- highest precedence -2. **Project settings** (`.devcontainer/config/` in the current project) +2. **Project settings** (`.codeforge/config/` in the current project) 3. **Default settings** (shipped with CodeForge) For example, setting `ANTHROPIC_MODEL=claude-sonnet-4-5-20250929` in your shell overrides whatever is configured in `settings.json`. diff --git a/docs/src/content/docs/customization/index.md b/docs/src/content/docs/customization/index.md index f518f38..39537d9 100644 --- a/docs/src/content/docs/customization/index.md +++ b/docs/src/content/docs/customization/index.md @@ -78,7 +78,7 @@ The rule is loaded automatically on the next session. See [Rules](./rules/) for ### Adjust Claude's Coding Style -Edit the main system prompt at `.devcontainer/config/defaults/main-system-prompt.md`. Changes to this file are deployed to `.claude/` on container start. See [System Prompts](./system-prompts/) for guidance on effective prompt customization. +Edit the main system prompt at `.codeforge/config/main-system-prompt.md`. Changes to this file are deployed to `.claude/` on container start. See [System Prompts](./system-prompts/) for guidance on effective prompt customization. ### Add a Custom Hook diff --git a/docs/src/content/docs/customization/keybindings.md b/docs/src/content/docs/customization/keybindings.md index 2b92c4a..67ae8ee 100644 --- a/docs/src/content/docs/customization/keybindings.md +++ b/docs/src/content/docs/customization/keybindings.md @@ -67,7 +67,7 @@ Common command IDs: ### Option 3: Custom Claude Code Keybindings -Edit `.devcontainer/config/defaults/keybindings.json` to remap Claude Code actions to non-conflicting shortcuts: +Edit `.codeforge/config/keybindings.json` to remap Claude Code actions to non-conflicting shortcuts: ```json { diff --git a/docs/src/content/docs/customization/rules.md b/docs/src/content/docs/customization/rules.md index f92ee6f..c3a1941 100644 --- a/docs/src/content/docs/customization/rules.md +++ b/docs/src/content/docs/customization/rules.md @@ -11,7 +11,7 @@ Rules are Markdown files that define hard constraints applied to every Claude Co Rule files are Markdown documents placed in `.claude/rules/`. Claude Code loads every `.md` file in this directory at session start and treats their contents as mandatory instructions. The filename is descriptive but does not affect loading -- all files are loaded equally. -Rules are deployed from `.devcontainer/config/defaults/rules/` to `~/.claude/rules/` via the file manifest on every container start. You can also add rules directly to `.claude/rules/` in your project. +Rules are deployed from `.codeforge/config/rules/` to `~/.claude/rules/` via the file manifest on every container start. You can also add rules directly to `.claude/rules/` in your project. ### Rule Precedence @@ -109,12 +109,12 @@ Claude treats rule file content as mandatory. If you write "consider using X," C To make a rule deploy automatically to all projects: -1. Create the rule file in `.devcontainer/config/defaults/rules/` -2. Add an entry to `.devcontainer/config/file-manifest.json`: +1. Create the rule file in `.codeforge/config/rules/` +2. Add an entry to `.codeforge/file-manifest.json`: ```json { - "src": "defaults/rules/my-rule.md", + "src": "config/rules/my-rule.md", "dest": "${CLAUDE_CONFIG_DIR}/rules", "enabled": true, "overwrite": "if-changed" diff --git a/docs/src/content/docs/customization/system-prompts.md b/docs/src/content/docs/customization/system-prompts.md index cf2fdfc..a70cbba 100644 --- a/docs/src/content/docs/customization/system-prompts.md +++ b/docs/src/content/docs/customization/system-prompts.md @@ -11,7 +11,7 @@ System prompts define how Claude Code behaves during your sessions -- its coding The main system prompt is loaded for every `cc` or `claude` session. It is the single most influential file in shaping how Claude works with your code. -**Location:** `.devcontainer/config/defaults/main-system-prompt.md` +**Location:** `.codeforge/config/main-system-prompt.md` **Deployed to:** `~/.claude/main-system-prompt.md` ### What It Controls @@ -52,7 +52,7 @@ Each section is self-contained. You can edit, remove, or add sections independen The writing system prompt is activated when you launch Claude with the `ccw` command. It replaces the development-focused prompt with one tuned for creative fiction writing. -**Location:** `.devcontainer/config/defaults/writing-system-prompt.md` +**Location:** `.codeforge/config/writing-system-prompt.md` **Deployed to:** `~/.claude/writing-system-prompt.md` ### Key Differences from Main Prompt @@ -70,7 +70,7 @@ Use `cc` for coding sessions and `ccw` for writing sessions. Both are shell alia ### Editing the Main Prompt -To change development behavior, edit `.devcontainer/config/defaults/main-system-prompt.md`. Your changes are deployed to `~/.claude/` on the next container start via the file manifest. +To change development behavior, edit `.codeforge/config/main-system-prompt.md`. Your changes are deployed to `~/.claude/` on the next container start via the file manifest. For changes to take effect immediately (without restarting the container), edit the deployed copy at `~/.claude/main-system-prompt.md` directly. Be aware that this copy will be overwritten on the next container rebuild unless you change the overwrite mode in `file-manifest.json`. @@ -169,7 +169,7 @@ Both system prompts are listed in `file-manifest.json` and deployed to `~/.claud ```json { - "src": "defaults/main-system-prompt.md", + "src": "config/main-system-prompt.md", "dest": "${CLAUDE_CONFIG_DIR}", "enabled": true, "overwrite": "if-changed" @@ -179,7 +179,7 @@ Both system prompts are listed in `file-manifest.json` and deployed to `~/.claud The `if-changed` mode means your deployed copy is only overwritten when the source file's SHA-256 hash changes. If you want to make persistent local edits to the deployed prompt, change the overwrite mode to `"never"` so your changes survive container rebuilds. :::note[Two Copies] -The source file at `.devcontainer/config/defaults/main-system-prompt.md` is the canonical version. The deployed copy at `~/.claude/main-system-prompt.md` is what Claude Code actually loads. Edits to the source are deployed on next container start. Edits to the deployed copy take effect immediately but may be overwritten. +The source file at `.codeforge/config/main-system-prompt.md` is the canonical version. The deployed copy at `~/.claude/main-system-prompt.md` is what Claude Code actually loads. Edits to the source are deployed on next container start. Edits to the deployed copy take effect immediately but may be overwritten. ::: ## Related diff --git a/docs/src/content/docs/getting-started/installation.md b/docs/src/content/docs/getting-started/installation.md index 1f85c5d..9320203 100644 --- a/docs/src/content/docs/getting-started/installation.md +++ b/docs/src/content/docs/getting-started/installation.md @@ -22,7 +22,7 @@ If your project already has a `.devcontainer/` directory, the installer will war ```bash npx codeforge-dev --force ``` -The `--force` flag uses an intelligent sync — it preserves files you've customized (creating `.codeforge-new` diff files for review) rather than blindly overwriting everything. +The `--force` flag uses an intelligent sync — it preserves files you've customized (writing `.default` copies of new defaults in `.codeforge/` for review) rather than blindly overwriting everything. ::: ### Alternative Installation Methods @@ -45,12 +45,13 @@ your-project/ ├── .devcontainer/ │ ├── devcontainer.json # Container definition and feature list │ ├── .env # Setup flags -│ ├── config/ -│ │ ├── file-manifest.json # Controls config file deployment -│ │ └── defaults/ # System prompts, settings, rules │ ├── features/ # 22 custom DevContainer features │ ├── plugins/ # 14 plugins with hooks and scripts │ └── scripts/ # Setup and verification scripts +├── .codeforge/ +│ ├── file-manifest.json # Controls config file deployment +│ ├── config/ # System prompts, settings, rules +│ └── scripts/ # Terminal connection scripts └── ... (your existing files) ``` @@ -198,9 +199,9 @@ All 14 plugins are installed and active by default. They're configured through ` CodeForge works out of the box, but everything is customizable: - **`devcontainer.json`** — container image, features, resource limits, port forwarding -- **`config/defaults/settings.json`** — model selection, permissions, enabled plugins, environment variables -- **`config/defaults/main-system-prompt.md`** — Claude Code's behavioral guidelines -- **`config/defaults/rules/`** — rules loaded into every session automatically +- **`.codeforge/config/settings.json`** — model selection, permissions, enabled plugins, environment variables +- **`.codeforge/config/main-system-prompt.md`** — Claude Code's behavioral guidelines +- **`.codeforge/config/rules/`** — rules loaded into every session automatically See the [Customization section](../customization/) for full details on each configuration surface. diff --git a/docs/src/content/docs/reference/architecture.md b/docs/src/content/docs/reference/architecture.md index 4460ce4..8f11d78 100644 --- a/docs/src/content/docs/reference/architecture.md +++ b/docs/src/content/docs/reference/architecture.md @@ -119,17 +119,6 @@ CodeForge ships 38 skills across the skill-engine, spec-workflow, ticket-workflo .devcontainer/ +-- devcontainer.json # Container definition (image, features, mounts) +-- .env # Setup flags (SETUP_CONFIG, SETUP_ALIASES, etc.) -+-- config/ -| +-- file-manifest.json # Declarative config deployment rules -| +-- defaults/ -| +-- settings.json # Claude Code settings (model, plugins, env vars) -| +-- keybindings.json # Keyboard shortcuts -| +-- main-system-prompt.md # Development system prompt -| +-- writing-system-prompt.md # Writing mode system prompt -| +-- rules/ # Default rules deployed to .claude/rules/ -| +-- spec-workflow.md -| +-- workspace-scope.md -| +-- session-search.md +-- features/ # DevContainer features (tool installers) | +-- ccms/ # Session history search (Rust) | +-- ccstatusline/ # Terminal status line @@ -165,6 +154,22 @@ CodeForge ships 38 skills across the skill-engine, spec-workflow, ticket-workflo +-- setup-plugins.sh # Plugin installation +-- setup-auth.sh # Git/NPM auth +-- check-setup.sh # Health verification + +.codeforge/ ++-- file-manifest.json # Declarative config deployment rules ++-- config/ +| +-- settings.json # Claude Code settings (model, plugins, env vars) +| +-- keybindings.json # Keyboard shortcuts +| +-- main-system-prompt.md # Development system prompt +| +-- writing-system-prompt.md # Writing mode system prompt +| +-- rules/ # Default rules deployed to .claude/rules/ +| +-- spec-workflow.md +| +-- workspace-scope.md +| +-- session-search.md ++-- scripts/ +| +-- connect-external-terminal.sh # External terminal connection (Linux/macOS) +| +-- connect-external-terminal.ps1 # External terminal connection (Windows) ++-- .codeforge-preserve # Lists additional files to preserve during updates ``` ## Design Principles diff --git a/docs/src/content/docs/reference/changelog.md b/docs/src/content/docs/reference/changelog.md index 5efbea8..6e8a65f 100644 --- a/docs/src/content/docs/reference/changelog.md +++ b/docs/src/content/docs/reference/changelog.md @@ -47,10 +47,96 @@ For minor and patch updates, you can usually just rebuild the container. Check t ## Version History -## Unreleased +## [Unreleased] + +### Changed + +#### Port Forwarding +- Dynamic port forwarding for all ports in VS Code — previously only port 7847 was statically forwarded; now all ports auto-forward with notification + +#### Documentation +- Updated prerequisites and installation docs to support all DevContainer clients (VS Code, CLI, JetBrains Gateway, DevPod, Codespaces) +- Added tabbed client-specific instructions on the installation page +- Added dedicated port forwarding reference page covering VS Code auto-detect, devcontainer-bridge, and SSH tunneling +- **Ported `.devcontainer/docs/` to docs site** — merged 5 legacy reference docs into the Starlight documentation site: + - New **Keybindings** page (Customization) — VS Code/Claude Code shortcut conflicts and resolution options + - New **Troubleshooting** page (Reference) — 12+ problem/solution entries for build, auth, plugins, and performance issues + - New **Optional Features** page (Customization) — mcp-qdrant vector memory setup guide + - Merged setup variables (`.env` flags) into the Environment Variables reference + - Merged `.secrets` file authentication docs into the Configuration page +- Removed `.devcontainer/docs/` directory — all content now lives in the docs site ### Fixed +#### Session Context Plugin +- **Commit reminder** no longer blocks Claude from stopping — switched from `decision: "block"` to advisory `systemMessage` wrapped in `` tags +- **Commit reminder** now uses tiered logic: meaningful changes (3+ files, 2+ source files, or test files) get an advisory suggestion; small changes are silent +- **Commit reminder** only fires when the session actually modified files (via new PostToolUse edit tracker), preventing false reminders during read-only sessions + +#### Auto Code Quality Plugin +- **Advisory test runner** now reads from the correct tmp file prefix (`claude-cq-edited` instead of `claude-edited-files`), fixing a mismatch that prevented it from ever finding edited files + +#### Docs +- Removed stale merge conflict marker in first-session docs page + +### Added + +#### Startup +- **Container runtime pre-flight check** — validates Docker or Podman is installed and running before attempting to build the devcontainer; aborts with OS-specific remediation guidance (Windows/WSL, macOS, Linux) instead of a cryptic Docker client error + +#### README +- **"Why CodeForge?" section** — motivation and value proposition explaining the project's origins as a power user's personal setup +- **Architecture overview** — three-layer diagram (DevContainer → CodeForge Layer → Claude Code) with brief descriptions and link to full architecture docs +- **Configuration summary** — table of key config files with links to the documentation site + +#### Public Repo Quality +- **CI workflow** (`.github/workflows/ci.yml`) — test and lint jobs on PRs and pushes to main (Node 18, `npm test` + Biome check) +- **CodeQL security analysis** (`.github/workflows/codeql.yml`) — JavaScript scanning on PRs, pushes, and weekly schedule +- **Dependabot** (`.github/dependabot.yml`) — weekly updates for npm (root + docs) and GitHub Actions +- **Bug report template** (`.github/ISSUE_TEMPLATE/bug-report.yml`) — YAML form with version, environment, and repro steps +- **Feature request template** (`.github/ISSUE_TEMPLATE/feature-request.yml`) — YAML form with problem/solution/alternatives +- **Issue template config** (`.github/ISSUE_TEMPLATE/config.yml`) — commercial licensing contact link +- **Pull request template** (`.github/pull_request_template.md`) — description, type of change, and checklist +- **CONTRIBUTING.md** — contribution guidelines with GPL-3.0 licensing and CLA requirement +- **CLA.md** — Individual Contributor License Agreement enabling dual licensing +- **Dual licensing notice** — added to README.md (Contributing + License sections) and LICENSE.txt (header) +- **CI badge** — added to README.md badge row +- **SPDX copyright headers** — `GPL-3.0-only` identifier and `Copyright (c) 2026 Marcus Krueger` added to all 36 source files (setup.js, test.js, 34 shell scripts) + +#### Docs +- **CLAUDE.md** — new "Status Bar Widgets" section documenting widget properties, token color conventions, label fusion pattern, and available widget types + +#### Skills +- **worktree** — New skill for git worktree creation, management, and cleanup. Covers `EnterWorktree` tool, `--worktree` CLI flag, `.worktreeinclude` setup, worktree naming conventions, cleanup lifecycle, and CodeForge integration (Project Manager auto-detection, agent isolation). Includes two reference files: manual worktree commands and parallel workflow patterns. + +#### Claude Code Installation +- **Post-start onboarding hook** (`99-claude-onboarding.sh`) — ensures `hasCompletedOnboarding: true` in `.claude.json` when token auth is configured; catches overwrites from Claude Code CLI/extension that race with `postStartCommand` + +#### Git Workflow Plugin +- **`/ship`** — Combined commit/push/PR command with full code review, commit message approval, and AskUserQuestion confirmation before PR creation; optionally links to tickets if context exists +- **`/pr:review`** — Review any PR by number/URL or auto-detect from current branch; posts findings as PR comment with severity ratings; never approves or merges + +#### Features +- **devcontainer-bridge (dbr)** — Ports opened inside the container are now automatically discovered and forwarded to the host, even outside VS Code. Requires `dbr host-daemon` running on the host. See [devcontainer-bridge](https://github.com/bradleybeddoes/devcontainer-bridge) + +#### Orchestrator Mode +- **`cc-orc` alias** — new Claude Code entry point using `orchestrator-system-prompt.md` for delegation-first operation; orchestrator decomposes tasks, delegates to agents, surfaces questions, and synthesizes results without performing direct implementation work +- **`orchestrator-system-prompt.md`** — slim system prompt (~250 lines) containing only delegation model, agent catalog, question surfacing protocol, planning gates, spec enforcement, and action safety; all code standards, testing standards, and implementation details live in agent prompts + +#### Workhorse Agents +- **`investigator`** — consolidated read-only research agent (sonnet) merging the domains of researcher, explorer, dependency-analyst, git-archaeologist, debug-logs, and perf-profiler; handles codebase search, web research, git forensics, dependency auditing, log analysis, and performance profiling +- **`implementer`** — consolidated read-write implementation agent (opus, worktree) merging generalist, refactorer, and migrator; handles all code modifications with embedded code standards, execution discipline, and Stop hook regression testing +- **`tester`** — enhanced test agent (opus, worktree) with full testing standards, framework-specific guidance, and Stop hook verification; creates and verifies test suites +- **`documenter`** — consolidated documentation and specification agent (opus) merging doc-writer and spec-writer; handles README, API docs, docstrings, and the full spec lifecycle (create, refine, build, review, update, check) +- **Question Surfacing Protocol** — all 4 workhorse agents carry an identical protocol requiring them to STOP and return `## BLOCKED: Questions` sections when hitting ambiguities, ensuring no assumptions are made without user input + +### Fixed + +#### CCStatusLine Deployment +- **`CONFIG_SOURCE_DIR` deprecation guard** — `setup.sh` now detects stale `CONFIG_SOURCE_DIR=/workspaces/.claude` in `.env`, overrides to `$DEVCONTAINER_DIR/config`, and auto-comments the line on disk; the wrong path caused `setup-config.sh` to skip the file manifest entirely, leaving ccstatusline (and all manifest-based configs) undeployed +- **System template directory permissions** — `install.sh` now chowns `/usr/local/share/ccstatusline/` to the target user so `setup-config.sh` can write the template file during post-start +- **Silent copy failures** — `setup-config.sh` now reports warnings when file deployment fails instead of logging success after a failed `cp` + #### Post-Integration Review Fixes - **skill-engine** — worktree skill definition uses weighted tuples (was plain strings, caused crash) - **dangerous-command-blocker** — fail closed on unexpected exceptions (was fail-open) @@ -63,12 +149,42 @@ For minor and patch updates, you can usually just rebuild the container. Check t - **Documentation** — remove merge conflict marker from first-session.md - **Documentation** — update architecture.md directory tree with new plugins +#### CodeRabbit Review Fixes +- **`implementer.md`** — changed PostToolUse hook (fires every Edit) to Stop hook (fires once at task end) with 120s timeout; prevents redundant test runs during multi-file tasks +- **`tester.md`** — increased Stop hook timeout from 30s to 120s to accommodate larger test suites +- **`setup-aliases.sh`** — added `cc-orc` to `cc-tools` discovery loop so it appears in tool audit +- **`CLAUDE.md`** — added missing `keybindings.json`, `orchestrator-system-prompt.md`, and `writing-system-prompt.md` to directory structure tree +- **`agent-system/README.md`** — updated `verify-no-regression.py` comment to list both consumers (implementer, refactorer); hyphenated "question-surfacing protocol" +- **`orchestrator-system-prompt.md`** — clarified plan mode allows investigator delegation for research; added catch-all entry in selection criteria pointing to the full specialist catalog +- **MD040 compliance** — added `text` language specifiers to 7 fenced code blocks across `investigator.md`, `tester.md`, and `documenter.md` + ### Changed +#### Skill Engine: Auto-Suggestion +- **Weighted scoring** — Skill suggestion phrases now carry confidence weights (0.0–1.0) instead of binary match/no-match. Specific phrases like "build a fastapi app" score 1.0; ambiguous phrases like "start building" score 0.2 +- **Negative patterns** — Skills can define substrings that instantly disqualify them. Prevents `fastapi` from triggering when discussing `pydantic-ai`, and `docker` from triggering for `docker-py` prompts +- **Context guards** — Low-confidence matches (score < 0.6) require a confirming context word elsewhere in the prompt. "health check" only suggests `docker` if "docker", "container", or "compose" also appears +- **Ranked results, capped at 3** — Suggestions are sorted by score (then priority tier), and only the top 3 are returned. Eliminates 6+ skill suggestion floods +- **Priority tiers** — Explicit commands (priority 10) outrank technology skills (7), which outrank patterns (5) and generic skills (3) when scores tie + +#### Claude Code Installation +- **Claude Code now installs as a native binary** — uses Anthropic's official installer (`https://claude.ai/install.sh`) via new `./features/claude-code-native` feature, replacing the npm-based `ghcr.io/anthropics/devcontainer-features/claude-code:1.0.5` +- **In-session auto-updater now works without root** — native binary at `~/.local/bin/claude` is owned by the container user, so `claude update` succeeds without permission issues + +#### System Prompt +- **`` section** — Updated to document Claude Code native worktree convention (`/.claude/worktrees/`) as the recommended approach alongside the legacy `.worktrees/` convention. Added `EnterWorktree` tool guidance, `.worktreeinclude` file documentation, and path convention comparison table. + #### Configuration - Moved `.claude` directory from `/workspaces/.claude` to `~/.claude` (home directory) - Added Docker named volume for persistence across rebuilds (per-instance isolation via `${devcontainerId}`) - `CLAUDE_CONFIG_DIR` now defaults to `~/.claude` +- `file-manifest.json` — added deployment entry for `orchestrator-system-prompt.md` +- `setup-aliases.sh` — added `cc-orc` alias alongside existing `cc`, `claude`, `ccw`, `ccraw` +- `CLAUDE.md` — documented `cc-orc` command and orchestrator system prompt in key configuration table + +#### Agent System +- Agent count increased from 17 to 21 (4 workhorse + 17 specialist) +- Agent-system README updated with workhorse agent table, per-agent hooks for implementer and tester, and updated plugin structure #### Authentication - Added `CLAUDE_AUTH_TOKEN` support in `.secrets` for long-lived tokens from `claude setup-token` @@ -81,16 +197,45 @@ For minor and patch updates, you can usually just rebuild the container. Check t - Auth token value is now JSON-escaped before writing to `.credentials.json` - Credential directory created with restrictive umask (700) matching credential file permissions (600) +#### Status Bar +- **ccstatusline line 1** — distinct background colors for each token widget (blue=input, magenta=output, yellow=cached, green=total), bold 2-char labels (In, Ou, Ca, Tt) fused to data widgets, `rawValue: true` on model widget to strip "Model:" prefix, restored spacing between token segments + #### Scripts - Replaced `setup-symlink-claude.sh` with `setup-migrate-claude.sh` (one-time migration) - Auto-migrates from `/workspaces/.claude/` if `.credentials.json` present - `chown` in mcp-qdrant poststart hooks now uses resolved `_USERNAME` instead of hardcoded `vscode` or `$(id -un)` +- **Migration script hardened** — switched from `cp -rn` to `cp -a` (archive mode); added marker-based idempotency, critical file verification, ownership fixup, and old-directory rename +- **`.env` deprecation guard** — `setup.sh` detects stale `CLAUDE_CONFIG_DIR=/workspaces/.claude` in `.env`, overrides to `$HOME/.claude`, and auto-comments the line on disk #### Documentation - All docs now reference `~/.claude` as default config path - Added `CLAUDE_AUTH_TOKEN` setup flow to README, configuration reference, and troubleshooting - ccstatusline README verification commands now respect `CLAUDE_CONFIG_DIR` +### Fixed + +#### Claude Code Installation +- **Update script no longer silently discards errors** — background update output now captured to log file instead of being discarded via `&>/dev/null` +- **Update script simplified to native-binary-only** — removed npm fallback and `claude install` bootstrap code; added 60s timeout and transitional npm cleanup +- **Alias resolution simplified** — `_CLAUDE_BIN` now resolves directly to native binary path (removed npm and `/usr/local/bin` fallbacks) +- **POSIX redirect** — replaced `&>/dev/null` with `>/dev/null 2>&1` in dependency check for portability +- **Installer shell** — changed `sh -s` to `bash -s` when piping the official installer (it requires bash) +- **Unquoted `${TARGET}`** — quoted variable in `su -c` command to prevent word splitting +- **Directory prep** — added `~/.local/state` and `~/.claude` pre-creation; consolidated `chown` to cover entire `~/.local` tree + +#### Plugin Marketplace +- **`marketplace.json` schema fix** — changed all 11 plugin `source` fields from bare names (e.g., `"codeforge-lsp"`) to relative paths (`"./plugins/codeforge-lsp"`) so `claude plugin marketplace add` passes schema validation and all plugins register correctly + +#### ChromaTerm +- **Regex lookbehinds** — replaced alternation inside lookbehinds (`(?<=[\s(]|^)` and `(?<=commit |merge |...)`) with non-capturing groups containing individual lookbehinds (`(?:(?<=[\s(])|^)` and `(?:(?<=commit )|(?<=merge )|...)`) for PCRE2 compatibility + +#### Terminal Color Support +- **devcontainer.json** — added `TERM` and `COLORTERM=truecolor` to `remoteEnv`; Docker defaults to `TERM=xterm` (8 colors) which caused Claude Code and other CLI tools to downgrade rendering +- **devcontainer.json** — `TERM` uses `${localEnv:TERM:xterm-256color}` to forward the host terminal type (e.g., `xterm-kitty`) instead of unconditionally overriding it +- **setup-aliases.sh** — added terminal color defaults to managed shell block so tmux panes, `docker exec`, and SSH sessions also get 256-color and truecolor support +- **kitty-terminfo/README.md** — updated documentation to reflect `localEnv` forwarding and clarify behavior across VS Code vs non-VS Code entry points +- **CLAUDE.md** — documented `TERM` and `COLORTERM` environment variables in the Environment section + ### Removed #### Scripts @@ -99,12 +244,45 @@ For minor and patch updates, you can usually just rebuild the container. Check t #### VS Code Extensions - **Todo+** (`fabiospampinato.vscode-todo-plus`) — removed from devcontainer extensions ---- +## v2.0.0 + +**Release date:** 2026-02-26 + +### Added + +#### .codeforge/ User Customization Directory +- New `.codeforge/` directory centralizes all user-customizable configuration files +- Checksum-based modification detection preserves user changes during updates +- `codeforge config apply` CLI command deploys config files to `~/.claude/` (same as container start) +- Auto-migration from `.devcontainer/config/defaults/` to `.codeforge/config/` for existing users +- `.codeforge/.codeforge-preserve` for listing additional files to preserve during updates + +### Changed + +#### Configuration +- Config files moved from `.devcontainer/config/defaults/` to `.codeforge/config/` +- File manifest moved from `.devcontainer/config/file-manifest.json` to `.codeforge/file-manifest.json` +- Terminal connection scripts moved from `.devcontainer/` to `.codeforge/scripts/` +- `CONFIG_SOURCE_DIR` env var deprecated in favor of `CODEFORGE_DIR` +- `--force` updates now use checksum comparison for `.codeforge/` files (writes `.default` instead of `.codeforge-new`) +- `--reset` preserves `.codeforge/` user modifications (only `.devcontainer/` is wiped) + +#### Migration +- v2 migration marker moved to `.codeforge/.markers/v2-migrated` +- Container start auto-migrates `.devcontainer/config/defaults/` to `.codeforge/config/` if needed ## v1.14.2 **Release date:** 2026-02-24 +### Added + +#### Prompt Snippets Plugin +- **New plugin: `prompt-snippets`** — single `/ps` slash command for quick behavioral mode switches (noaction, brief, plan, go, review, ship, deep, hold, recall, wait) +- Snippets inject short directives that persist for the conversation (e.g., `/ps noaction` → "Investigate and report only. Take no action.") +- Composable: `/ps noaction brief` applies multiple snippets at once +- Isolated from skill-engine auto-suggestion (`disable-model-invocation: true`) and independently toggleable via `enabledPlugins` + ### Changed #### Docs diff --git a/docs/src/content/docs/reference/environment.md b/docs/src/content/docs/reference/environment.md index 5d8a559..0f82aa2 100644 --- a/docs/src/content/docs/reference/environment.md +++ b/docs/src/content/docs/reference/environment.md @@ -94,7 +94,7 @@ These variables live in `.devcontainer/.env` and control what `setup.sh` does on | Variable | Default | Description | |----------|---------|-------------| | `CLAUDE_CONFIG_DIR` | `/home/vscode/.claude` | Where Claude Code config files are stored | -| `CONFIG_SOURCE_DIR` | (auto-detected) | Source directory for config defaults | +| `CODEFORGE_DIR` | (auto-detected) | Source directory for user-customizable config files (`.codeforge/`) | | `SETUP_CONFIG` | `true` | Copy config files per `file-manifest.json` | | `SETUP_ALIASES` | `true` | Add `cc`/`claude`/`ccraw`/`cc-tools` aliases to shell | | `SETUP_AUTH` | `true` | Configure Git/NPM auth from `.secrets` file | diff --git a/docs/src/content/docs/reference/index.md b/docs/src/content/docs/reference/index.md index be7e748..3eb1858 100644 --- a/docs/src/content/docs/reference/index.md +++ b/docs/src/content/docs/reference/index.md @@ -32,9 +32,9 @@ This section is a lookup resource for CodeForge internals. Use it when you need | Path | Purpose | |------|---------| -| `.devcontainer/config/defaults/settings.json` | Primary configuration file | -| `.devcontainer/config/defaults/main-system-prompt.md` | Main system prompt | -| `.devcontainer/config/file-manifest.json` | Config file deployment rules | +| `.codeforge/config/settings.json` | Primary configuration file | +| `.codeforge/config/main-system-prompt.md` | Main system prompt | +| `.codeforge/file-manifest.json` | Config file deployment rules | | `.devcontainer/devcontainer.json` | Container definition | | `.devcontainer/plugins/devs-marketplace/` | Local plugin marketplace | | `.claude/rules/` | Active rules (deployed from defaults) | @@ -57,10 +57,10 @@ These are the files you will interact with most often when configuring CodeForge | File | Location | Purpose | |------|----------|---------| -| `settings.json` | `.devcontainer/config/defaults/` | Model selection, plugin toggles, env vars, permissions | -| `main-system-prompt.md` | `.devcontainer/config/defaults/` | Claude's coding behavior and response style | -| `writing-system-prompt.md` | `.devcontainer/config/defaults/` | Claude's writing mode behavior | -| `file-manifest.json` | `.devcontainer/config/` | Rules for deploying config files to `.claude/` | +| `settings.json` | `.codeforge/config/` | Model selection, plugin toggles, env vars, permissions | +| `main-system-prompt.md` | `.codeforge/config/` | Claude's coding behavior and response style | +| `writing-system-prompt.md` | `.codeforge/config/` | Claude's writing mode behavior | +| `file-manifest.json` | `.codeforge/` | Rules for deploying config files to `.claude/` | | `devcontainer.json` | `.devcontainer/` | Container image, features, mounts, resource limits | | `hooks.json` | Each plugin's `hooks/` directory | Hook registration for lifecycle automation | | `plugin.json` | Each plugin's `.claude-plugin/` directory | Plugin manifest and metadata | diff --git a/docs/src/content/docs/reference/troubleshooting.md b/docs/src/content/docs/reference/troubleshooting.md index 0dbee68..498d343 100644 --- a/docs/src/content/docs/reference/troubleshooting.md +++ b/docs/src/content/docs/reference/troubleshooting.md @@ -78,7 +78,7 @@ Any local feature can be disabled without removing it from `devcontainer.json` b **Problem: Plugin not loading or not appearing in Claude Code.** -- Check `enabledPlugins` in `.devcontainer/config/defaults/settings.json` — the plugin must be listed there. +- Check `enabledPlugins` in `.codeforge/config/settings.json` — the plugin must be listed there. - Verify the plugin directory exists under `plugins/devs-marketplace/plugins/`. - Run `check-setup` to verify core configuration is correct. - Check plugin blacklist: ensure it's not in `PLUGIN_BLACKLIST` in `.env`. @@ -95,7 +95,7 @@ Any local feature can be disabled without removing it from `devcontainer.json` b - Agent Teams requires tmux. Use the **"Claude Teams (tmux)"** terminal profile in VS Code. - Verify tmux is installed: `tmux -V`. -- If using an external terminal, connect via `connect-external-terminal.sh`. +- If using an external terminal, connect via `.codeforge/scripts/connect-external-terminal.sh`. **Problem: tmux Unicode/emoji rendering broken.** @@ -132,7 +132,7 @@ Any local feature can be disabled without removing it from `devcontainer.json` b ## How to Reset to Defaults -1. **Reset config files** — delete `~/.claude/` and restart the container. `setup-config.sh` will recopy all files from `config/defaults/`. +1. **Reset config files** — delete `~/.claude/` and restart the container. `setup-config.sh` will recopy all files from `.codeforge/config/`. Note that `--reset` preserves `.codeforge/` user modifications (only `.devcontainer/` is wiped). 2. **Reset aliases** — delete the `# Claude Code environment and aliases` block from `~/.bashrc` and `~/.zshrc`, then run `bash /workspaces/.devcontainer/scripts/setup-aliases.sh`. diff --git a/package.json b/package.json index d15d620..ce811ae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codeforge-dev", - "version": "1.14.2", + "version": "2.0.0", "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.", "main": "setup.js", "bin": { @@ -30,6 +30,7 @@ "license": "GPL-3.0", "files": [ ".devcontainer/**/*", + ".codeforge/**/*", "setup.js", "README.md" ], diff --git a/setup.js b/setup.js index da13926..93bcb42 100755 --- a/setup.js +++ b/setup.js @@ -4,18 +4,13 @@ const fs = require("node:fs"); const path = require("node:path"); +const crypto = require("node:crypto"); // ── Default preserve list ──────────────────────────────────────── -// Files in the package that should NOT overwrite user customizations. +// Files in .devcontainer that should NOT overwrite user customizations. // The package version is saved as .codeforge-new for diffing. -const DEFAULT_PRESERVE = [ - "config/defaults/settings.json", - "config/defaults/main-system-prompt.md", - "config/defaults/keybindings.json", - "config/defaults/ccstatusline-settings.json", - "config/file-manifest.json", - ".codeforge-preserve", -]; +// Note: .codeforge/ uses checksum-based preservation instead. +const DEFAULT_PRESERVE = [".codeforge-preserve"]; // ── copyDirectory ──────────────────────────────────────────────── // Simple recursive copy (used for fresh install and --reset). @@ -56,6 +51,154 @@ function loadPreserveList(devcontainerDest) { return new Set([...DEFAULT_PRESERVE, ...custom]); } +// ── computeChecksum ────────────────────────────────────────────── +// Returns SHA-256 hex digest of a file's contents. +function computeChecksum(filePath) { + return crypto + .createHash("sha256") + .update(fs.readFileSync(filePath)) + .digest("hex"); +} + +// ── generateChecksums ──────────────────────────────────────────── +// Walks directory recursively, returns { relativePath: sha256hex } map. +// Skips .checksums/ and .markers/ directories. +function generateChecksums(dir) { + const checksums = {}; + + function walk(currentDir, relativeBase) { + const entries = fs.readdirSync(currentDir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(currentDir, entry.name); + const relativePath = relativeBase + ? `${relativeBase}/${entry.name}` + : entry.name; + + if (entry.isDirectory()) { + if (entry.name === ".checksums" || entry.name === ".markers") { + continue; + } + walk(fullPath, relativePath); + } else { + checksums[relativePath] = computeChecksum(fullPath); + } + } + } + + walk(dir, ""); + return checksums; +} + +// ── writeChecksums ─────────────────────────────────────────────── +// Writes .checksums/.json with version, timestamp, and file hashes. +function writeChecksums(codeforgeDir, version, checksums) { + const checksumsDir = path.join(codeforgeDir, ".checksums"); + fs.mkdirSync(checksumsDir, { recursive: true }); + const data = { + version, + generated: new Date().toISOString(), + files: checksums, + }; + fs.writeFileSync( + path.join(checksumsDir, `${version}.json`), + JSON.stringify(data, null, "\t") + "\n", + ); +} + +// ── readChecksums ──────────────────────────────────────────────── +// Reads latest version's checksums from .checksums/ dir. +// Returns { files: {} } if none found. +function readChecksums(codeforgeDir) { + const checksumsDir = path.join(codeforgeDir, ".checksums"); + if (!fs.existsSync(checksumsDir)) { + return { files: {} }; + } + + const files = fs + .readdirSync(checksumsDir) + .filter((f) => f.endsWith(".json")) + .sort(); + + if (files.length === 0) { + return { files: {} }; + } + + const latest = files[files.length - 1]; + return JSON.parse(fs.readFileSync(path.join(checksumsDir, latest), "utf-8")); +} + +// ── syncCodeforgeDirectory ─────────────────────────────────────── +// Checksum-aware sync for .codeforge/ directory. +// Unmodified files get overwritten; modified files are preserved +// and new defaults are written as .default. +function syncCodeforgeDirectory(src, dest) { + const stored = readChecksums(dest); + const stats = { + updated: 0, + preserved: 0, + added: 0, + preservedFiles: [], + defaultFiles: [], + }; + + function walk(srcDir, destDir, relativeBase) { + if (!fs.existsSync(destDir)) { + fs.mkdirSync(destDir, { recursive: true }); + } + + const entries = fs.readdirSync(srcDir, { withFileTypes: true }); + + for (const entry of entries) { + const srcPath = path.join(srcDir, entry.name); + const destPath = path.join(destDir, entry.name); + const relativePath = relativeBase + ? `${relativeBase}/${entry.name}` + : entry.name; + + if (entry.isDirectory()) { + if (entry.name === ".checksums" || entry.name === ".markers") { + continue; + } + walk(srcPath, destPath, relativePath); + continue; + } + + const storedHash = stored.files[relativePath]; + const currentHash = fs.existsSync(destPath) + ? computeChecksum(destPath) + : null; + + if (!storedHash) { + // First install or new file + if (currentHash === null) { + fs.copyFileSync(srcPath, destPath); + stats.added++; + } else { + // File exists but no stored hash — treat as user-created + fs.copyFileSync(srcPath, `${destPath}.default`); + stats.preserved++; + stats.preservedFiles.push(relativePath); + stats.defaultFiles.push(relativePath); + } + } else if (currentHash === storedHash) { + // File UNMODIFIED — overwrite with new version + fs.copyFileSync(srcPath, destPath); + stats.updated++; + } else { + // File USER-MODIFIED — keep user's file, write new as .default + fs.copyFileSync(srcPath, `${destPath}.default`); + stats.preserved++; + stats.preservedFiles.push(relativePath); + stats.defaultFiles.push(relativePath); + } + } + } + + walk(src, dest, ""); + return stats; +} + // ── syncDirectory ──────────────────────────────────────────────── // Selective overwrite: walks the package tree and copies files to dest. // - Framework files (scripts, features, plugins): always overwrite @@ -125,20 +268,33 @@ function syncDirectory(src, dest, preserveSet) { // ── main ───────────────────────────────────────────────────────── function main() { const args = process.argv.slice(2); + + // Subcommand: config apply + if (args[0] === "config" && args[1] === "apply") { + return configApply(); + } + const force = args.includes("--force") || args.includes("-f"); const reset = args.includes("--reset"); if (args.includes("--help") || args.includes("-h")) { console.log("Usage: codeforge [options]"); + console.log(" codeforge config apply"); console.log(""); console.log("Options:"); console.log( - " --force, -f Update existing .devcontainer (preserves user config)", + " --force, -f Update existing .devcontainer and .codeforge (preserves user config)", ); console.log( - " --reset Remove all customizations and install fresh defaults", + " --reset Remove .devcontainer customizations and install fresh defaults", + ); + console.log(" (.codeforge user modifications preserved)"); + console.log(" --help, -h Show this help message"); + console.log(""); + console.log("Subcommands:"); + console.log( + " config apply Deploy .codeforge/config/ files to ~/.claude/", ); - console.log(" --help, -h Show this help message"); console.log(""); console.log( "Without flags, installs only if .devcontainer does not exist.", @@ -150,6 +306,11 @@ function main() { const packageDir = __dirname; const devcontainerSrc = path.join(packageDir, ".devcontainer"); const devcontainerDest = path.join(currentDir, ".devcontainer"); + const codeforgeSrc = path.join(packageDir, ".codeforge"); + const codeforgeDest = path.join(currentDir, ".codeforge"); + const packageVersion = JSON.parse( + fs.readFileSync(path.join(packageDir, "package.json"), "utf8"), + ).version; console.log(""); @@ -163,12 +324,44 @@ function main() { if (fs.existsSync(devcontainerDest)) { if (reset) { - // Nuclear: delete everything and copy fresh + // Nuclear: delete .devcontainer and copy fresh console.log("Resetting .devcontainer to package defaults..."); console.log(""); fs.rmSync(devcontainerDest, { recursive: true, force: true }); copyDirectory(devcontainerSrc, devcontainerDest); - console.log(" Reset complete. All user customizations removed."); + console.log( + " Reset complete. All .devcontainer customizations removed.", + ); + + // .codeforge uses checksum-based preservation (not wipe) + if (fs.existsSync(codeforgeSrc)) { + if (fs.existsSync(codeforgeDest)) { + const codeforgeStats = syncCodeforgeDirectory( + codeforgeSrc, + codeforgeDest, + ); + console.log(" .codeforge/ user modifications preserved."); + console.log(` Updated: ${codeforgeStats.updated} files`); + console.log(` Added: ${codeforgeStats.added} new files`); + console.log( + ` Preserved: ${codeforgeStats.preserved} user config files`, + ); + if (codeforgeStats.defaultFiles.length > 0) { + console.log(""); + console.log( + " Review .default files for new defaults you may want to merge:", + ); + for (const f of codeforgeStats.defaultFiles) { + console.log(` ${f}.default`); + } + } + } else { + copyDirectory(codeforgeSrc, codeforgeDest); + } + const newChecksums = generateChecksums(codeforgeSrc); + writeChecksums(codeforgeDest, packageVersion, newChecksums); + } + console.log(""); printNextSteps(); } else if (force) { @@ -206,6 +399,34 @@ function main() { console.log(""); } + // .codeforge sync with checksum-based preservation + if (fs.existsSync(codeforgeSrc)) { + const codeforgeStats = syncCodeforgeDirectory( + codeforgeSrc, + codeforgeDest, + ); + const newChecksums = generateChecksums(codeforgeSrc); + writeChecksums(codeforgeDest, packageVersion, newChecksums); + + console.log(" .codeforge/ update:"); + console.log(` Updated: ${codeforgeStats.updated} files`); + console.log(` Added: ${codeforgeStats.added} new files`); + console.log( + ` Preserved: ${codeforgeStats.preserved} user config files`, + ); + + if (codeforgeStats.defaultFiles.length > 0) { + console.log(""); + console.log( + " Review .default files for new defaults you may want to merge:", + ); + for (const f of codeforgeStats.defaultFiles) { + console.log(` ${f}.default`); + } + } + console.log(""); + } + printNextSteps(); } else { // No flags: error with guidance @@ -223,6 +444,13 @@ function main() { try { copyDirectory(devcontainerSrc, devcontainerDest); + + if (fs.existsSync(codeforgeSrc)) { + copyDirectory(codeforgeSrc, codeforgeDest); + const checksums = generateChecksums(codeforgeDest); + writeChecksums(codeforgeDest, packageVersion, checksums); + } + console.log(" CodeForge DevContainer configuration installed!"); console.log(""); printNextSteps(); @@ -234,13 +462,90 @@ function main() { } } +// ── configApply ────────────────────────────────────────────────── +// Deploys .codeforge/config/ files to ~/.claude/ using file-manifest.json. +function configApply() { + const codeforgeDir = + process.env.CODEFORGE_DIR || path.join(process.cwd(), ".codeforge"); + const manifest = path.join(codeforgeDir, "file-manifest.json"); + + if (!fs.existsSync(manifest)) { + console.error("Error: file-manifest.json not found at " + manifest); + console.error("Are you in a CodeForge project directory?"); + process.exit(1); + } + + const entries = JSON.parse(fs.readFileSync(manifest, "utf-8")); + const claudeConfigDir = + process.env.CLAUDE_CONFIG_DIR || + path.join(process.env.HOME || "/home/vscode", ".claude"); + const workspaceRoot = process.env.WORKSPACE_ROOT || process.cwd(); + + function expandVars(val) { + return val + .replace("${CLAUDE_CONFIG_DIR}", claudeConfigDir) + .replace("${WORKSPACE_ROOT}", workspaceRoot) + .replace("${HOME}", process.env.HOME || "/home/vscode"); + } + + console.log(""); + console.log("Applying .codeforge/config/ to Claude configuration..."); + console.log(""); + + let deployed = 0; + let skipped = 0; + + for (const entry of entries) { + if (entry.enabled === false) { + skipped++; + continue; + } + + const srcPath = path.join(codeforgeDir, entry.src); + if (!fs.existsSync(srcPath)) { + console.log(" Skip: " + entry.src + " (not found)"); + skipped++; + continue; + } + + const destDir = expandVars(entry.dest); + const filename = entry.destFilename || path.basename(entry.src); + const destPath = path.join(destDir, filename); + fs.mkdirSync(destDir, { recursive: true }); + + if (entry.overwrite === "never" && fs.existsSync(destPath)) { + console.log(" Skip: " + filename + " (exists, overwrite=never)"); + skipped++; + continue; + } + + if (entry.overwrite === "if-changed" && fs.existsSync(destPath)) { + const srcHash = computeChecksum(srcPath); + const destHash = computeChecksum(destPath); + if (srcHash === destHash) { + skipped++; + continue; + } + } + + fs.copyFileSync(srcPath, destPath); + console.log(" Deployed: " + entry.src + " → " + destPath); + deployed++; + } + + console.log(""); + console.log( + "Config apply complete: " + deployed + " deployed, " + skipped + " skipped", + ); +} + function printNextSteps() { console.log("Next steps:"); console.log(" 1. Open this folder in VS Code"); console.log(' 2. Select "Reopen in Container" from the command palette'); console.log(" 3. Run: claude"); console.log(""); - console.log("Documentation: .devcontainer/README.md"); + console.log("Documentation: .devcontainer/README.md and .codeforge/"); console.log(""); } @@ -257,4 +562,12 @@ if (require.main === module) { main(); } -module.exports = { copyDirectory, syncDirectory, loadPreserveList, main }; +module.exports = { + copyDirectory, + syncDirectory, + syncCodeforgeDirectory, + loadPreserveList, + computeChecksum, + generateChecksums, + main, +}; diff --git a/test.js b/test.js index 48a671b..18c60c9 100644 --- a/test.js +++ b/test.js @@ -4,7 +4,13 @@ const fs = require("node:fs"); const path = require("node:path"); -const { copyDirectory, main } = require("./setup.js"); +const { + copyDirectory, + computeChecksum, + generateChecksums, + syncCodeforgeDirectory, + main, +} = require("./setup.js"); function runTests() { console.log("🧪 Running CodeForge package tests...\n"); @@ -22,7 +28,8 @@ function runTests() { "README.md", ".devcontainer/devcontainer.json", ".devcontainer/scripts/setup.sh", - ".devcontainer/config/defaults/settings.json", + ".codeforge/config/settings.json", + ".codeforge/file-manifest.json", ]; let allFilesExist = true; @@ -69,9 +76,68 @@ function runTests() { setupExecutable = false; } + // Test 6: New checksum and sync functions exist + let checksumFunctionsExist = true; + if (typeof computeChecksum === "function") { + console.log("✓ Test 6.1: computeChecksum function exists"); + } else { + console.log("❌ Test 6.1: computeChecksum function missing"); + checksumFunctionsExist = false; + } + if (typeof generateChecksums === "function") { + console.log("✓ Test 6.2: generateChecksums function exists"); + } else { + console.log("❌ Test 6.2: generateChecksums function missing"); + checksumFunctionsExist = false; + } + if (typeof syncCodeforgeDirectory === "function") { + console.log("✓ Test 6.3: syncCodeforgeDirectory function exists"); + } else { + console.log("❌ Test 6.3: syncCodeforgeDirectory function missing"); + checksumFunctionsExist = false; + } + + // Test 7: generateChecksums produces expected structure + let checksumStructureValid = true; + const codeforgeDir = path.join(__dirname, ".codeforge"); + if (fs.existsSync(codeforgeDir)) { + const checksums = generateChecksums(codeforgeDir); + if (typeof checksums === "object" && checksums !== null) { + const keys = Object.keys(checksums); + if (keys.length > 0) { + const firstValue = checksums[keys[0]]; + if (typeof firstValue === "string" && firstValue.length === 64) { + console.log( + "✓ Test 7: generateChecksums returns valid SHA-256 hex map", + ); + } else { + console.log( + "❌ Test 7: generateChecksums values are not SHA-256 hex strings", + ); + checksumStructureValid = false; + } + } else { + console.log("❌ Test 7: generateChecksums returned empty map"); + checksumStructureValid = false; + } + } else { + console.log("❌ Test 7: generateChecksums did not return an object"); + checksumStructureValid = false; + } + } else { + console.log("❌ Test 7: .codeforge directory not found, skipping"); + checksumStructureValid = false; + } + // Summary console.log("\n📊 Test Results:"); - if (allFilesExist && packageValid && setupExecutable) { + if ( + allFilesExist && + packageValid && + setupExecutable && + checksumFunctionsExist && + checksumStructureValid + ) { console.log("🎉 All tests passed! Package is ready for distribution."); process.exit(0); } else { From c064d10b2e7e5442028c62bccd584dffd5a63dd5 Mon Sep 17 00:00:00 2001 From: AnExiledDev Date: Fri, 27 Feb 2026 00:12:27 +0000 Subject: [PATCH 2/6] Fix review issues: corrupted checksums, var ordering, stale links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review fixes from 4-agent code review: - setup.js: Handle corrupted checksums JSON gracefully (try/catch) - setup.js: Generate checksums from source, not destination on fresh install - setup.js: Use regex with /g flag in expandVars for multiple occurrences - setup.sh: Fix variable ordering — set CODEFORGE_DIR before CONFIG_SOURCE_DIR - setup.sh: Unset CONFIG_SOURCE_DIR after deprecation guard fires - .gitignore: Remove duplicate .devcontainer/ un-ignore and stale config paths - architecture.md: Add missing orchestrator-system-prompt.md and migration step - README.md: Fix dead links to removed docs/ directory, point to docs site --- .devcontainer/README.md | 12 +++++------ .devcontainer/scripts/setup.sh | 3 ++- .gitignore | 4 ---- .../content/docs/reference/architecture.md | 18 +++++++++------- setup.js | 21 ++++++++++++++----- 5 files changed, 34 insertions(+), 24 deletions(-) diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 9483cf2..1cec96d 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -432,7 +432,7 @@ The `setup-projects.sh` script auto-detects projects under `/workspaces/` and ma ## Troubleshooting -Common issues and solutions. For detailed troubleshooting, see [docs/troubleshooting.md](docs/troubleshooting.md). +Common issues and solutions. For detailed troubleshooting, see the [Troubleshooting](https://anexileddev.github.io/CodeForge/reference/troubleshooting/) page on the docs site. | Problem | Solution | |---------|----------| @@ -447,11 +447,11 @@ Common issues and solutions. For detailed troubleshooting, see [docs/troubleshoo ## Further Reading **CodeForge Documentation**: -- [Configuration Reference](docs/configuration-reference.md) — all env vars and config options -- [Plugin System](docs/plugins.md) — plugin architecture and per-plugin docs -- [Optional Features](docs/optional-features.md) — mcp-qdrant and other optional components, disabling features -- [Keybinding Customization](docs/keybindings.md) — resolving VS Code conflicts -- [Troubleshooting](docs/troubleshooting.md) — common issues and solutions +- [Configuration Reference](https://anexileddev.github.io/CodeForge/customization/configuration/) — all env vars and config options +- [Plugin System](https://anexileddev.github.io/CodeForge/plugins/) — plugin architecture and per-plugin docs +- [Optional Features](https://anexileddev.github.io/CodeForge/customization/optional-features/) — mcp-qdrant and other optional components, disabling features +- [Keybinding Customization](https://anexileddev.github.io/CodeForge/customization/keybindings/) — resolving VS Code conflicts +- [Troubleshooting](https://anexileddev.github.io/CodeForge/reference/troubleshooting/) — common issues and solutions **External**: - [Claude Code Documentation](https://docs.anthropic.com/en/docs/claude-code) diff --git a/.devcontainer/scripts/setup.sh b/.devcontainer/scripts/setup.sh index 135273c..18510e1 100755 --- a/.devcontainer/scripts/setup.sh +++ b/.devcontainer/scripts/setup.sh @@ -47,6 +47,7 @@ if [ "$CONFIG_SOURCE_DIR" = "$DEVCONTAINER_DIR/config" ] || [ "$CONFIG_SOURCE_DI echo "[setup] WARNING: CONFIG_SOURCE_DIR pointing to .devcontainer/config is deprecated (moved to .codeforge in v2.0)" echo "[setup] Redirecting to .codeforge." CODEFORGE_DIR="${WORKSPACE_ROOT:?}/.codeforge" + unset CONFIG_SOURCE_DIR if [ -f "$ENV_FILE" ]; then sed -i 's|^CONFIG_SOURCE_DIR=.*\.devcontainer/config.*|# CONFIG_SOURCE_DIR removed (v2.0: now uses .codeforge)|' "$ENV_FILE" echo "[setup] .env updated — CONFIG_SOURCE_DIR line commented out." @@ -55,8 +56,8 @@ fi # Apply defaults for any unset variables : "${CLAUDE_CONFIG_DIR:=$HOME/.claude}" -: "${CONFIG_SOURCE_DIR:=$DEVCONTAINER_DIR/config}" : "${CODEFORGE_DIR:=${WORKSPACE_ROOT:?}/.codeforge}" +: "${CONFIG_SOURCE_DIR:=$CODEFORGE_DIR}" : "${SETUP_CONFIG:=true}" : "${SETUP_ALIASES:=true}" : "${SETUP_AUTH:=true}" diff --git a/.gitignore b/.gitignore index ace9696..4531a1c 100644 --- a/.gitignore +++ b/.gitignore @@ -67,8 +67,6 @@ node_modules/ .* !.devcontainer/ !.devcontainer/**/.claude-plugin/ -!.devcontainer/ -!.devcontainer/**/.claude-plugin/**/.claude-plugin/ !.codeforge/ !.git/ !.github/ @@ -81,8 +79,6 @@ node_modules/ .codeforge/.markers/ # Un-ignore .codeforge-preserve (user-editable, should be tracked) !.codeforge/.codeforge-preserve -.devcontainer/config/claude/output-styles/strict-development.md -.devcontainer/config/claude/mcp.json # Docs docs/node_modules/ diff --git a/docs/src/content/docs/reference/architecture.md b/docs/src/content/docs/reference/architecture.md index 8f11d78..ca80ce6 100644 --- a/docs/src/content/docs/reference/architecture.md +++ b/docs/src/content/docs/reference/architecture.md @@ -161,6 +161,7 @@ CodeForge ships 38 skills across the skill-engine, spec-workflow, ticket-workflo | +-- settings.json # Claude Code settings (model, plugins, env vars) | +-- keybindings.json # Keyboard shortcuts | +-- main-system-prompt.md # Development system prompt +| +-- orchestrator-system-prompt.md # Orchestrator mode system prompt | +-- writing-system-prompt.md # Writing mode system prompt | +-- rules/ # Default rules deployed to .claude/rules/ | +-- spec-workflow.md @@ -206,14 +207,15 @@ devcontainer.json | v [postStartCommand] setup.sh orchestrates: - 1. setup-migrate-claude.sh -- Migrate Claude config from old location - 2. setup-auth.sh -- Git/NPM authentication - 3. setup-config.sh -- Deploy settings, prompts, rules via file-manifest.json - 4. setup-aliases.sh -- Write shell aliases to .bashrc/.zshrc - 5. setup-plugins.sh -- Sync plugins from marketplace - 6. setup-projects.sh -- Detect projects, configure Project Manager - 7. setup-terminal.sh -- Configure terminal settings - 8. setup-update-claude.sh -- Update Claude Code if needed (background) + 1. setup-migrate-claude.sh -- Migrate Claude config from old location + 2. setup-migrate-codeforge.sh -- Migrate legacy config to .codeforge/ + 3. setup-auth.sh -- Git/NPM authentication + 4. setup-config.sh -- Deploy settings, prompts, rules via file-manifest.json + 5. setup-aliases.sh -- Write shell aliases to .bashrc/.zshrc + 6. setup-plugins.sh -- Sync plugins from marketplace + 7. setup-projects.sh -- Detect projects, configure Project Manager + 8. setup-terminal.sh -- Configure terminal settings + 9. setup-update-claude.sh -- Update Claude Code if needed (background) ``` ### Session Lifecycle diff --git a/setup.js b/setup.js index 93bcb42..36575a0 100755 --- a/setup.js +++ b/setup.js @@ -125,7 +125,18 @@ function readChecksums(codeforgeDir) { } const latest = files[files.length - 1]; - return JSON.parse(fs.readFileSync(path.join(checksumsDir, latest), "utf-8")); + try { + return JSON.parse( + fs.readFileSync(path.join(checksumsDir, latest), "utf-8"), + ); + } catch { + console.log( + " Warning: Could not read checksums from " + + latest + + ", treating as fresh install.", + ); + return { files: {} }; + } } // ── syncCodeforgeDirectory ─────────────────────────────────────── @@ -447,7 +458,7 @@ function main() { if (fs.existsSync(codeforgeSrc)) { copyDirectory(codeforgeSrc, codeforgeDest); - const checksums = generateChecksums(codeforgeDest); + const checksums = generateChecksums(codeforgeSrc); writeChecksums(codeforgeDest, packageVersion, checksums); } @@ -483,9 +494,9 @@ function configApply() { function expandVars(val) { return val - .replace("${CLAUDE_CONFIG_DIR}", claudeConfigDir) - .replace("${WORKSPACE_ROOT}", workspaceRoot) - .replace("${HOME}", process.env.HOME || "/home/vscode"); + .replace(/\$\{CLAUDE_CONFIG_DIR\}/g, claudeConfigDir) + .replace(/\$\{WORKSPACE_ROOT\}/g, workspaceRoot) + .replace(/\$\{HOME\}/g, process.env.HOME || "/home/vscode"); } console.log(""); From a58eec6ae34cf1e931d9107ba2b672201c109905 Mon Sep 17 00:00:00 2001 From: AnExiledDev Date: Fri, 27 Feb 2026 00:16:39 +0000 Subject: [PATCH 3/6] Fix semver sort in readChecksums and validate overwrite field - readChecksums: Sort version filenames by numeric semver components instead of lexicographic order (1.10.0 now correctly sorts after 1.9.0) - configApply: Validate overwrite field against known values (always, if-changed, never) and warn on unrecognized values --- setup.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/setup.js b/setup.js index 36575a0..ceacc57 100755 --- a/setup.js +++ b/setup.js @@ -118,7 +118,15 @@ function readChecksums(codeforgeDir) { const files = fs .readdirSync(checksumsDir) .filter((f) => f.endsWith(".json")) - .sort(); + .sort((a, b) => { + const pa = a.replace(".json", "").split(".").map(Number); + const pb = b.replace(".json", "").split(".").map(Number); + for (let i = 0; i < Math.max(pa.length, pb.length); i++) { + const diff = (pa[i] || 0) - (pb[i] || 0); + if (diff !== 0) return diff; + } + return 0; + }); if (files.length === 0) { return { files: {} }; @@ -506,12 +514,24 @@ function configApply() { let deployed = 0; let skipped = 0; + const validOverwrite = ["always", "if-changed", "never"]; + for (const entry of entries) { if (entry.enabled === false) { skipped++; continue; } + if (entry.overwrite && !validOverwrite.includes(entry.overwrite)) { + console.log( + ' Warning: Unknown overwrite value "' + + entry.overwrite + + '" for ' + + entry.src + + ', defaulting to "always"', + ); + } + const srcPath = path.join(codeforgeDir, entry.src); if (!fs.existsSync(srcPath)) { console.log(" Skip: " + entry.src + " (not found)"); From c5172e81c2fa6afc69cc590f411254565f1fe719 Mon Sep 17 00:00:00 2001 From: AnExiledDev Date: Fri, 27 Feb 2026 00:19:37 +0000 Subject: [PATCH 4/6] Remove dead .codeforge-preserve file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The checksum system in syncCodeforgeDirectory automatically preserves user-modified files. This file was never read by any code — removing to avoid implying functionality that doesn't exist. --- .codeforge/.codeforge-preserve | 11 ----------- .gitignore | 2 -- 2 files changed, 13 deletions(-) delete mode 100644 .codeforge/.codeforge-preserve diff --git a/.codeforge/.codeforge-preserve b/.codeforge/.codeforge-preserve deleted file mode 100644 index af1d1c6..0000000 --- a/.codeforge/.codeforge-preserve +++ /dev/null @@ -1,11 +0,0 @@ -# .codeforge-preserve -# List additional files to preserve during --force updates. -# One file path per line, relative to .codeforge/. -# Lines starting with # are comments. -# -# Default preserved files (built-in, no need to list here): -# config/settings.json -# config/main-system-prompt.md -# config/keybindings.json -# config/ccstatusline-settings.json -# file-manifest.json diff --git a/.gitignore b/.gitignore index 4531a1c..14980e1 100644 --- a/.gitignore +++ b/.gitignore @@ -77,8 +77,6 @@ node_modules/ # .codeforge per-installation state (not tracked) .codeforge/.checksums/ .codeforge/.markers/ -# Un-ignore .codeforge-preserve (user-editable, should be tracked) -!.codeforge/.codeforge-preserve # Docs docs/node_modules/ From 540a42de2aa355f6fa34a1bb8260e405f8b8d89c Mon Sep 17 00:00:00 2001 From: AnExiledDev Date: Fri, 27 Feb 2026 00:26:31 +0000 Subject: [PATCH 5/6] Fix CodeRabbit review issues: path traversal, URLs, docs, setup.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add path traversal validation in configApply() for source and destination paths in file-manifest.json - Use default-assignment for CODEFORGE_DIR in deprecation guard - Replace old GitHub Pages URLs with custom domain (codeforge.core-directive.com) - Add .checksums/ and .markers/ to architecture docs tree - Clarify "How to Reset" section in troubleshooting docs - Fix "merged" → "migrated content from" in changelog wording --- .devcontainer/CHANGELOG.md | 5 ++++ .devcontainer/CLAUDE.md | 2 +- .devcontainer/README.md | 12 ++++----- .devcontainer/scripts/setup.sh | 2 +- README.md | 4 +-- .../content/docs/reference/architecture.md | 2 ++ .../content/docs/reference/troubleshooting.md | 12 +++++---- setup.js | 27 +++++++++++++++++-- 8 files changed, 49 insertions(+), 17 deletions(-) diff --git a/.devcontainer/CHANGELOG.md b/.devcontainer/CHANGELOG.md index 4ea3055..bf46240 100644 --- a/.devcontainer/CHANGELOG.md +++ b/.devcontainer/CHANGELOG.md @@ -110,6 +110,11 @@ - **`agent-system/README.md`** — updated `verify-no-regression.py` comment to list both consumers (implementer, refactorer); hyphenated "question-surfacing protocol" - **`orchestrator-system-prompt.md`** — clarified plan mode allows investigator delegation for research; added catch-all entry in selection criteria pointing to the full specialist catalog - **MD040 compliance** — added `text` language specifiers to 7 fenced code blocks across `investigator.md`, `tester.md`, and `documenter.md` +- **`setup.js` path traversal** — `configApply()` now validates that source paths resolve within `.codeforge/` and destination paths resolve within allowed directories (`CLAUDE_CONFIG_DIR`, `HOME`, `/usr/local/`), preventing directory traversal via `../` in manifest entries +- **`setup.sh` CODEFORGE_DIR** — deprecation guard now uses default-assignment semantics (`:=`) instead of unconditional overwrite, preserving any user-defined `CODEFORGE_DIR` from `.env` +- **Docs site URLs** — replaced `anexileddev.github.io/CodeForge/` with custom domain `codeforge.core-directive.com/` across README.md, CLAUDE.md, and .devcontainer/README.md +- **Architecture docs** — added `.checksums/` and `.markers/` directories to the `.codeforge/` tree in architecture.md +- **Troubleshooting docs** — renamed "Reset to Defaults" to "How to Reset" and clarified that `--reset` preserves `.codeforge/` user modifications; added step for restoring default config sources ### Changed diff --git a/.devcontainer/CLAUDE.md b/.devcontainer/CLAUDE.md index b3e7920..5685a2e 100644 --- a/.devcontainer/CLAUDE.md +++ b/.devcontainer/CLAUDE.md @@ -201,4 +201,4 @@ Three mechanisms handle port access depending on your client: VS Code auto-detects all ports opened inside the container (configured via `portsAttributes` in `devcontainer.json`). Outside VS Code, `dbr` provides dynamic port discovery via a reverse connection model (container→host). The container daemon auto-starts and is inert without the host daemon (`dbr host-daemon`). Both mechanisms coexist. See [devcontainer-bridge](https://github.com/bradleybeddoes/devcontainer-bridge). -For full setup instructions, see the [Port Forwarding reference](https://anexileddev.github.io/CodeForge/reference/port-forwarding/) in the docs. +For full setup instructions, see the [Port Forwarding reference](https://codeforge.core-directive.com/reference/port-forwarding/) in the docs. diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 1cec96d..230fdd9 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -432,7 +432,7 @@ The `setup-projects.sh` script auto-detects projects under `/workspaces/` and ma ## Troubleshooting -Common issues and solutions. For detailed troubleshooting, see the [Troubleshooting](https://anexileddev.github.io/CodeForge/reference/troubleshooting/) page on the docs site. +Common issues and solutions. For detailed troubleshooting, see the [Troubleshooting](https://codeforge.core-directive.com/reference/troubleshooting/) page on the docs site. | Problem | Solution | |---------|----------| @@ -447,11 +447,11 @@ Common issues and solutions. For detailed troubleshooting, see the [Troubleshoot ## Further Reading **CodeForge Documentation**: -- [Configuration Reference](https://anexileddev.github.io/CodeForge/customization/configuration/) — all env vars and config options -- [Plugin System](https://anexileddev.github.io/CodeForge/plugins/) — plugin architecture and per-plugin docs -- [Optional Features](https://anexileddev.github.io/CodeForge/customization/optional-features/) — mcp-qdrant and other optional components, disabling features -- [Keybinding Customization](https://anexileddev.github.io/CodeForge/customization/keybindings/) — resolving VS Code conflicts -- [Troubleshooting](https://anexileddev.github.io/CodeForge/reference/troubleshooting/) — common issues and solutions +- [Configuration Reference](https://codeforge.core-directive.com/customization/configuration/) — all env vars and config options +- [Plugin System](https://codeforge.core-directive.com/plugins/) — plugin architecture and per-plugin docs +- [Optional Features](https://codeforge.core-directive.com/customization/optional-features/) — mcp-qdrant and other optional components, disabling features +- [Keybinding Customization](https://codeforge.core-directive.com/customization/keybindings/) — resolving VS Code conflicts +- [Troubleshooting](https://codeforge.core-directive.com/reference/troubleshooting/) — common issues and solutions **External**: - [Claude Code Documentation](https://docs.anthropic.com/en/docs/claude-code) diff --git a/.devcontainer/scripts/setup.sh b/.devcontainer/scripts/setup.sh index 18510e1..2e64bd9 100755 --- a/.devcontainer/scripts/setup.sh +++ b/.devcontainer/scripts/setup.sh @@ -46,7 +46,7 @@ fi if [ "$CONFIG_SOURCE_DIR" = "$DEVCONTAINER_DIR/config" ] || [ "$CONFIG_SOURCE_DIR" = "/workspaces/.devcontainer/config" ]; then echo "[setup] WARNING: CONFIG_SOURCE_DIR pointing to .devcontainer/config is deprecated (moved to .codeforge in v2.0)" echo "[setup] Redirecting to .codeforge." - CODEFORGE_DIR="${WORKSPACE_ROOT:?}/.codeforge" + : "${CODEFORGE_DIR:=${WORKSPACE_ROOT:?}/.codeforge}" unset CONFIG_SOURCE_DIR if [ -f "$ENV_FILE" ]; then sed -i 's|^CONFIG_SOURCE_DIR=.*\.devcontainer/config.*|# CONFIG_SOURCE_DIR removed (v2.0: now uses .codeforge)|' "$ENV_FILE" diff --git a/README.md b/README.md index c2ae4b3..de1013c 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ CodeForge operates in three layers, each building on the one below: **Claude Code** — The AI assistant, executing tools and coordinating work. CodeForge enhances it through configuration — replacing built-in subagents, adding safety guardrails, and wiring up quality checks that run automatically. -For the full architecture breakdown — hook pipeline, agent routing, skill loading, and design principles — see the [Architecture Reference](https://anexileddev.github.io/CodeForge/reference/architecture/). +For the full architecture breakdown — hook pipeline, agent routing, skill loading, and design principles — see the [Architecture Reference](https://codeforge.core-directive.com/reference/architecture/). ## Configuration @@ -127,7 +127,7 @@ All configuration lives in `.devcontainer/` and deploys automatically on contain Config files use SHA-256 change detection — your edits persist across container rebuilds unless the source changes. Set a file's overwrite mode to `"never"` in `file-manifest.json` to permanently preserve your customizations. -For the complete configuration guide, see the [documentation site](https://anexileddev.github.io/CodeForge/customization/configuration/). +For the complete configuration guide, see the [documentation site](https://codeforge.core-directive.com/customization/configuration/). ## Quick Start diff --git a/docs/src/content/docs/reference/architecture.md b/docs/src/content/docs/reference/architecture.md index ca80ce6..ac114cf 100644 --- a/docs/src/content/docs/reference/architecture.md +++ b/docs/src/content/docs/reference/architecture.md @@ -171,6 +171,8 @@ CodeForge ships 38 skills across the skill-engine, spec-workflow, ticket-workflo | +-- connect-external-terminal.sh # External terminal connection (Linux/macOS) | +-- connect-external-terminal.ps1 # External terminal connection (Windows) +-- .codeforge-preserve # Lists additional files to preserve during updates ++-- .checksums/ # File hashes for change detection (gitignored) ++-- .markers/ # Migration state markers (gitignored) ``` ## Design Principles diff --git a/docs/src/content/docs/reference/troubleshooting.md b/docs/src/content/docs/reference/troubleshooting.md index 498d343..67d5d8b 100644 --- a/docs/src/content/docs/reference/troubleshooting.md +++ b/docs/src/content/docs/reference/troubleshooting.md @@ -130,15 +130,17 @@ Any local feature can be disabled without removing it from `devcontainer.json` b - Subsequent starts skip unchanged config files (SHA-256 comparison). - Disable steps you don't need via `.env` (e.g., `SETUP_PROJECTS=false`). See [Environment Variables — Setup Variables](./environment/#setup-variables-env). -## How to Reset to Defaults +## How to Reset -1. **Reset config files** — delete `~/.claude/` and restart the container. `setup-config.sh` will recopy all files from `.codeforge/config/`. Note that `--reset` preserves `.codeforge/` user modifications (only `.devcontainer/` is wiped). +1. **Reset runtime config** — delete `~/.claude/` and restart the container. `setup-config.sh` will redeploy all files from `.codeforge/config/`. This resets the deployed copies but preserves your `.codeforge/` source files (user modifications remain intact). -2. **Reset aliases** — delete the `# Claude Code environment and aliases` block from `~/.bashrc` and `~/.zshrc`, then run `bash /workspaces/.devcontainer/scripts/setup-aliases.sh`. +2. **Restore default config sources** — run `git checkout .codeforge/config/` to discard any local edits to the source files, then restart the container to redeploy. -3. **Full reset** — rebuild the container from scratch (VS Code: **Dev Containers: Rebuild Container**). +3. **Reset aliases** — delete the `# Claude Code environment and aliases` block from `~/.bashrc` and `~/.zshrc`, then run `bash /workspaces/.devcontainer/scripts/setup-aliases.sh`. -4. **Reset a single feature** — set it to `"version": "none"`, rebuild, then set it back to the desired version and rebuild again. +4. **Full reset** — rebuild the container from scratch (VS Code: **Dev Containers: Rebuild Container**). This recreates everything but still preserves `.codeforge/` user modifications since they live in the repository. + +5. **Reset a single feature** — set it to `"version": "none"`, rebuild, then set it back to the desired version and rebuild again. ## Related diff --git a/setup.js b/setup.js index ceacc57..56f5f12 100755 --- a/setup.js +++ b/setup.js @@ -532,14 +532,37 @@ function configApply() { ); } - const srcPath = path.join(codeforgeDir, entry.src); + const codeforgeRoot = path.resolve(codeforgeDir); + const srcPath = path.resolve(codeforgeRoot, entry.src); + if (!srcPath.startsWith(codeforgeRoot + path.sep)) { + console.log(" Skip: " + entry.src + " (source path escapes .codeforge/)"); + skipped++; + continue; + } if (!fs.existsSync(srcPath)) { console.log(" Skip: " + entry.src + " (not found)"); skipped++; continue; } - const destDir = expandVars(entry.dest); + const homeDir = path.resolve(process.env.HOME || "/home/vscode"); + const allowedDestRoots = [ + path.resolve(claudeConfigDir), + homeDir, + "/usr/local", + ]; + const destDir = path.resolve(expandVars(entry.dest)); + const destAllowed = allowedDestRoots.some( + (root) => destDir === root || destDir.startsWith(root + path.sep), + ); + if (!destAllowed) { + console.log( + " Skip: " + entry.dest + " (destination outside allowed directories)", + ); + skipped++; + continue; + } + const filename = entry.destFilename || path.basename(entry.src); const destPath = path.join(destDir, filename); fs.mkdirSync(destDir, { recursive: true }); From 17f38c928cb03ba2f828ccf151f84629ee11720b Mon Sep 17 00:00:00 2001 From: AnExiledDev Date: Fri, 27 Feb 2026 00:28:29 +0000 Subject: [PATCH 6/6] Fix biome formatting in setup.js path traversal validation --- setup.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.js b/setup.js index 56f5f12..0812767 100755 --- a/setup.js +++ b/setup.js @@ -535,7 +535,9 @@ function configApply() { const codeforgeRoot = path.resolve(codeforgeDir); const srcPath = path.resolve(codeforgeRoot, entry.src); if (!srcPath.startsWith(codeforgeRoot + path.sep)) { - console.log(" Skip: " + entry.src + " (source path escapes .codeforge/)"); + console.log( + " Skip: " + entry.src + " (source path escapes .codeforge/)", + ); skipped++; continue; }