forge-orkes 0.3.5 → 0.3.6

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.
@@ -8,6 +8,11 @@ const templateDir = path.join(__dirname, '..', 'template');
8
8
  const targetDir = process.cwd();
9
9
  const pkgVersion = require('../package.json').version;
10
10
 
11
+ // --- Section markers for CLAUDE.md ---
12
+
13
+ const FORGE_START = '<!-- forge:start -->';
14
+ const FORGE_END = '<!-- forge:end -->';
15
+
11
16
  // --- File classification for upgrades ---
12
17
 
13
18
  // Framework-owned: Forge controls these entirely
@@ -16,9 +21,6 @@ const FRAMEWORK_OWNED_DIRS = ['.claude/agents', '.claude/skills'];
16
21
  // Template-only: reference templates Forge controls
17
22
  const TEMPLATE_ONLY_DIRS = ['.forge/templates'];
18
23
 
19
- // Merge-owned: never auto-overwrite, stage for review
20
- const MERGE_OWNED_FILES = ['CLAUDE.md'];
21
-
22
24
  // Settings file gets smart-merge (overwrite forge.* keys, preserve user hooks)
23
25
  const SETTINGS_FILE = '.claude/settings.json';
24
26
 
@@ -128,27 +130,62 @@ function upgradeDir(relDir) {
128
130
  }
129
131
 
130
132
  /**
131
- * Handle merge-owned files: stage new version for manual review if different.
133
+ * Smart-merge CLAUDE.md using section markers.
134
+ * - If markers exist: replace the forge section, preserve everything else
135
+ * - If no markers but forge content exists: replace it and add markers
136
+ * - If no forge content: append with markers
137
+ * Returns 'replaced' | 'appended' | 'unchanged' | 'created'
132
138
  */
133
- function handleMergeFile(relFile) {
134
- const srcPath = path.join(templateDir, relFile);
135
- const destPath = path.join(targetDir, relFile);
139
+ function mergeClaudeMd() {
140
+ const srcPath = path.join(templateDir, 'CLAUDE.md');
141
+ const destPath = path.join(targetDir, 'CLAUDE.md');
136
142
 
137
143
  if (!fs.existsSync(srcPath)) return null;
138
- if (!fs.existsSync(destPath)) return null;
139
144
 
140
- const srcContent = fs.readFileSync(srcPath, 'utf-8');
141
- const destContent = fs.readFileSync(destPath, 'utf-8');
145
+ const forgeContent = fs.readFileSync(srcPath, 'utf-8');
146
+
147
+ // No existing CLAUDE.md — just copy
148
+ if (!fs.existsSync(destPath)) {
149
+ fs.writeFileSync(destPath, forgeContent);
150
+ return 'created';
151
+ }
152
+
153
+ const existing = fs.readFileSync(destPath, 'utf-8');
142
154
 
143
- if (srcContent === destContent) return 'unchanged';
155
+ // Check if content is already identical
156
+ if (existing === forgeContent) return 'unchanged';
144
157
 
145
- // Stage the new version for manual review
146
- const upgradeDir = path.join(targetDir, '.forge', 'upgrade');
147
- fs.mkdirSync(upgradeDir, { recursive: true });
148
- const basename = path.basename(relFile);
149
- const newPath = path.join(upgradeDir, `${basename}.new`);
150
- fs.writeFileSync(newPath, srcContent);
151
- return 'staged';
158
+ const startIdx = existing.indexOf(FORGE_START);
159
+ const endIdx = existing.indexOf(FORGE_END);
160
+
161
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
162
+ // Markers found — replace the section between them (inclusive)
163
+ const before = existing.substring(0, startIdx);
164
+ const after = existing.substring(endIdx + FORGE_END.length);
165
+ const merged = before + forgeContent + after;
166
+
167
+ // Clean up any double newlines at the seams
168
+ const cleaned = merged.replace(/\n{3,}/g, '\n\n');
169
+ fs.writeFileSync(destPath, cleaned);
170
+ return 'replaced';
171
+ }
172
+
173
+ // No markers — check if there's an old forge section (starts with "# Forge")
174
+ const forgeHeaderIdx = existing.indexOf('# Forge\n');
175
+ if (forgeHeaderIdx !== -1) {
176
+ // Old install without markers — replace from "# Forge" to end of file
177
+ // (Forge content is always appended at the end in old installs)
178
+ const before = existing.substring(0, forgeHeaderIdx);
179
+ const merged = before + forgeContent;
180
+ const cleaned = merged.replace(/\n{3,}/g, '\n\n');
181
+ fs.writeFileSync(destPath, cleaned);
182
+ return 'replaced';
183
+ }
184
+
185
+ // No forge content at all — append with markers
186
+ const merged = existing.trimEnd() + '\n\n' + forgeContent + '\n';
187
+ fs.writeFileSync(destPath, merged);
188
+ return 'appended';
152
189
  }
153
190
 
154
191
  /**
@@ -178,49 +215,40 @@ function upgradeSettings() {
178
215
  return 'updated';
179
216
  }
180
217
 
218
+ /**
219
+ * Detect if Forge is already installed in this project.
220
+ */
221
+ function isForgeInstalled() {
222
+ const settingsPath = path.join(targetDir, SETTINGS_FILE);
223
+ if (!fs.existsSync(settingsPath)) return false;
224
+
225
+ try {
226
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
227
+ return !!settings.forge;
228
+ } catch {
229
+ return false;
230
+ }
231
+ }
232
+
181
233
  // --- Commands ---
182
234
 
