codeforge-dev 1.14.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{.devcontainer/config/defaults → .codeforge/config}/ccstatusline-settings.json +44 -6
- package/{.devcontainer/config/defaults → .codeforge/config}/main-system-prompt.md +14 -6
- package/.codeforge/config/orchestrator-system-prompt.md +333 -0
- package/{.devcontainer/config/defaults → .codeforge/config}/settings.json +3 -1
- package/{.devcontainer/config → .codeforge}/file-manifest.json +15 -9
- package/{.devcontainer → .codeforge/scripts}/connect-external-terminal.sh +3 -1
- package/.devcontainer/.env.example +5 -5
- package/.devcontainer/.secrets.example +3 -0
- package/.devcontainer/CHANGELOG.md +242 -0
- package/.devcontainer/CLAUDE.md +129 -22
- package/.devcontainer/README.md +34 -19
- package/.devcontainer/devcontainer.json +28 -10
- package/.devcontainer/features/agent-browser/install.sh +2 -0
- package/.devcontainer/features/ast-grep/install.sh +2 -0
- package/.devcontainer/features/biome/install.sh +2 -0
- 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/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/install.sh +2 -0
- package/.devcontainer/features/claude-session-dashboard/README.md +2 -2
- package/.devcontainer/features/claude-session-dashboard/install.sh +2 -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 +2 -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 -1
- 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/install.sh +2 -0
- package/.devcontainer/plugins/devs-marketplace/.claude-plugin/marketplace.json +27 -11
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/README.md +23 -4
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/claude-guide.md +4 -4
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/documenter.md +254 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/implementer.md +260 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/investigator.md +255 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/tester.md +304 -0
- 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/scripts/block-dangerous.py +2 -2
- 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/scripts/guard-protected-bash.py +1 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected.py +1 -1
- 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/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/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 +3 -2
- 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 +3 -1
- package/.devcontainer/scripts/setup-projects.sh +3 -1
- package/.devcontainer/scripts/setup-terminal.sh +3 -1
- package/.devcontainer/scripts/setup-update-claude.sh +22 -27
- package/.devcontainer/scripts/setup.sh +57 -5
- package/LICENSE.txt +14 -0
- package/README.md +79 -5
- package/package.json +2 -1
- package/setup.js +392 -21
- 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/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/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",
|
|
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
|
+
};
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# Configuration Reference
|
|
2
|
-
|
|
3
|
-
Quick reference for all CodeForge configuration options.
|
|
4
|
-
|
|
5
|
-
## Which File to Edit
|
|
6
|
-
|
|
7
|
-
| Task | File to Edit |
|
|
8
|
-
|------|-------------|
|
|
9
|
-
| Change default model | `config/defaults/settings.json` |
|
|
10
|
-
| Change system prompt | `config/defaults/main-system-prompt.md` |
|
|
11
|
-
| Customize keybindings | `config/defaults/keybindings.json` |
|
|
12
|
-
| Control setup steps | `.env` |
|
|
13
|
-
| Add custom config file | `config/file-manifest.json` |
|
|
14
|
-
| Disable a feature | `devcontainer.json` (set `"version": "none"`) |
|
|
15
|
-
| Disable a plugin | `config/defaults/settings.json` (remove from `enabledPlugins`) |
|
|
16
|
-
| Skip a plugin install | `.env` (`PLUGIN_BLACKLIST`) |
|
|
17
|
-
| Change container memory | `devcontainer.json` (`runArgs`) |
|
|
18
|
-
| Add VS Code extension | `devcontainer.json` (`customizations.vscode.extensions`) |
|
|
19
|
-
|
|
20
|
-
## `.env` Variables (Setup Behavior)
|
|
21
|
-
|
|
22
|
-
These control what `setup.sh` does on each container start. Copy `.env.example` to `.env` and customize.
|
|
23
|
-
|
|
24
|
-
| Variable | Default | Description |
|
|
25
|
-
|----------|---------|-------------|
|
|
26
|
-
| `CLAUDE_CONFIG_DIR` | `/workspaces/.claude` | Where Claude Code config files are stored |
|
|
27
|
-
| `CONFIG_SOURCE_DIR` | `(auto-detected)` | Source directory for config defaults |
|
|
28
|
-
| `SETUP_CONFIG` | `true` | Copy config files per `file-manifest.json` |
|
|
29
|
-
| `SETUP_ALIASES` | `true` | Add cc/claude/ccraw/cc-tools aliases to shell |
|
|
30
|
-
| `SETUP_AUTH` | `true` | Configure Git/NPM auth from `.secrets` file |
|
|
31
|
-
| `SETUP_PLUGINS` | `true` | Install Anthropic plugins + register local marketplace |
|
|
32
|
-
| `SETUP_UPDATE_CLAUDE` | `true` | Background-update Claude Code CLI binary |
|
|
33
|
-
| `SETUP_TERMINAL` | `true` | Configure VS Code Shift+Enter keybinding for Claude Code terminal |
|
|
34
|
-
| `SETUP_PROJECTS` | `true` | Auto-detect projects for VS Code Project Manager |
|
|
35
|
-
| `SETUP_POSTSTART` | `true` | Run post-start hooks from `/usr/local/devcontainer-poststart.d/` |
|
|
36
|
-
| `PLUGIN_BLACKLIST` | `""` | Comma-separated plugin names to skip during installation |
|
|
37
|
-
|
|
38
|
-
## `devcontainer.json` `remoteEnv` (Container Runtime)
|
|
39
|
-
|
|
40
|
-
These environment variables are set in every terminal session inside the container.
|
|
41
|
-
|
|
42
|
-
| Variable | Value | Description |
|
|
43
|
-
|----------|-------|-------------|
|
|
44
|
-
| `WORKSPACE_ROOT` | `/workspaces` | Workspace root directory |
|
|
45
|
-
| `CLAUDE_CONFIG_DIR` | `/workspaces/.claude` | Claude Code config directory |
|
|
46
|
-
| `GH_CONFIG_DIR` | `/workspaces/.gh` | GitHub CLI config directory |
|
|
47
|
-
| `TMPDIR` | `/workspaces/.tmp` | Temporary files directory |
|
|
48
|
-
| `CLAUDECODE` | `null` (unset) | Unsets the variable to allow nested Claude Code sessions (claude-in-claude) |
|
|
49
|
-
|
|
50
|
-
## `config/file-manifest.json` (File Copy Rules)
|
|
51
|
-
|
|
52
|
-
Each entry in the manifest array controls how a config file is deployed:
|
|
53
|
-
|
|
54
|
-
```json
|
|
55
|
-
{
|
|
56
|
-
"src": "defaults/settings.json",
|
|
57
|
-
"dest": "${CLAUDE_CONFIG_DIR}",
|
|
58
|
-
"destFilename": "settings.json",
|
|
59
|
-
"overwrite": "if-changed",
|
|
60
|
-
"enabled": true
|
|
61
|
-
}
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
| Field | Required | Default | Description |
|
|
65
|
-
|-------|----------|---------|-------------|
|
|
66
|
-
| `src` | Yes | — | Source file path relative to `config/` |
|
|
67
|
-
| `dest` | Yes | — | Destination directory (supports `${CLAUDE_CONFIG_DIR}`, `${WORKSPACE_ROOT}`) |
|
|
68
|
-
| `destFilename` | No | basename of `src` | Override the destination filename |
|
|
69
|
-
| `overwrite` | No | `"if-changed"` | `"always"`, `"never"`, or `"if-changed"` (sha256 comparison) |
|
|
70
|
-
| `enabled` | No | `true` | Set `false` to skip this entry |
|
|
71
|
-
|
|
72
|
-
## Feature Options
|
|
73
|
-
|
|
74
|
-
Each feature in `devcontainer.json` supports options defined in its `devcontainer-feature.json`. Common options:
|
|
75
|
-
|
|
76
|
-
| Option | Description | Used By |
|
|
77
|
-
|--------|-------------|---------|
|
|
78
|
-
| `version` | Tool version to install. `"none"` skips installation. | All local features |
|
|
79
|
-
| `username` | Container user to install for. `"automatic"` auto-detects. | dprint, ruff, ccusage, ccburn, etc. |
|
|
80
|
-
| `shells` | Which shell rc files to modify (`"both"`, `"bash"`, `"zsh"`). | ccusage, ccburn |
|
|
81
|
-
|
|
82
|
-
## `.secrets` File (Authentication)
|
|
83
|
-
|
|
84
|
-
Create `.devcontainer/.secrets` with tokens for automatic authentication:
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
GH_TOKEN=ghp_your_token_here
|
|
88
|
-
GH_USERNAME=your-github-username
|
|
89
|
-
GH_EMAIL=your-email@example.com
|
|
90
|
-
NPM_TOKEN=npm_your_token_here
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
Environment variables with the same names take precedence over `.secrets` file values (useful for Codespaces).
|