codeforge-dev 1.14.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{.devcontainer/config/defaults → .codeforge/config}/ccstatusline-settings.json +44 -6
- package/.codeforge/config/main-system-prompt.md +412 -0
- package/.codeforge/config/orchestrator-system-prompt.md +333 -0
- package/{.devcontainer/config/defaults → .codeforge/config}/settings.json +7 -2
- package/{.devcontainer/config → .codeforge}/file-manifest.json +15 -9
- package/{.devcontainer → .codeforge/scripts}/connect-external-terminal.sh +3 -1
- package/.devcontainer/.env.example +17 -5
- package/.devcontainer/.secrets.example +3 -0
- package/.devcontainer/CHANGELOG.md +224 -3
- package/.devcontainer/CLAUDE.md +26 -43
- package/.devcontainer/README.md +35 -20
- package/.devcontainer/devcontainer.json +36 -17
- package/.devcontainer/features/agent-browser/install.sh +3 -0
- package/.devcontainer/features/ast-grep/install.sh +3 -0
- package/.devcontainer/features/biome/install.sh +3 -0
- package/.devcontainer/features/ccburn/devcontainer-feature.json +0 -5
- package/.devcontainer/features/ccburn/install.sh +2 -0
- package/.devcontainer/features/ccms/install.sh +2 -0
- package/.devcontainer/features/ccstatusline/README.md +7 -6
- package/.devcontainer/features/ccstatusline/install.sh +9 -4
- package/.devcontainer/features/ccusage/devcontainer-feature.json +0 -5
- package/.devcontainer/features/ccusage/install.sh +2 -0
- package/.devcontainer/features/chromaterm/chromaterm.yml +2 -2
- package/.devcontainer/features/chromaterm/install.sh +2 -0
- package/.devcontainer/features/claude-code-native/README.md +47 -0
- package/.devcontainer/features/claude-code-native/devcontainer-feature.json +29 -0
- package/.devcontainer/features/claude-code-native/install.sh +131 -0
- package/.devcontainer/features/claude-monitor/devcontainer-feature.json +0 -5
- package/.devcontainer/features/claude-monitor/install.sh +2 -0
- package/.devcontainer/features/claude-session-dashboard/README.md +2 -2
- package/.devcontainer/features/claude-session-dashboard/devcontainer-feature.json +1 -2
- package/.devcontainer/features/claude-session-dashboard/install.sh +3 -0
- package/.devcontainer/features/dprint/install.sh +2 -0
- package/.devcontainer/features/hadolint/install.sh +2 -0
- package/.devcontainer/features/kitty-terminfo/README.md +3 -1
- package/.devcontainer/features/kitty-terminfo/install.sh +2 -0
- package/.devcontainer/features/lsp-servers/install.sh +4 -0
- package/.devcontainer/features/mcp-qdrant/CHANGES.md +3 -3
- package/.devcontainer/features/mcp-qdrant/README.md +1 -0
- package/.devcontainer/features/mcp-qdrant/devcontainer-feature.json +1 -7
- package/.devcontainer/features/mcp-qdrant/install.sh +9 -2
- package/.devcontainer/features/mcp-qdrant/poststart-hook.sh +9 -2
- package/.devcontainer/features/notify-hook/devcontainer-feature.json +1 -1
- package/.devcontainer/features/notify-hook/install.sh +2 -0
- package/.devcontainer/features/ruff/install.sh +2 -0
- package/.devcontainer/features/shellcheck/install.sh +2 -0
- package/.devcontainer/features/shfmt/install.sh +2 -0
- package/.devcontainer/features/tmux/README.md +3 -3
- package/.devcontainer/features/tmux/install.sh +3 -1
- package/.devcontainer/features/tree-sitter/devcontainer-feature.json +0 -6
- package/.devcontainer/features/tree-sitter/install.sh +4 -0
- package/.devcontainer/plugins/devs-marketplace/.claude-plugin/marketplace.json +27 -11
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/README.md +20 -6
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/architect.md +182 -29
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/bash-exec.md +9 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/claude-guide.md +13 -4
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/debug-logs.md +24 -5
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/dependency-analyst.md +16 -5
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/documenter.md +412 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/explorer.md +18 -6
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/generalist.md +36 -10
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/git-archaeologist.md +10 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/implementer.md +260 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/investigator.md +262 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/migrator.md +10 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/perf-profiler.md +21 -5
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/refactorer.md +18 -8
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/researcher.md +23 -5
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/security-auditor.md +20 -6
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/spec-writer.md +12 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/statusline-config.md +12 -2
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/test-writer.md +22 -7
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/scripts/guard-readonly-bash.py +9 -5
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/scripts/redirect-builtin-agents.py +2 -5
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-code-quality/README.md +1 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-code-quality/scripts/advisory-test-runner.py +4 -2
- package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/README.md +3 -2
- package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/scripts/block-dangerous.py +89 -15
- package/.devcontainer/plugins/devs-marketplace/plugins/git-workflow/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/git-workflow/README.md +125 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/git-workflow/skills/pr-review/SKILL.md +325 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/git-workflow/skills/ship/SKILL.md +314 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/prompt-snippets/.claude-plugin/plugin.json +5 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/prompt-snippets/README.md +52 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/prompt-snippets/skills/ps/SKILL.md +37 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/README.md +2 -2
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected-bash.py +80 -6
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected.py +4 -4
- package/.devcontainer/plugins/devs-marketplace/plugins/session-context/README.md +30 -14
- package/.devcontainer/plugins/devs-marketplace/plugins/session-context/hooks/hooks.json +13 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/session-context/scripts/collect-session-edits.py +44 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/session-context/scripts/commit-reminder.py +89 -10
- package/.devcontainer/plugins/devs-marketplace/plugins/skill-engine/.claude-plugin/plugin.json +1 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/skill-engine/README.md +19 -11
- package/.devcontainer/plugins/devs-marketplace/plugins/skill-engine/scripts/skill-suggester.py +476 -282
- package/.devcontainer/plugins/devs-marketplace/plugins/skill-engine/skills/team/SKILL.md +4 -4
- package/.devcontainer/plugins/devs-marketplace/plugins/skill-engine/skills/worktree/SKILL.md +227 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/skill-engine/skills/worktree/references/manual-worktree-commands.md +238 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/skill-engine/skills/worktree/references/parallel-workflow-patterns.md +228 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/spec-workflow/skills/spec-build/SKILL.md +2 -2
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/scripts/ticket-linker.py +2 -2
- package/.devcontainer/plugins/devs-marketplace/plugins/workspace-scope-guard/README.md +1 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/workspace-scope-guard/scripts/guard-workspace-scope.py +69 -31
- package/.devcontainer/scripts/check-setup.sh +5 -3
- package/.devcontainer/scripts/preflight.sh +113 -0
- package/.devcontainer/scripts/setup-aliases.sh +13 -8
- package/.devcontainer/scripts/setup-auth.sh +46 -0
- package/.devcontainer/scripts/setup-config.sh +29 -10
- package/.devcontainer/scripts/setup-migrate-claude.sh +80 -0
- package/.devcontainer/scripts/setup-migrate-codeforge.sh +60 -0
- package/.devcontainer/scripts/setup-plugins.sh +5 -5
- package/.devcontainer/scripts/setup-projects.sh +4 -2
- package/.devcontainer/scripts/setup-terminal.sh +3 -1
- package/.devcontainer/scripts/setup-update-claude.sh +22 -27
- package/.devcontainer/scripts/setup.sh +78 -5
- package/LICENSE.txt +14 -0
- package/README.md +82 -7
- package/package.json +4 -1
- package/setup.js +392 -21
- package/.devcontainer/config/defaults/main-system-prompt.md +0 -664
- package/.devcontainer/docs/configuration-reference.md +0 -93
- package/.devcontainer/docs/keybindings.md +0 -100
- package/.devcontainer/docs/optional-features.md +0 -64
- package/.devcontainer/docs/plugins.md +0 -176
- package/.devcontainer/docs/troubleshooting.md +0 -128
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/doc-writer.md +0 -334
- package/.devcontainer/scripts/setup-symlink-claude.sh +0 -36
- /package/{.devcontainer/config/defaults → .codeforge/config}/keybindings.json +0 -0
- /package/{.devcontainer/config/defaults → .codeforge/config}/rules/session-search.md +0 -0
- /package/{.devcontainer/config/defaults → .codeforge/config}/rules/spec-workflow.md +0 -0
- /package/{.devcontainer/config/defaults → .codeforge/config}/rules/workspace-scope.md +0 -0
- /package/{.devcontainer/config/defaults → .codeforge/config}/writing-system-prompt.md +0 -0
- /package/{.devcontainer → .codeforge/scripts}/connect-external-terminal.ps1 +0 -0
package/README.md
CHANGED
|
@@ -7,9 +7,16 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/codeforge-dev)
|
|
8
8
|
[](https://nodejs.org/)
|
|
9
9
|
[](https://github.com/AnExiledDev/CodeForge/issues)
|
|
10
|
+
[](https://github.com/AnExiledDev/CodeForge/actions/workflows/ci.yml)
|
|
10
11
|
|
|
11
12
|
A curated development environment optimized for AI-powered coding with Claude Code. CodeForge comes pre-configured with language servers, code intelligence tools, and official Anthropic plugins to streamline your development workflow.
|
|
12
13
|
|
|
14
|
+
## Why CodeForge?
|
|
15
|
+
|
|
16
|
+
Claude Code is powerful out of the box, but getting the most from it takes significant configuration — custom agents, safety plugins, code quality hooks, system prompts, and development tools that aren't obvious from the docs. CodeForge is a Claude Code power user's personal development environment, packaged so anyone can use it.
|
|
17
|
+
|
|
18
|
+
Instead of spending hours discovering and configuring advanced features like built-in agent replacement, automated code quality pipelines, or spec-driven workflows, you get a production-tested setup in one command. It's opinionated by design — every default reflects real daily use, not theoretical best practices.
|
|
19
|
+
|
|
13
20
|
## Installation
|
|
14
21
|
|
|
15
22
|
Add CodeForge to any project:
|
|
@@ -23,8 +30,9 @@ This copies the `.devcontainer/` directory to your project. Then open in VS Code
|
|
|
23
30
|
### Options
|
|
24
31
|
|
|
25
32
|
```bash
|
|
26
|
-
npx codeforge-dev --force #
|
|
33
|
+
npx codeforge-dev --force # Smart update (preserves your customizations)
|
|
27
34
|
npx codeforge-dev -f # Short form
|
|
35
|
+
npx codeforge-dev --reset # Fresh install (wipes .devcontainer, keeps .codeforge)
|
|
28
36
|
```
|
|
29
37
|
|
|
30
38
|
### Alternative Install Methods
|
|
@@ -41,7 +49,12 @@ npx codeforge-dev@1.2.3
|
|
|
41
49
|
## Prerequisites
|
|
42
50
|
|
|
43
51
|
- **Docker Desktop** (or compatible container runtime like Podman)
|
|
44
|
-
- **
|
|
52
|
+
- **A DevContainer client** — any of:
|
|
53
|
+
- **VS Code** with the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
|
|
54
|
+
- **DevContainer CLI** — `npm install -g @devcontainers/cli` ([docs](https://containers.dev/supporting#devcontainer-cli))
|
|
55
|
+
- **GitHub Codespaces** — zero local setup
|
|
56
|
+
- **JetBrains Gateway** with [Dev Containers plugin](https://plugins.jetbrains.com/plugin/21962-dev-containers)
|
|
57
|
+
- **DevPod** — open-source, editor-agnostic ([devpod.sh](https://devpod.sh/))
|
|
45
58
|
- **Claude Code authentication** — run `claude` on first start to authenticate
|
|
46
59
|
|
|
47
60
|
## What's Included
|
|
@@ -70,18 +83,80 @@ tree-sitter (JS/TS/Python), ast-grep, Pyright, TypeScript LSP
|
|
|
70
83
|
|
|
71
84
|
tmux, agent-browser, claude-monitor, ccusage, ccburn, ccstatusline, ast-grep, tree-sitter, lsp-servers, biome, ruff, shfmt, shellcheck, hadolint, dprint, ccms, notify-hook, mcp-qdrant, chromaterm, kitty-terminfo, claude-session-dashboard
|
|
72
85
|
|
|
73
|
-
### Agents (17) & Skills (
|
|
86
|
+
### Agents (17) & Skills (35)
|
|
87
|
+
|
|
88
|
+
The `agent-system` plugin includes 17 specialized agents (architect, explorer, test-writer, security-auditor, etc.). The `skill-engine` plugin provides 22 general coding skills, `spec-workflow` adds 8 spec lifecycle skills, and `ticket-workflow` provides 4 ticket management skills.
|
|
89
|
+
|
|
90
|
+
## Architecture
|
|
91
|
+
|
|
92
|
+
CodeForge operates in three layers, each building on the one below:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
┌──────────────────────────────────────────────┐
|
|
96
|
+
│ Claude Code │
|
|
97
|
+
│ AI assistant, tool execution, Agent Teams │
|
|
98
|
+
├──────────────────────────────────────────────┤
|
|
99
|
+
│ CodeForge Layer │
|
|
100
|
+
│ Plugins · Agents · Skills · Hooks · Rules │
|
|
101
|
+
├──────────────────────────────────────────────┤
|
|
102
|
+
│ DevContainer │
|
|
103
|
+
│ Runtimes · CLI Tools · LSP Servers │
|
|
104
|
+
└──────────────────────────────────────────────┘
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**DevContainer** — The foundation. A Python 3.14 container with Node.js, Rust, and Bun runtimes, plus 22 custom features that install development tools (ast-grep, tree-sitter, biome, ruff, and others).
|
|
108
|
+
|
|
109
|
+
**CodeForge Layer** — The intelligence. 17 plugins register hooks that validate commands, inject context, and enforce safety. 21 agents provide specialized personas. 38 skills offer on-demand reference material. System prompts and rules shape behavior.
|
|
74
110
|
|
|
75
|
-
|
|
111
|
+
**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.
|
|
112
|
+
|
|
113
|
+
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/).
|
|
114
|
+
|
|
115
|
+
## Configuration
|
|
116
|
+
|
|
117
|
+
All configuration lives in `.devcontainer/` and deploys automatically on container start. Key files:
|
|
118
|
+
|
|
119
|
+
| File | What It Configures | User-Modifiable? |
|
|
120
|
+
|------|--------------------|------------------|
|
|
121
|
+
| `config/defaults/settings.json` | Model, plugins, permissions, environment variables | Yes |
|
|
122
|
+
| `config/defaults/main-system-prompt.md` | Claude's behavioral guidelines and directives | Yes |
|
|
123
|
+
| `config/defaults/keybindings.json` | Keyboard shortcuts | Yes |
|
|
124
|
+
| `config/defaults/ccstatusline-settings.json` | Terminal status bar widgets and layout | Yes |
|
|
125
|
+
| `config/file-manifest.json` | Which config files deploy and how they update | Yes |
|
|
126
|
+
| `devcontainer.json` | Container image, features, runtimes, ports | Yes |
|
|
127
|
+
| `.env` | Setup phase toggles (auth, plugins, aliases, etc.) | Yes |
|
|
128
|
+
|
|
129
|
+
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.
|
|
130
|
+
|
|
131
|
+
For the complete configuration guide, see the [documentation site](https://codeforge.core-directive.com/customization/configuration/).
|
|
76
132
|
|
|
77
133
|
## Quick Start
|
|
78
134
|
|
|
79
135
|
1. **Install**: `npx codeforge-dev`
|
|
80
|
-
2. **Open in Container**:
|
|
136
|
+
2. **Open in Container**:
|
|
137
|
+
- **VS Code**: "Reopen in Container" from the Command Palette
|
|
138
|
+
- **CLI**: `devcontainer up --workspace-folder .` then `docker exec -it <container> zsh`
|
|
139
|
+
- **Codespaces**: Create a Codespace from the repo
|
|
81
140
|
3. **Authenticate**: Run `claude` and follow prompts
|
|
82
141
|
4. **Start coding**: Run `cc`
|
|
83
142
|
|
|
84
|
-
For full usage documentation — authentication, configuration, tools, agents, and keybindings — see [`.devcontainer/README.md`](.devcontainer/README.md).
|
|
143
|
+
CodeForge uses the open [Dev Containers specification](https://containers.dev/) — any compatible client works. For full usage documentation — authentication, configuration, tools, agents, and keybindings — see [`.devcontainer/README.md`](.devcontainer/README.md).
|
|
144
|
+
|
|
145
|
+
## Contributing
|
|
146
|
+
|
|
147
|
+
We welcome contributions! Please read our [Contributing Guide](CONTRIBUTING.md)
|
|
148
|
+
before submitting a pull request. All contributions require signing our
|
|
149
|
+
[Contributor License Agreement](CLA.md).
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
This project is licensed under the [GNU General Public License v3.0](LICENSE.txt).
|
|
154
|
+
|
|
155
|
+
**Commercial licensing** is available for organizations that need to use CodeForge
|
|
156
|
+
without GPL-3.0 obligations. Contact
|
|
157
|
+
[696222+AnExiledDev@users.noreply.github.com](mailto:696222+AnExiledDev@users.noreply.github.com)
|
|
158
|
+
or [open a GitHub issue](https://github.com/AnExiledDev/CodeForge/issues/new)
|
|
159
|
+
for terms.
|
|
85
160
|
|
|
86
161
|
## Development
|
|
87
162
|
|
|
@@ -102,7 +177,7 @@ npm publish
|
|
|
102
177
|
|
|
103
178
|
## Changelog
|
|
104
179
|
|
|
105
|
-
See [CHANGELOG.md](.devcontainer/CHANGELOG.md) for release history. Current version: **
|
|
180
|
+
See [CHANGELOG.md](.devcontainer/CHANGELOG.md) for release history. Current version: **2.0.0**.
|
|
106
181
|
|
|
107
182
|
## Further Reading
|
|
108
183
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeforge-dev",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Complete development container that sets up Claude Code with modular devcontainer features, modern dev tools, and persistent configurations. Drop it into any project and get a production-ready AI development environment in minutes.",
|
|
5
5
|
"main": "setup.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "node test.js",
|
|
11
|
+
"test:plugins": "pytest tests/ -v",
|
|
12
|
+
"test:all": "npm test && pytest tests/ -v",
|
|
11
13
|
"prepublishOnly": "npm test",
|
|
12
14
|
"docs:dev": "npm run dev --prefix docs",
|
|
13
15
|
"docs:build": "npm run build --prefix docs",
|
|
@@ -30,6 +32,7 @@
|
|
|
30
32
|
"license": "GPL-3.0",
|
|
31
33
|
"files": [
|
|
32
34
|
".devcontainer/**/*",
|
|
35
|
+
".codeforge/**/*",
|
|
33
36
|
"setup.js",
|
|
34
37
|
"README.md"
|
|
35
38
|
],
|
package/setup.js
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
3
|
+
// Copyright (c) 2026 Marcus Krueger
|
|
2
4
|
|
|
3
|
-
const fs = require("fs");
|
|
4
|
-
const path = require("path");
|
|
5
|
+
const fs = require("node:fs");
|
|
6
|
+
const path = require("node:path");
|
|
7
|
+
const crypto = require("node:crypto");
|
|
5
8
|
|
|
6
9
|
// ── Default preserve list ────────────────────────────────────────
|
|
7
|
-
// Files in
|
|
10
|
+
// Files in .devcontainer that should NOT overwrite user customizations.
|
|
8
11
|
// The package version is saved as <file>.codeforge-new for diffing.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"config/defaults/main-system-prompt.md",
|
|
12
|
-
"config/defaults/keybindings.json",
|
|
13
|
-
"config/defaults/ccstatusline-settings.json",
|
|
14
|
-
"config/file-manifest.json",
|
|
15
|
-
".codeforge-preserve",
|
|
16
|
-
];
|
|
12
|
+
// Note: .codeforge/ uses checksum-based preservation instead.
|
|
13
|
+
const DEFAULT_PRESERVE = [".codeforge-preserve"];
|
|
17
14
|
|
|
18
15
|
// ── copyDirectory ────────────────────────────────────────────────
|
|
19
16
|
// Simple recursive copy (used for fresh install and --reset).
|
|
@@ -54,6 +51,173 @@ function loadPreserveList(devcontainerDest) {
|
|
|
54
51
|
return new Set([...DEFAULT_PRESERVE, ...custom]);
|
|
55
52
|
}
|
|
56
53
|
|
|
54
|
+
// ── computeChecksum ──────────────────────────────────────────────
|
|
55
|
+
// Returns SHA-256 hex digest of a file's contents.
|
|
56
|
+
function computeChecksum(filePath) {
|
|
57
|
+
return crypto
|
|
58
|
+
.createHash("sha256")
|
|
59
|
+
.update(fs.readFileSync(filePath))
|
|
60
|
+
.digest("hex");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── generateChecksums ────────────────────────────────────────────
|
|
64
|
+
// Walks directory recursively, returns { relativePath: sha256hex } map.
|
|
65
|
+
// Skips .checksums/ and .markers/ directories.
|
|
66
|
+
function generateChecksums(dir) {
|
|
67
|
+
const checksums = {};
|
|
68
|
+
|
|
69
|
+
function walk(currentDir, relativeBase) {
|
|
70
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
71
|
+
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
74
|
+
const relativePath = relativeBase
|
|
75
|
+
? `${relativeBase}/${entry.name}`
|
|
76
|
+
: entry.name;
|
|
77
|
+
|
|
78
|
+
if (entry.isDirectory()) {
|
|
79
|
+
if (entry.name === ".checksums" || entry.name === ".markers") {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
walk(fullPath, relativePath);
|
|
83
|
+
} else {
|
|
84
|
+
checksums[relativePath] = computeChecksum(fullPath);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
walk(dir, "");
|
|
90
|
+
return checksums;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── writeChecksums ───────────────────────────────────────────────
|
|
94
|
+
// Writes .checksums/<version>.json with version, timestamp, and file hashes.
|
|
95
|
+
function writeChecksums(codeforgeDir, version, checksums) {
|
|
96
|
+
const checksumsDir = path.join(codeforgeDir, ".checksums");
|
|
97
|
+
fs.mkdirSync(checksumsDir, { recursive: true });
|
|
98
|
+
const data = {
|
|
99
|
+
version,
|
|
100
|
+
generated: new Date().toISOString(),
|
|
101
|
+
files: checksums,
|
|
102
|
+
};
|
|
103
|
+
fs.writeFileSync(
|
|
104
|
+
path.join(checksumsDir, `${version}.json`),
|
|
105
|
+
JSON.stringify(data, null, "\t") + "\n",
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── readChecksums ────────────────────────────────────────────────
|
|
110
|
+
// Reads latest version's checksums from .checksums/ dir.
|
|
111
|
+
// Returns { files: {} } if none found.
|
|
112
|
+
function readChecksums(codeforgeDir) {
|
|
113
|
+
const checksumsDir = path.join(codeforgeDir, ".checksums");
|
|
114
|
+
if (!fs.existsSync(checksumsDir)) {
|
|
115
|
+
return { files: {} };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const files = fs
|
|
119
|
+
.readdirSync(checksumsDir)
|
|
120
|
+
.filter((f) => f.endsWith(".json"))
|
|
121
|
+
.sort((a, b) => {
|
|
122
|
+
const pa = a.replace(".json", "").split(".").map(Number);
|
|
123
|
+
const pb = b.replace(".json", "").split(".").map(Number);
|
|
124
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
125
|
+
const diff = (pa[i] || 0) - (pb[i] || 0);
|
|
126
|
+
if (diff !== 0) return diff;
|
|
127
|
+
}
|
|
128
|
+
return 0;
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
if (files.length === 0) {
|
|
132
|
+
return { files: {} };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const latest = files[files.length - 1];
|
|
136
|
+
try {
|
|
137
|
+
return JSON.parse(
|
|
138
|
+
fs.readFileSync(path.join(checksumsDir, latest), "utf-8"),
|
|
139
|
+
);
|
|
140
|
+
} catch {
|
|
141
|
+
console.log(
|
|
142
|
+
" Warning: Could not read checksums from " +
|
|
143
|
+
latest +
|
|
144
|
+
", treating as fresh install.",
|
|
145
|
+
);
|
|
146
|
+
return { files: {} };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ── syncCodeforgeDirectory ───────────────────────────────────────
|
|
151
|
+
// Checksum-aware sync for .codeforge/ directory.
|
|
152
|
+
// Unmodified files get overwritten; modified files are preserved
|
|
153
|
+
// and new defaults are written as <file>.default.
|
|
154
|
+
function syncCodeforgeDirectory(src, dest) {
|
|
155
|
+
const stored = readChecksums(dest);
|
|
156
|
+
const stats = {
|
|
157
|
+
updated: 0,
|
|
158
|
+
preserved: 0,
|
|
159
|
+
added: 0,
|
|
160
|
+
preservedFiles: [],
|
|
161
|
+
defaultFiles: [],
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
function walk(srcDir, destDir, relativeBase) {
|
|
165
|
+
if (!fs.existsSync(destDir)) {
|
|
166
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
170
|
+
|
|
171
|
+
for (const entry of entries) {
|
|
172
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
173
|
+
const destPath = path.join(destDir, entry.name);
|
|
174
|
+
const relativePath = relativeBase
|
|
175
|
+
? `${relativeBase}/${entry.name}`
|
|
176
|
+
: entry.name;
|
|
177
|
+
|
|
178
|
+
if (entry.isDirectory()) {
|
|
179
|
+
if (entry.name === ".checksums" || entry.name === ".markers") {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
walk(srcPath, destPath, relativePath);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const storedHash = stored.files[relativePath];
|
|
187
|
+
const currentHash = fs.existsSync(destPath)
|
|
188
|
+
? computeChecksum(destPath)
|
|
189
|
+
: null;
|
|
190
|
+
|
|
191
|
+
if (!storedHash) {
|
|
192
|
+
// First install or new file
|
|
193
|
+
if (currentHash === null) {
|
|
194
|
+
fs.copyFileSync(srcPath, destPath);
|
|
195
|
+
stats.added++;
|
|
196
|
+
} else {
|
|
197
|
+
// File exists but no stored hash — treat as user-created
|
|
198
|
+
fs.copyFileSync(srcPath, `${destPath}.default`);
|
|
199
|
+
stats.preserved++;
|
|
200
|
+
stats.preservedFiles.push(relativePath);
|
|
201
|
+
stats.defaultFiles.push(relativePath);
|
|
202
|
+
}
|
|
203
|
+
} else if (currentHash === storedHash) {
|
|
204
|
+
// File UNMODIFIED — overwrite with new version
|
|
205
|
+
fs.copyFileSync(srcPath, destPath);
|
|
206
|
+
stats.updated++;
|
|
207
|
+
} else {
|
|
208
|
+
// File USER-MODIFIED — keep user's file, write new as .default
|
|
209
|
+
fs.copyFileSync(srcPath, `${destPath}.default`);
|
|
210
|
+
stats.preserved++;
|
|
211
|
+
stats.preservedFiles.push(relativePath);
|
|
212
|
+
stats.defaultFiles.push(relativePath);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
walk(src, dest, "");
|
|
218
|
+
return stats;
|
|
219
|
+
}
|
|
220
|
+
|
|
57
221
|
// ── syncDirectory ────────────────────────────────────────────────
|
|
58
222
|
// Selective overwrite: walks the package tree and copies files to dest.
|
|
59
223
|
// - Framework files (scripts, features, plugins): always overwrite
|
|
@@ -80,7 +244,7 @@ function syncDirectory(src, dest, preserveSet) {
|
|
|
80
244
|
const srcPath = path.join(srcDir, entry.name);
|
|
81
245
|
const destPath = path.join(destDir, entry.name);
|
|
82
246
|
const relativePath = relativeBase
|
|
83
|
-
? relativeBase
|
|
247
|
+
? `${relativeBase}/${entry.name}`
|
|
84
248
|
: entry.name;
|
|
85
249
|
|
|
86
250
|
if (entry.isDirectory()) {
|
|
@@ -90,7 +254,7 @@ function syncDirectory(src, dest, preserveSet) {
|
|
|
90
254
|
|
|
91
255
|
// Special handling for devcontainer.json: overwrite + save .bak
|
|
92
256
|
if (relativePath === "devcontainer.json" && fs.existsSync(destPath)) {
|
|
93
|
-
fs.copyFileSync(destPath, destPath
|
|
257
|
+
fs.copyFileSync(destPath, `${destPath}.bak`);
|
|
94
258
|
fs.copyFileSync(srcPath, destPath);
|
|
95
259
|
stats.backedUp++;
|
|
96
260
|
stats.updated++;
|
|
@@ -99,7 +263,7 @@ function syncDirectory(src, dest, preserveSet) {
|
|
|
99
263
|
|
|
100
264
|
// Preserved files: skip overwrite, save package version as .codeforge-new
|
|
101
265
|
if (preserveSet.has(relativePath) && fs.existsSync(destPath)) {
|
|
102
|
-
fs.copyFileSync(srcPath, destPath
|
|
266
|
+
fs.copyFileSync(srcPath, `${destPath}.codeforge-new`);
|
|
103
267
|
stats.preserved++;
|
|
104
268
|
stats.preservedFiles.push(relativePath);
|
|
105
269
|
continue;
|
|
@@ -123,20 +287,33 @@ function syncDirectory(src, dest, preserveSet) {
|
|
|
123
287
|
// ── main ─────────────────────────────────────────────────────────
|
|
124
288
|
function main() {
|
|
125
289
|
const args = process.argv.slice(2);
|
|
290
|
+
|
|
291
|
+
// Subcommand: config apply
|
|
292
|
+
if (args[0] === "config" && args[1] === "apply") {
|
|
293
|
+
return configApply();
|
|
294
|
+
}
|
|
295
|
+
|
|
126
296
|
const force = args.includes("--force") || args.includes("-f");
|
|
127
297
|
const reset = args.includes("--reset");
|
|
128
298
|
|
|
129
299
|
if (args.includes("--help") || args.includes("-h")) {
|
|
130
300
|
console.log("Usage: codeforge [options]");
|
|
301
|
+
console.log(" codeforge config apply");
|
|
131
302
|
console.log("");
|
|
132
303
|
console.log("Options:");
|
|
133
304
|
console.log(
|
|
134
|
-
" --force, -f
|
|
305
|
+
" --force, -f Update existing .devcontainer and .codeforge (preserves user config)",
|
|
135
306
|
);
|
|
136
307
|
console.log(
|
|
137
|
-
" --reset
|
|
308
|
+
" --reset Remove .devcontainer customizations and install fresh defaults",
|
|
309
|
+
);
|
|
310
|
+
console.log(" (.codeforge user modifications preserved)");
|
|
311
|
+
console.log(" --help, -h Show this help message");
|
|
312
|
+
console.log("");
|
|
313
|
+
console.log("Subcommands:");
|
|
314
|
+
console.log(
|
|
315
|
+
" config apply Deploy .codeforge/config/ files to ~/.claude/",
|
|
138
316
|
);
|
|
139
|
-
console.log(" --help, -h Show this help message");
|
|
140
317
|
console.log("");
|
|
141
318
|
console.log(
|
|
142
319
|
"Without flags, installs only if .devcontainer does not exist.",
|
|
@@ -148,6 +325,11 @@ function main() {
|
|
|
148
325
|
const packageDir = __dirname;
|
|
149
326
|
const devcontainerSrc = path.join(packageDir, ".devcontainer");
|
|
150
327
|
const devcontainerDest = path.join(currentDir, ".devcontainer");
|
|
328
|
+
const codeforgeSrc = path.join(packageDir, ".codeforge");
|
|
329
|
+
const codeforgeDest = path.join(currentDir, ".codeforge");
|
|
330
|
+
const packageVersion = JSON.parse(
|
|
331
|
+
fs.readFileSync(path.join(packageDir, "package.json"), "utf8"),
|
|
332
|
+
).version;
|
|
151
333
|
|
|
152
334
|
console.log("");
|
|
153
335
|
|
|
@@ -161,12 +343,44 @@ function main() {
|
|
|
161
343
|
|
|
162
344
|
if (fs.existsSync(devcontainerDest)) {
|
|
163
345
|
if (reset) {
|
|
164
|
-
// Nuclear: delete
|
|
346
|
+
// Nuclear: delete .devcontainer and copy fresh
|
|
165
347
|
console.log("Resetting .devcontainer to package defaults...");
|
|
166
348
|
console.log("");
|
|
167
349
|
fs.rmSync(devcontainerDest, { recursive: true, force: true });
|
|
168
350
|
copyDirectory(devcontainerSrc, devcontainerDest);
|
|
169
|
-
console.log(
|
|
351
|
+
console.log(
|
|
352
|
+
" Reset complete. All .devcontainer customizations removed.",
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
// .codeforge uses checksum-based preservation (not wipe)
|
|
356
|
+
if (fs.existsSync(codeforgeSrc)) {
|
|
357
|
+
if (fs.existsSync(codeforgeDest)) {
|
|
358
|
+
const codeforgeStats = syncCodeforgeDirectory(
|
|
359
|
+
codeforgeSrc,
|
|
360
|
+
codeforgeDest,
|
|
361
|
+
);
|
|
362
|
+
console.log(" .codeforge/ user modifications preserved.");
|
|
363
|
+
console.log(` Updated: ${codeforgeStats.updated} files`);
|
|
364
|
+
console.log(` Added: ${codeforgeStats.added} new files`);
|
|
365
|
+
console.log(
|
|
366
|
+
` Preserved: ${codeforgeStats.preserved} user config files`,
|
|
367
|
+
);
|
|
368
|
+
if (codeforgeStats.defaultFiles.length > 0) {
|
|
369
|
+
console.log("");
|
|
370
|
+
console.log(
|
|
371
|
+
" Review .default files for new defaults you may want to merge:",
|
|
372
|
+
);
|
|
373
|
+
for (const f of codeforgeStats.defaultFiles) {
|
|
374
|
+
console.log(` ${f}.default`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
copyDirectory(codeforgeSrc, codeforgeDest);
|
|
379
|
+
}
|
|
380
|
+
const newChecksums = generateChecksums(codeforgeSrc);
|
|
381
|
+
writeChecksums(codeforgeDest, packageVersion, newChecksums);
|
|
382
|
+
}
|
|
383
|
+
|
|
170
384
|
console.log("");
|
|
171
385
|
printNextSteps();
|
|
172
386
|
} else if (force) {
|
|
@@ -204,6 +418,34 @@ function main() {
|
|
|
204
418
|
console.log("");
|
|
205
419
|
}
|
|
206
420
|
|
|
421
|
+
// .codeforge sync with checksum-based preservation
|
|
422
|
+
if (fs.existsSync(codeforgeSrc)) {
|
|
423
|
+
const codeforgeStats = syncCodeforgeDirectory(
|
|
424
|
+
codeforgeSrc,
|
|
425
|
+
codeforgeDest,
|
|
426
|
+
);
|
|
427
|
+
const newChecksums = generateChecksums(codeforgeSrc);
|
|
428
|
+
writeChecksums(codeforgeDest, packageVersion, newChecksums);
|
|
429
|
+
|
|
430
|
+
console.log(" .codeforge/ update:");
|
|
431
|
+
console.log(` Updated: ${codeforgeStats.updated} files`);
|
|
432
|
+
console.log(` Added: ${codeforgeStats.added} new files`);
|
|
433
|
+
console.log(
|
|
434
|
+
` Preserved: ${codeforgeStats.preserved} user config files`,
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
if (codeforgeStats.defaultFiles.length > 0) {
|
|
438
|
+
console.log("");
|
|
439
|
+
console.log(
|
|
440
|
+
" Review .default files for new defaults you may want to merge:",
|
|
441
|
+
);
|
|
442
|
+
for (const f of codeforgeStats.defaultFiles) {
|
|
443
|
+
console.log(` ${f}.default`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
console.log("");
|
|
447
|
+
}
|
|
448
|
+
|
|
207
449
|
printNextSteps();
|
|
208
450
|
} else {
|
|
209
451
|
// No flags: error with guidance
|
|
@@ -221,6 +463,13 @@ function main() {
|
|
|
221
463
|
|
|
222
464
|
try {
|
|
223
465
|
copyDirectory(devcontainerSrc, devcontainerDest);
|
|
466
|
+
|
|
467
|
+
if (fs.existsSync(codeforgeSrc)) {
|
|
468
|
+
copyDirectory(codeforgeSrc, codeforgeDest);
|
|
469
|
+
const checksums = generateChecksums(codeforgeSrc);
|
|
470
|
+
writeChecksums(codeforgeDest, packageVersion, checksums);
|
|
471
|
+
}
|
|
472
|
+
|
|
224
473
|
console.log(" CodeForge DevContainer configuration installed!");
|
|
225
474
|
console.log("");
|
|
226
475
|
printNextSteps();
|
|
@@ -232,13 +481,127 @@ function main() {
|
|
|
232
481
|
}
|
|
233
482
|
}
|
|
234
483
|
|
|
484
|
+
// ── configApply ──────────────────────────────────────────────────
|
|
485
|
+
// Deploys .codeforge/config/ files to ~/.claude/ using file-manifest.json.
|
|
486
|
+
function configApply() {
|
|
487
|
+
const codeforgeDir =
|
|
488
|
+
process.env.CODEFORGE_DIR || path.join(process.cwd(), ".codeforge");
|
|
489
|
+
const manifest = path.join(codeforgeDir, "file-manifest.json");
|
|
490
|
+
|
|
491
|
+
if (!fs.existsSync(manifest)) {
|
|
492
|
+
console.error("Error: file-manifest.json not found at " + manifest);
|
|
493
|
+
console.error("Are you in a CodeForge project directory?");
|
|
494
|
+
process.exit(1);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const entries = JSON.parse(fs.readFileSync(manifest, "utf-8"));
|
|
498
|
+
const claudeConfigDir =
|
|
499
|
+
process.env.CLAUDE_CONFIG_DIR ||
|
|
500
|
+
path.join(process.env.HOME || "/home/vscode", ".claude");
|
|
501
|
+
const workspaceRoot = process.env.WORKSPACE_ROOT || process.cwd();
|
|
502
|
+
|
|
503
|
+
function expandVars(val) {
|
|
504
|
+
return val
|
|
505
|
+
.replace(/\$\{CLAUDE_CONFIG_DIR\}/g, claudeConfigDir)
|
|
506
|
+
.replace(/\$\{WORKSPACE_ROOT\}/g, workspaceRoot)
|
|
507
|
+
.replace(/\$\{HOME\}/g, process.env.HOME || "/home/vscode");
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
console.log("");
|
|
511
|
+
console.log("Applying .codeforge/config/ to Claude configuration...");
|
|
512
|
+
console.log("");
|
|
513
|
+
|
|
514
|
+
let deployed = 0;
|
|
515
|
+
let skipped = 0;
|
|
516
|
+
|
|
517
|
+
const validOverwrite = ["always", "if-changed", "never"];
|
|
518
|
+
|
|
519
|
+
for (const entry of entries) {
|
|
520
|
+
if (entry.enabled === false) {
|
|
521
|
+
skipped++;
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (entry.overwrite && !validOverwrite.includes(entry.overwrite)) {
|
|
526
|
+
console.log(
|
|
527
|
+
' Warning: Unknown overwrite value "' +
|
|
528
|
+
entry.overwrite +
|
|
529
|
+
'" for ' +
|
|
530
|
+
entry.src +
|
|
531
|
+
', defaulting to "always"',
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const codeforgeRoot = path.resolve(codeforgeDir);
|
|
536
|
+
const srcPath = path.resolve(codeforgeRoot, entry.src);
|
|
537
|
+
if (!srcPath.startsWith(codeforgeRoot + path.sep)) {
|
|
538
|
+
console.log(
|
|
539
|
+
" Skip: " + entry.src + " (source path escapes .codeforge/)",
|
|
540
|
+
);
|
|
541
|
+
skipped++;
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
if (!fs.existsSync(srcPath)) {
|
|
545
|
+
console.log(" Skip: " + entry.src + " (not found)");
|
|
546
|
+
skipped++;
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const homeDir = path.resolve(process.env.HOME || "/home/vscode");
|
|
551
|
+
const allowedDestRoots = [
|
|
552
|
+
path.resolve(claudeConfigDir),
|
|
553
|
+
homeDir,
|
|
554
|
+
"/usr/local/share",
|
|
555
|
+
];
|
|
556
|
+
const destDir = path.resolve(expandVars(entry.dest));
|
|
557
|
+
const destAllowed = allowedDestRoots.some(
|
|
558
|
+
(root) => destDir === root || destDir.startsWith(root + path.sep),
|
|
559
|
+
);
|
|
560
|
+
if (!destAllowed) {
|
|
561
|
+
console.log(
|
|
562
|
+
" Skip: " + entry.dest + " (destination outside allowed directories)",
|
|
563
|
+
);
|
|
564
|
+
skipped++;
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const filename = entry.destFilename || path.basename(entry.src);
|
|
569
|
+
const destPath = path.join(destDir, filename);
|
|
570
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
571
|
+
|
|
572
|
+
if (entry.overwrite === "never" && fs.existsSync(destPath)) {
|
|
573
|
+
console.log(" Skip: " + filename + " (exists, overwrite=never)");
|
|
574
|
+
skipped++;
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (entry.overwrite === "if-changed" && fs.existsSync(destPath)) {
|
|
579
|
+
const srcHash = computeChecksum(srcPath);
|
|
580
|
+
const destHash = computeChecksum(destPath);
|
|
581
|
+
if (srcHash === destHash) {
|
|
582
|
+
skipped++;
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
fs.copyFileSync(srcPath, destPath);
|
|
588
|
+
console.log(" Deployed: " + entry.src + " → " + destPath);
|
|
589
|
+
deployed++;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
console.log("");
|
|
593
|
+
console.log(
|
|
594
|
+
"Config apply complete: " + deployed + " deployed, " + skipped + " skipped",
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
|
|
235
598
|
function printNextSteps() {
|
|
236
599
|
console.log("Next steps:");
|
|
237
600
|
console.log(" 1. Open this folder in VS Code");
|
|
238
601
|
console.log(' 2. Select "Reopen in Container" from the command palette');
|
|
239
602
|
console.log(" 3. Run: claude");
|
|
240
603
|
console.log("");
|
|
241
|
-
console.log("Documentation: .devcontainer/README.md");
|
|
604
|
+
console.log("Documentation: .devcontainer/README.md and .codeforge/");
|
|
242
605
|
console.log("");
|
|
243
606
|
}
|
|
244
607
|
|
|
@@ -255,4 +618,12 @@ if (require.main === module) {
|
|
|
255
618
|
main();
|
|
256
619
|
}
|
|
257
620
|
|
|
258
|
-
module.exports = {
|
|
621
|
+
module.exports = {
|
|
622
|
+
copyDirectory,
|
|
623
|
+
syncDirectory,
|
|
624
|
+
syncCodeforgeDirectory,
|
|
625
|
+
loadPreserveList,
|
|
626
|
+
computeChecksum,
|
|
627
|
+
generateChecksums,
|
|
628
|
+
main,
|
|
629
|
+
};
|