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.
Files changed (106) hide show
  1. package/{.devcontainer/config/defaults → .codeforge/config}/ccstatusline-settings.json +44 -6
  2. package/{.devcontainer/config/defaults → .codeforge/config}/main-system-prompt.md +14 -6
  3. package/.codeforge/config/orchestrator-system-prompt.md +333 -0
  4. package/{.devcontainer/config/defaults → .codeforge/config}/settings.json +3 -1
  5. package/{.devcontainer/config → .codeforge}/file-manifest.json +15 -9
  6. package/{.devcontainer → .codeforge/scripts}/connect-external-terminal.sh +3 -1
  7. package/.devcontainer/.env.example +5 -5
  8. package/.devcontainer/.secrets.example +3 -0
  9. package/.devcontainer/CHANGELOG.md +242 -0
  10. package/.devcontainer/CLAUDE.md +129 -22
  11. package/.devcontainer/README.md +34 -19
  12. package/.devcontainer/devcontainer.json +28 -10
  13. package/.devcontainer/features/agent-browser/install.sh +2 -0
  14. package/.devcontainer/features/ast-grep/install.sh +2 -0
  15. package/.devcontainer/features/biome/install.sh +2 -0
  16. package/.devcontainer/features/ccburn/install.sh +2 -0
  17. package/.devcontainer/features/ccms/install.sh +2 -0
  18. package/.devcontainer/features/ccstatusline/README.md +7 -6
  19. package/.devcontainer/features/ccstatusline/install.sh +9 -4
  20. package/.devcontainer/features/ccusage/install.sh +2 -0
  21. package/.devcontainer/features/chromaterm/chromaterm.yml +2 -2
  22. package/.devcontainer/features/chromaterm/install.sh +2 -0
  23. package/.devcontainer/features/claude-code-native/README.md +47 -0
  24. package/.devcontainer/features/claude-code-native/devcontainer-feature.json +29 -0
  25. package/.devcontainer/features/claude-code-native/install.sh +131 -0
  26. package/.devcontainer/features/claude-monitor/install.sh +2 -0
  27. package/.devcontainer/features/claude-session-dashboard/README.md +2 -2
  28. package/.devcontainer/features/claude-session-dashboard/install.sh +2 -0
  29. package/.devcontainer/features/dprint/install.sh +2 -0
  30. package/.devcontainer/features/hadolint/install.sh +2 -0
  31. package/.devcontainer/features/kitty-terminfo/README.md +3 -1
  32. package/.devcontainer/features/kitty-terminfo/install.sh +2 -0
  33. package/.devcontainer/features/lsp-servers/install.sh +2 -0
  34. package/.devcontainer/features/mcp-qdrant/CHANGES.md +3 -3
  35. package/.devcontainer/features/mcp-qdrant/README.md +1 -0
  36. package/.devcontainer/features/mcp-qdrant/devcontainer-feature.json +1 -1
  37. package/.devcontainer/features/mcp-qdrant/install.sh +9 -2
  38. package/.devcontainer/features/mcp-qdrant/poststart-hook.sh +9 -2
  39. package/.devcontainer/features/notify-hook/devcontainer-feature.json +1 -1
  40. package/.devcontainer/features/notify-hook/install.sh +2 -0
  41. package/.devcontainer/features/ruff/install.sh +2 -0
  42. package/.devcontainer/features/shellcheck/install.sh +2 -0
  43. package/.devcontainer/features/shfmt/install.sh +2 -0
  44. package/.devcontainer/features/tmux/README.md +3 -3
  45. package/.devcontainer/features/tmux/install.sh +3 -1
  46. package/.devcontainer/features/tree-sitter/install.sh +2 -0
  47. package/.devcontainer/plugins/devs-marketplace/.claude-plugin/marketplace.json +27 -11
  48. package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/README.md +23 -4
  49. package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/claude-guide.md +4 -4
  50. package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/documenter.md +254 -0
  51. package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/implementer.md +260 -0
  52. package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/investigator.md +255 -0
  53. package/.devcontainer/plugins/devs-marketplace/plugins/agent-system/agents/tester.md +304 -0
  54. package/.devcontainer/plugins/devs-marketplace/plugins/auto-code-quality/README.md +1 -1
  55. package/.devcontainer/plugins/devs-marketplace/plugins/auto-code-quality/scripts/advisory-test-runner.py +4 -2
  56. package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/scripts/block-dangerous.py +2 -2
  57. package/.devcontainer/plugins/devs-marketplace/plugins/git-workflow/.claude-plugin/plugin.json +7 -0
  58. package/.devcontainer/plugins/devs-marketplace/plugins/git-workflow/README.md +125 -0
  59. package/.devcontainer/plugins/devs-marketplace/plugins/git-workflow/skills/pr-review/SKILL.md +325 -0
  60. package/.devcontainer/plugins/devs-marketplace/plugins/git-workflow/skills/ship/SKILL.md +314 -0
  61. package/.devcontainer/plugins/devs-marketplace/plugins/prompt-snippets/.claude-plugin/plugin.json +5 -0
  62. package/.devcontainer/plugins/devs-marketplace/plugins/prompt-snippets/README.md +52 -0
  63. package/.devcontainer/plugins/devs-marketplace/plugins/prompt-snippets/skills/ps/SKILL.md +37 -0
  64. package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected-bash.py +1 -1
  65. package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected.py +1 -1
  66. package/.devcontainer/plugins/devs-marketplace/plugins/session-context/README.md +30 -14
  67. package/.devcontainer/plugins/devs-marketplace/plugins/session-context/hooks/hooks.json +13 -1
  68. package/.devcontainer/plugins/devs-marketplace/plugins/session-context/scripts/collect-session-edits.py +44 -0
  69. package/.devcontainer/plugins/devs-marketplace/plugins/session-context/scripts/commit-reminder.py +89 -10
  70. package/.devcontainer/plugins/devs-marketplace/plugins/skill-engine/.claude-plugin/plugin.json +1 -1
  71. package/.devcontainer/plugins/devs-marketplace/plugins/skill-engine/README.md +19 -11
  72. package/.devcontainer/plugins/devs-marketplace/plugins/skill-engine/scripts/skill-suggester.py +476 -282
  73. package/.devcontainer/plugins/devs-marketplace/plugins/skill-engine/skills/worktree/SKILL.md +227 -0
  74. package/.devcontainer/plugins/devs-marketplace/plugins/skill-engine/skills/worktree/references/manual-worktree-commands.md +238 -0
  75. package/.devcontainer/plugins/devs-marketplace/plugins/skill-engine/skills/worktree/references/parallel-workflow-patterns.md +228 -0
  76. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/scripts/ticket-linker.py +2 -2
  77. package/.devcontainer/plugins/devs-marketplace/plugins/workspace-scope-guard/README.md +1 -1
  78. package/.devcontainer/plugins/devs-marketplace/plugins/workspace-scope-guard/scripts/guard-workspace-scope.py +3 -2
  79. package/.devcontainer/scripts/check-setup.sh +5 -3
  80. package/.devcontainer/scripts/preflight.sh +113 -0
  81. package/.devcontainer/scripts/setup-aliases.sh +13 -8
  82. package/.devcontainer/scripts/setup-auth.sh +46 -0
  83. package/.devcontainer/scripts/setup-config.sh +29 -10
  84. package/.devcontainer/scripts/setup-migrate-claude.sh +80 -0
  85. package/.devcontainer/scripts/setup-migrate-codeforge.sh +60 -0
  86. package/.devcontainer/scripts/setup-plugins.sh +3 -1
  87. package/.devcontainer/scripts/setup-projects.sh +3 -1
  88. package/.devcontainer/scripts/setup-terminal.sh +3 -1
  89. package/.devcontainer/scripts/setup-update-claude.sh +22 -27
  90. package/.devcontainer/scripts/setup.sh +57 -5
  91. package/LICENSE.txt +14 -0
  92. package/README.md +79 -5
  93. package/package.json +2 -1
  94. package/setup.js +392 -21
  95. package/.devcontainer/docs/configuration-reference.md +0 -93
  96. package/.devcontainer/docs/keybindings.md +0 -100
  97. package/.devcontainer/docs/optional-features.md +0 -64
  98. package/.devcontainer/docs/plugins.md +0 -176
  99. package/.devcontainer/docs/troubleshooting.md +0 -128
  100. package/.devcontainer/scripts/setup-symlink-claude.sh +0 -36
  101. /package/{.devcontainer/config/defaults → .codeforge/config}/keybindings.json +0 -0
  102. /package/{.devcontainer/config/defaults → .codeforge/config}/rules/session-search.md +0 -0
  103. /package/{.devcontainer/config/defaults → .codeforge/config}/rules/spec-workflow.md +0 -0
  104. /package/{.devcontainer/config/defaults → .codeforge/config}/rules/workspace-scope.md +0 -0
  105. /package/{.devcontainer/config/defaults → .codeforge/config}/writing-system-prompt.md +0 -0
  106. /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 the package that should NOT overwrite user customizations.
10
+ // Files in .devcontainer that should NOT overwrite user customizations.
8
11
  // The package version is saved as <file>.codeforge-new for diffing.
9
- const DEFAULT_PRESERVE = [
10
- "config/defaults/settings.json",
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 + "/" + entry.name
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 + ".bak");
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 + ".codeforge-new");
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 Update existing .devcontainer (preserves user config)",
305
+ " --force, -f Update existing .devcontainer and .codeforge (preserves user config)",
135
306
  );
136
307
  console.log(
137
- " --reset Remove all customizations and install fresh defaults",
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 everything and copy fresh
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(" Reset complete. All user customizations removed.");
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 = { copyDirectory, syncDirectory, loadPreserveList, main };
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).