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.
- package/bin/create-forge.js +97 -72
- package/package.json +1 -1
- package/template/CLAUDE.md +2 -0
package/bin/create-forge.js
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
134
|
-
const srcPath = path.join(templateDir,
|
|
135
|
-
const destPath = path.join(targetDir,
|
|
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
|
|
141
|
-
|
|
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
|
|
155
|
+
// Check if content is already identical
|
|
156
|
+
if (existing === forgeContent) return 'unchanged';
|
|
144
157
|
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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 '
|
|
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
|
-
|
|
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.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
package/template/CLAUDE.md
CHANGED
|
@@ -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 -->
|