183
235
  async function install() {
184
236
  console.log('\n Forge - Meta-prompting framework for Claude Code\n');
185
237
 
186
- // Handle CLAUDE.md
187
- const srcClaudeMd = path.join(templateDir, 'CLAUDE.md');
188
- const destClaudeMd = path.join(targetDir, 'CLAUDE.md');
189
- let claudeMdAction = 'copy';
190
-
191
- if (fs.existsSync(destClaudeMd)) {
192
- console.log(' CLAUDE.md already exists in this directory.\n');
193
- const answer = await prompt(
194
- ' What would you like to do? (a)ppend / (r)eplace / (s)kip: '
195
- );
196
-
197
- if (answer === 'a' || answer === 'append') {
198
- claudeMdAction = 'append';
199
- } else if (answer === 'r' || answer === 'replace') {
200
- claudeMdAction = 'replace';
201
- } else {
202
- claudeMdAction = 'skip';
203
- }
204
- }
205
-
206
- const srcContent = fs.readFileSync(srcClaudeMd, 'utf-8');
207
-
208
- switch (claudeMdAction) {
209
- case 'copy':
210
- case 'replace':
211
- fs.writeFileSync(destClaudeMd, srcContent);
212
- console.log(
213
- ` ${claudeMdAction === 'replace' ? 'Replaced' : 'Created'} CLAUDE.md`
214
- );
238
+ // Handle CLAUDE.md — use smart merge
239
+ const claudeStatus = mergeClaudeMd();
240
+ switch (claudeStatus) {
241
+ case 'created':
242
+ console.log(' Created CLAUDE.md');
243
+ break;
244
+ case 'replaced':
245
+ console.log(' Updated Forge section in CLAUDE.md (user content preserved)');
215
246
  break;
216
- case 'append': {
217
- const existing = fs.readFileSync(destClaudeMd, 'utf-8');
218
- fs.writeFileSync(destClaudeMd, existing + '\n\n' + srcContent);
247
+ case 'appended':
219
248
  console.log(' Appended Forge config to existing CLAUDE.md');
220
249
  break;
221
- }
222
- case 'skip':
223
- console.log(' Skipped CLAUDE.md');
250
+ case 'unchanged':
251
+ console.log(' CLAUDE.md already up to date');
224
252
  break;
225
253
  }
226
254
 
@@ -274,7 +302,6 @@ async function upgrade() {
274
302
  added: [],
275
303
  unchanged: [],
276
304
  removed: [],
277
- needsReview: [],
278
305
  };
279
306
 
280
307
  // 1. Process framework-owned directories
@@ -295,14 +322,14 @@ async function upgrade() {
295
322
  results.removed.push(...dirResult.removed);
296
323
  }
297
324
 
298
- // 3. Process merge-owned files
299
- for (const file of MERGE_OWNED_FILES) {
300
- const status = handleMergeFile(file);
301
- if (status === 'staged') {
302
- results.needsReview.push(file);
303
- } else if (status === 'unchanged') {
304
- results.unchanged.push(file);
305
- }
325
+ // 3. Smart-merge CLAUDE.md using section markers
326
+ const claudeStatus = mergeClaudeMd();
327
+ if (claudeStatus === 'replaced' || claudeStatus === 'appended') {
328
+ results.updated.push('CLAUDE.md');
329
+ } else if (claudeStatus === 'unchanged') {
330
+ results.unchanged.push('CLAUDE.md');
331
+ } else if (claudeStatus === 'created') {
332
+ results.added.push('CLAUDE.md');
306
333
  }
307
334
 
308
335
  // 4. Smart-merge settings.json
@@ -314,8 +341,7 @@ async function upgrade() {
314
341
  }
315
342
 
316
343
  // Report results
317
- const totalChanges =
318
- results.updated.length + results.added.length + results.needsReview.length;
344
+ const totalChanges = results.updated.length + results.added.length;
319
345
 
320
346
  if (totalChanges === 0 && results.removed.length === 0) {
321
347
  console.log(' Already up to date.\n');
@@ -338,14 +364,6 @@ async function upgrade() {
338
364
  console.log();
339
365
  }
340
366
 
341
- if (results.needsReview.length > 0) {
342
- console.log(` Needs manual review (${results.needsReview.length}):`);
343
- for (const f of results.needsReview) {
344
- console.log(` ${f} → .forge/upgrade/${path.basename(f)}.new`);
345
- }
346
- console.log();
347
- }
348
-
349
367
  if (results.removed.length > 0) {
350
368
  console.log(` Removed from template (${results.removed.length}):`);
351
369
  for (const f of results.removed) {
@@ -366,6 +384,13 @@ if (subcommand === 'upgrade') {
366
384
  console.error('Error:', err.message);
367
385
  process.exit(1);
368
386
  });
387
+ } else if (isForgeInstalled()) {
388
+ // Auto-detect: Forge already installed, run upgrade instead
389
+ console.log(' Forge detected — running upgrade automatically.\n');
390
+ upgrade().catch((err) => {
391
+ console.error('Error:', err.message);
392
+ process.exit(1);
393
+ });
369
394
  } else {
370
395
  install().catch((err) => {
371
396
  console.error('Error:', err.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-orkes",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "description": "Set up the Forge meta-prompting framework for Claude Code in your project",
5
5
  "bin": {
6
6
  "create-forge": "./bin/create-forge.js"
@@ -1,3 +1,4 @@
1
+ <!-- forge:start -->
1
2
  # Forge
2
3
 
3
4
  A lean meta-prompting framework for Claude Code. Synthesizes context engineering (GSD) and constitutional governance (Spec-Kit) on Claude Code's native primitives.
@@ -161,3 +162,4 @@ Every task gets its own commit. Format: `{type}({scope}): {description}`
161
162
  Types: `feat`, `fix`, `test`, `refactor`, `chore`, `docs`
162
163
  Scope: phase-plan or feature area
163
164
  Never use `git add .` or `git add -A` — stage files individually.
165
+ <!-- forge:end -->