claudeforge-cli 1.0.2 → 1.0.3

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/README.md CHANGED
@@ -114,6 +114,19 @@ uv tool install claudeforge
114
114
  claudeforge --version
115
115
  ```
116
116
 
117
+ ### Update to latest version
118
+
119
+ ```bash
120
+ # npm
121
+ npm update -g claudeforge-cli
122
+
123
+ # pip
124
+ pip install --upgrade claudeforge
125
+
126
+ # uv
127
+ uv tool upgrade claudeforge
128
+ ```
129
+
117
130
  ---
118
131
 
119
132
  ## Quick Start
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudeforge-cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Forge production-ready AI agent projects — agents, slash commands, memory, CI/CD, and devcontainers in one command",
5
5
  "bin": {
6
6
  "claudeforge": "bin/cli.js"
@@ -6,45 +6,98 @@ const scaffolder = require('../scaffolder');
6
6
  const logger = require('../logger');
7
7
  const fs = require('fs-extra');
8
8
 
9
- /**
10
- * Files that are infrastructure templates (always safe to upgrade).
11
- * User-edited files like CLAUDE.md are excluded by default.
12
- */
13
- const UPGRADEABLE = [
14
- // Infrastructure — always safe to re-apply
15
- { src: 'claude/settings.local.json.tpl', dest: '.claude/settings.local.json' },
16
- { src: 'claude/README.md.tpl', dest: '.claude/README.md' },
17
- { src: 'claude/hooks/pre-tool-use.sh.tpl', dest: '.claude/hooks/pre-tool-use.sh' },
18
- { src: 'claude/hooks/post-tool-use.sh.tpl', dest: '.claude/hooks/post-tool-use.sh' },
19
- { src: 'claude/rules/no-sensitive-files.md.tpl', dest: '.claude/rules/no-sensitive-files.md' },
20
- { src: 'claude/skills/project-conventions/SKILL.md.tpl', dest: '.claude/skills/project-conventions/SKILL.md' },
21
- { src: 'memory/MEMORY.md.tpl', dest: 'memory/MEMORY.md' },
22
- // Built-in AI slash commands — safe to upgrade (project-specific ones in USER_OWNED)
23
- { src: 'claude/commands/setup-project.md.tpl', dest: '.claude/commands/setup-project.md' },
24
- { src: 'claude/commands/memory-sync.md.tpl', dest: '.claude/commands/memory-sync.md' },
25
- { src: 'claude/commands/project-health.md.tpl', dest: '.claude/commands/project-health.md' },
26
- { src: 'claude/commands/standup.md.tpl', dest: '.claude/commands/standup.md' },
27
- { src: 'claude/commands/explain-codebase.md.tpl', dest: '.claude/commands/explain-codebase.md' },
28
- { src: 'claude/commands/fix-issue.md.tpl', dest: '.claude/commands/fix-issue.md' },
29
- { src: 'claude/commands/scaffold-structure.md.tpl', dest: '.claude/commands/scaffold-structure.md' },
30
- ];
9
+ // ── Infrastructure files — always safe to update ──────────────────────────────
10
+ // These are claudeforge-owned. User edits here are not expected.
11
+ const INFRASTRUCTURE = new Set([
12
+ '.claude/README.md',
13
+ '.claude/settings.local.json',
14
+ '.claude/hooks/pre-tool-use.sh',
15
+ '.claude/hooks/post-tool-use.sh',
16
+ '.claude/rules/no-sensitive-files.md',
17
+ '.claude/skills/project-conventions/SKILL.md',
18
+ 'memory/MEMORY.md',
19
+ // Built-in slash commands — all claudeforge-managed
20
+ '.claude/commands/setup-project.md',
21
+ '.claude/commands/analyze-project.md',
22
+ '.claude/commands/memory-sync.md',
23
+ '.claude/commands/project-health.md',
24
+ '.claude/commands/standup.md',
25
+ '.claude/commands/explain-codebase.md',
26
+ '.claude/commands/fix-issue.md',
27
+ '.claude/commands/scaffold-structure.md',
28
+ ]);
29
+
30
+ // ── User-owned files — skip unless --all ─────────────────────────────────────
31
+ // These are edited by the user after init. Only touch with explicit --all.
32
+ const USER_OWNED = new Set([
33
+ 'CLAUDE.md',
34
+ 'CLAUDE.local.md',
35
+ '.env.example',
36
+ '.mcp.json',
37
+ '.gitignore',
38
+ '.claude/settings.json',
39
+ '.claude/agents/code-reviewer.md',
40
+ '.claude/commands/commit.md',
41
+ '.claude/commands/review-pr.md',
42
+ 'memory/feedback_communication.md',
43
+ 'memory/project_ai_workflow.md',
44
+ 'memory/user_profile.md',
45
+ ]);
46
+
47
+ // ── Full manifest (must stay in sync with init.js) ────────────────────────────
48
+ const MANIFEST = [
49
+ // Directories
50
+ { type: 'dir', dest: '.claude' },
51
+ { type: 'dir', dest: '.claude/agents' },
52
+ { type: 'dir', dest: '.claude/commands' },
53
+ { type: 'dir', dest: '.claude/hooks' },
54
+ { type: 'dir', dest: '.claude/rules' },
55
+ { type: 'dir', dest: '.claude/skills' },
56
+ { type: 'dir', dest: '.claude/skills/project-conventions' },
57
+ { type: 'dir', dest: 'memory' },
58
+
59
+ // .claude/ root
60
+ { type: 'file', src: 'claude/README.md.tpl', dest: '.claude/README.md' },
61
+ { type: 'file', src: 'claude/settings.json.tpl', dest: '.claude/settings.json' },
62
+ { type: 'file', src: 'claude/settings.local.json.tpl', dest: '.claude/settings.local.json' },
63
+
64
+ // Agents
65
+ { type: 'file', src: 'claude/agents/code-reviewer.md.tpl', dest: '.claude/agents/code-reviewer.md' },
66
+
67
+ // Built-in commands
68
+ { type: 'file', src: 'claude/commands/commit.md.tpl', dest: '.claude/commands/commit.md' },
69
+ { type: 'file', src: 'claude/commands/review-pr.md.tpl', dest: '.claude/commands/review-pr.md' },
70
+ { type: 'file', src: 'claude/commands/setup-project.md.tpl', dest: '.claude/commands/setup-project.md' },
71
+ { type: 'file', src: 'claude/commands/analyze-project.md.tpl', dest: '.claude/commands/analyze-project.md' },
72
+ { type: 'file', src: 'claude/commands/memory-sync.md.tpl', dest: '.claude/commands/memory-sync.md' },
73
+ { type: 'file', src: 'claude/commands/project-health.md.tpl', dest: '.claude/commands/project-health.md' },
74
+ { type: 'file', src: 'claude/commands/standup.md.tpl', dest: '.claude/commands/standup.md' },
75
+ { type: 'file', src: 'claude/commands/explain-codebase.md.tpl', dest: '.claude/commands/explain-codebase.md' },
76
+ { type: 'file', src: 'claude/commands/fix-issue.md.tpl', dest: '.claude/commands/fix-issue.md' },
77
+ { type: 'file', src: 'claude/commands/scaffold-structure.md.tpl',dest: '.claude/commands/scaffold-structure.md' },
78
+
79
+ // Hooks
80
+ { type: 'file', src: 'claude/hooks/pre-tool-use.sh.tpl', dest: '.claude/hooks/pre-tool-use.sh' },
81
+ { type: 'file', src: 'claude/hooks/post-tool-use.sh.tpl', dest: '.claude/hooks/post-tool-use.sh' },
31
82
 
32
- /**
33
- * User-owned files only upgraded with --all flag.
34
- */
35
- const USER_OWNED = [
36
- { src: 'CLAUDE.md.tpl', dest: 'CLAUDE.md' },
37
- { src: 'CLAUDE.local.md.tpl', dest: 'CLAUDE.local.md' },
38
- { src: '.env.example.tpl', dest: '.env.example' },
39
- { src: 'mcp.json.tpl', dest: '.mcp.json' },
40
- { src: '.gitignore.tpl', dest: '.gitignore' },
41
- { src: 'claude/settings.json.tpl', dest: '.claude/settings.json' },
42
- { src: 'claude/agents/code-reviewer.md.tpl', dest: '.claude/agents/code-reviewer.md' },
43
- { src: 'claude/commands/commit.md.tpl', dest: '.claude/commands/commit.md' },
44
- { src: 'claude/commands/review-pr.md.tpl', dest: '.claude/commands/review-pr.md' },
45
- { src: 'memory/feedback_communication.md.tpl', dest: 'memory/feedback_communication.md' },
46
- { src: 'memory/project_ai_workflow.md.tpl', dest: 'memory/project_ai_workflow.md' },
47
- { src: 'memory/user_profile.md.tpl', dest: 'memory/user_profile.md' },
83
+ // Rules
84
+ { type: 'file', src: 'claude/rules/no-sensitive-files.md.tpl', dest: '.claude/rules/no-sensitive-files.md' },
85
+
86
+ // Skills
87
+ { type: 'file', src: 'claude/skills/project-conventions/SKILL.md.tpl', dest: '.claude/skills/project-conventions/SKILL.md' },
88
+
89
+ // Memory
90
+ { type: 'file', src: 'memory/MEMORY.md.tpl', dest: 'memory/MEMORY.md' },
91
+ { type: 'file', src: 'memory/user_profile.md.tpl', dest: 'memory/user_profile.md' },
92
+ { type: 'file', src: 'memory/feedback_communication.md.tpl', dest: 'memory/feedback_communication.md' },
93
+ { type: 'file', src: 'memory/project_ai_workflow.md.tpl', dest: 'memory/project_ai_workflow.md' },
94
+
95
+ // Project root
96
+ { type: 'file', src: 'CLAUDE.md.tpl', dest: 'CLAUDE.md' },
97
+ { type: 'file', src: 'CLAUDE.local.md.tpl', dest: 'CLAUDE.local.md' },
98
+ { type: 'file', src: '.env.example.tpl', dest: '.env.example' },
99
+ { type: 'file', src: 'mcp.json.tpl', dest: '.mcp.json' },
100
+ { type: 'file', src: '.gitignore.tpl', dest: '.gitignore' },
48
101
  ];
49
102
 
50
103
  async function upgrade(options) {
@@ -65,22 +118,57 @@ async function upgrade(options) {
65
118
  console.log('');
66
119
 
67
120
  const templatesDir = path.join(__dirname, '../../templates');
68
- const manifest = includeAll ? [...UPGRADEABLE, ...USER_OWNED] : UPGRADEABLE;
121
+ const stats = { updated: 0, created: 0, skipped: 0 };
69
122
 
70
- const stats = { updated: 0, skipped: 0 };
71
-
72
- for (const entry of manifest) {
73
- const srcAbs = path.join(templatesDir, entry.src);
123
+ for (const entry of MANIFEST) {
74
124
  const destAbs = path.join(targetDir, entry.dest);
75
125
 
76
- const result = await scaffolder.writeFile(srcAbs, destAbs, { force: true, dryRun });
126
+ // Always ensure directories exist
127
+ if (entry.type === 'dir') {
128
+ await scaffolder.ensureDir(destAbs, dryRun);
129
+ continue;
130
+ }
131
+
132
+ const isInfrastructure = INFRASTRUCTURE.has(entry.dest);
133
+ const isUserOwned = USER_OWNED.has(entry.dest);
134
+ const alreadyExists = await fs.pathExists(destAbs);
135
+
136
+ // Decision logic:
137
+ // 1. Doesn't exist yet → always create (new file added in this version)
138
+ // 2. Infrastructure → always update
139
+ // 3. User-owned exists → skip unless --all
140
+ let action;
141
+ if (!alreadyExists) {
142
+ action = 'create';
143
+ } else if (isInfrastructure) {
144
+ action = 'update';
145
+ } else if (includeAll) {
146
+ action = 'update';
147
+ } else {
148
+ action = 'skip';
149
+ }
150
+
151
+ if (action === 'skip') {
152
+ console.log(` ${chalk.dim('–')} ${chalk.dim(entry.dest)} ${chalk.dim('skipped (user-owned)')}`);
153
+ stats.skipped++;
154
+ continue;
155
+ }
156
+
157
+ const srcAbs = path.join(templatesDir, entry.src);
77
158
 
78
159
  if (dryRun) {
79
- console.log(` ${chalk.cyan('~')} ${chalk.dim(entry.dest)} ${chalk.cyan('would update')}`);
160
+ const label = action === 'create' ? chalk.green('would create') : chalk.cyan('would update');
161
+ console.log(` ${chalk.cyan('~')} ${chalk.dim(entry.dest)} ${label}`);
80
162
  } else {
81
- console.log(` ${chalk.yellow('↺')} ${chalk.dim(entry.dest)}`);
163
+ await scaffolder.writeFile(srcAbs, destAbs, { force: true, dryRun: false });
164
+ if (action === 'create') {
165
+ console.log(` ${chalk.green('+')} ${chalk.dim(entry.dest)} ${chalk.green('created (new in this version)')}`);
166
+ stats.created++;
167
+ } else {
168
+ console.log(` ${chalk.yellow('↺')} ${chalk.dim(entry.dest)}`);
169
+ stats.updated++;
170
+ }
82
171
  }
83
- stats.updated++;
84
172
  }
85
173
 
86
174
  // Re-chmod hooks
@@ -93,22 +181,22 @@ async function upgrade(options) {
93
181
  console.log(chalk.dim(' ─────────────────────────────────────────'));
94
182
 
95
183
  if (dryRun) {
96
- console.log(chalk.cyan(` Dry run: ${stats.updated} file(s) would be updated.`));
97
- console.log('');
184
+ console.log(chalk.cyan(` Dry run complete. Run without --dry-run to apply changes.`));
98
185
  } else {
99
- console.log(chalk.green(` ✓ ${stats.updated} infrastructure file(s) updated to latest templates.`));
100
- if (!includeAll) {
101
- console.log('');
102
- console.log(chalk.dim(' User-owned files (CLAUDE.md, settings.json, agents, commands) were NOT touched.'));
103
- console.log(chalk.dim(' Use --all to force-update everything (will overwrite your edits).'));
186
+ if (stats.created > 0) {
187
+ console.log(chalk.green(` ✓ ${stats.created} new file(s) created (added in this version)`));
188
+ }
189
+ console.log(chalk.green(` ${stats.updated} infrastructure file(s) updated to latest templates`));
190
+ if (stats.skipped > 0 && !includeAll) {
191
+ console.log(chalk.dim(` – ${stats.skipped} user-owned file(s) skipped (use --all to update them too)`));
104
192
  }
105
193
  console.log('');
106
194
  console.log(chalk.bold(' What to do next:'));
107
195
  console.log('');
108
196
  console.log(` ${chalk.dim('›')} ${chalk.cyan('/project-health')} ${chalk.dim('audit your setup to see if anything else needs attention')}`);
109
- console.log(` ${chalk.dim('›')} ${chalk.cyan('claudeforge status')} ${chalk.dim('verify the updated files look correct')}`);
110
- console.log('');
197
+ console.log(` ${chalk.dim('›')} ${chalk.cyan('claudeforge status')} ${chalk.dim('verify the updated files look correct')}`);
111
198
  }
199
+ console.log('');
112
200
  }
113
201
 
114
202
  module.exports = upgrade;