forge-orkes 0.2.0 → 0.3.1
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 +277 -6
- package/package.json +1 -1
- package/template/.claude/settings.json +27 -2
- package/template/.claude/skills/discussing/SKILL.md +118 -37
- package/template/.claude/skills/forge/SKILL.md +6 -0
- package/template/.claude/skills/upgrading/SKILL.md +90 -0
- package/template/CLAUDE.md +1 -0
package/bin/create-forge.js
CHANGED
|
@@ -6,6 +6,23 @@ const readline = require('readline');
|
|
|
6
6
|
|
|
7
7
|
const templateDir = path.join(__dirname, '..', 'template');
|
|
8
8
|
const targetDir = process.cwd();
|
|
9
|
+
const pkgVersion = require('../package.json').version;
|
|
10
|
+
|
|
11
|
+
// --- File classification for upgrades ---
|
|
12
|
+
|
|
13
|
+
// Framework-owned: Forge controls these entirely
|
|
14
|
+
const FRAMEWORK_OWNED_DIRS = ['.claude/agents', '.claude/skills'];
|
|
15
|
+
|
|
16
|
+
// Template-only: reference templates Forge controls
|
|
17
|
+
const TEMPLATE_ONLY_DIRS = ['.forge/templates'];
|
|
18
|
+
|
|
19
|
+
// Merge-owned: never auto-overwrite, stage for review
|
|
20
|
+
const MERGE_OWNED_FILES = ['CLAUDE.md'];
|
|
21
|
+
|
|
22
|
+
// Settings file gets smart-merge (overwrite forge.* keys, preserve user hooks)
|
|
23
|
+
const SETTINGS_FILE = '.claude/settings.json';
|
|
24
|
+
|
|
25
|
+
// --- Helpers ---
|
|
9
26
|
|
|
10
27
|
function copyDirRecursive(src, dest) {
|
|
11
28
|
let count = 0;
|
|
@@ -38,7 +55,132 @@ function prompt(question) {
|
|
|
38
55
|
});
|
|
39
56
|
}
|
|
40
57
|
|
|
41
|
-
|
|
58
|
+
function stampVersion(settingsPath) {
|
|
59
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
60
|
+
if (settings.forge) {
|
|
61
|
+
settings.forge.version = pkgVersion;
|
|
62
|
+
}
|
|
63
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Recursively collect all relative file paths under a directory.
|
|
68
|
+
*/
|
|
69
|
+
function collectFiles(dir, base) {
|
|
70
|
+
const results = [];
|
|
71
|
+
if (!fs.existsSync(dir)) return results;
|
|
72
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
73
|
+
const rel = path.join(base, entry.name);
|
|
74
|
+
if (entry.isDirectory()) {
|
|
75
|
+
results.push(...collectFiles(path.join(dir, entry.name), rel));
|
|
76
|
+
} else {
|
|
77
|
+
results.push(rel);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return results;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Compare and overwrite framework-owned or template-only files.
|
|
85
|
+
* Returns { updated, added, unchanged, removed } arrays of relative paths.
|
|
86
|
+
*/
|
|
87
|
+
function upgradeDir(relDir) {
|
|
88
|
+
const srcDir = path.join(templateDir, relDir);
|
|
89
|
+
const destDir = path.join(targetDir, relDir);
|
|
90
|
+
|
|
91
|
+
const result = { updated: [], added: [], unchanged: [], removed: [] };
|
|
92
|
+
|
|
93
|
+
if (!fs.existsSync(srcDir)) return result;
|
|
94
|
+
|
|
95
|
+
const srcFiles = collectFiles(srcDir, '');
|
|
96
|
+
const destFiles = new Set(collectFiles(destDir, ''));
|
|
97
|
+
|
|
98
|
+
for (const rel of srcFiles) {
|
|
99
|
+
const srcPath = path.join(srcDir, rel);
|
|
100
|
+
const destPath = path.join(destDir, rel);
|
|
101
|
+
const displayPath = path.join(relDir, rel);
|
|
102
|
+
|
|
103
|
+
if (!fs.existsSync(destPath)) {
|
|
104
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
105
|
+
fs.copyFileSync(srcPath, destPath);
|
|
106
|
+
result.added.push(displayPath);
|
|
107
|
+
} else {
|
|
108
|
+
const srcContent = fs.readFileSync(srcPath);
|
|
109
|
+
const destContent = fs.readFileSync(destPath);
|
|
110
|
+
if (Buffer.compare(srcContent, destContent) !== 0) {
|
|
111
|
+
fs.copyFileSync(srcPath, destPath);
|
|
112
|
+
result.updated.push(displayPath);
|
|
113
|
+
} else {
|
|
114
|
+
result.unchanged.push(displayPath);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Detect files in dest that are no longer in template
|
|
120
|
+
for (const rel of destFiles) {
|
|
121
|
+
const srcPath = path.join(srcDir, rel);
|
|
122
|
+
if (!fs.existsSync(srcPath)) {
|
|
123
|
+
result.removed.push(path.join(relDir, rel));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Handle merge-owned files: stage new version for manual review if different.
|
|
132
|
+
*/
|
|
133
|
+
function handleMergeFile(relFile) {
|
|
134
|
+
const srcPath = path.join(templateDir, relFile);
|
|
135
|
+
const destPath = path.join(targetDir, relFile);
|
|
136
|
+
|
|
137
|
+
if (!fs.existsSync(srcPath)) return null;
|
|
138
|
+
if (!fs.existsSync(destPath)) return null;
|
|
139
|
+
|
|
140
|
+
const srcContent = fs.readFileSync(srcPath, 'utf-8');
|
|
141
|
+
const destContent = fs.readFileSync(destPath, 'utf-8');
|
|
142
|
+
|
|
143
|
+
if (srcContent === destContent) return 'unchanged';
|
|
144
|
+
|
|
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';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Smart-merge settings.json: overwrite forge.* keys from template, preserve user hooks.
|
|
156
|
+
*/
|
|
157
|
+
function upgradeSettings() {
|
|
158
|
+
const srcPath = path.join(templateDir, SETTINGS_FILE);
|
|
159
|
+
const destPath = path.join(targetDir, SETTINGS_FILE);
|
|
160
|
+
|
|
161
|
+
if (!fs.existsSync(destPath)) return 'missing';
|
|
162
|
+
if (!fs.existsSync(srcPath)) return 'missing';
|
|
163
|
+
|
|
164
|
+
const srcSettings = JSON.parse(fs.readFileSync(srcPath, 'utf-8'));
|
|
165
|
+
const destSettings = JSON.parse(fs.readFileSync(destPath, 'utf-8'));
|
|
166
|
+
|
|
167
|
+
const before = JSON.stringify(destSettings);
|
|
168
|
+
|
|
169
|
+
// Overwrite forge.* keys from template
|
|
170
|
+
destSettings.forge = { ...destSettings.forge, ...srcSettings.forge };
|
|
171
|
+
// Always stamp current package version
|
|
172
|
+
destSettings.forge.version = pkgVersion;
|
|
173
|
+
|
|
174
|
+
const after = JSON.stringify(destSettings);
|
|
175
|
+
if (before === after) return 'unchanged';
|
|
176
|
+
|
|
177
|
+
fs.writeFileSync(destPath, JSON.stringify(destSettings, null, 2) + '\n');
|
|
178
|
+
return 'updated';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// --- Commands ---
|
|
182
|
+
|
|
183
|
+
async function install() {
|
|
42
184
|
console.log('\n Forge - Meta-prompting framework for Claude Code\n');
|
|
43
185
|
|
|
44
186
|
// Handle CLAUDE.md
|
|
@@ -94,10 +236,139 @@ async function main() {
|
|
|
94
236
|
const forgeCount = copyDirRecursive(srcForge, destForge);
|
|
95
237
|
console.log(` Installed .forge/templates/ (${forgeCount} files)`);
|
|
96
238
|
|
|
97
|
-
|
|
239
|
+
// Stamp version from package.json into settings.json
|
|
240
|
+
const settingsPath = path.join(targetDir, SETTINGS_FILE);
|
|
241
|
+
if (fs.existsSync(settingsPath)) {
|
|
242
|
+
stampVersion(settingsPath);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
console.log(`\n Forge v${pkgVersion} is ready. Start with: /forge\n`);
|
|
98
246
|
}
|
|
99
247
|
|
|
100
|
-
|
|
101
|
-
console.
|
|
102
|
-
|
|
103
|
-
|
|
248
|
+
async function upgrade() {
|
|
249
|
+
console.log('\n Forge Upgrade\n');
|
|
250
|
+
|
|
251
|
+
// Verify Forge is installed
|
|
252
|
+
const settingsPath = path.join(targetDir, SETTINGS_FILE);
|
|
253
|
+
if (!fs.existsSync(settingsPath)) {
|
|
254
|
+
console.error(
|
|
255
|
+
' Forge is not installed in this directory.\n Run `npx forge-orkes` first to install.\n'
|
|
256
|
+
);
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Read installed version
|
|
261
|
+
let installedVersion = 'unknown';
|
|
262
|
+
try {
|
|
263
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
264
|
+
installedVersion = settings.forge?.version || 'unknown';
|
|
265
|
+
} catch {
|
|
266
|
+
// proceed with unknown version
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
console.log(` Installed: v${installedVersion}`);
|
|
270
|
+
console.log(` Available: v${pkgVersion}\n`);
|
|
271
|
+
|
|
272
|
+
const results = {
|
|
273
|
+
updated: [],
|
|
274
|
+
added: [],
|
|
275
|
+
unchanged: [],
|
|
276
|
+
removed: [],
|
|
277
|
+
needsReview: [],
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// 1. Process framework-owned directories
|
|
281
|
+
for (const dir of FRAMEWORK_OWNED_DIRS) {
|
|
282
|
+
const dirResult = upgradeDir(dir);
|
|
283
|
+
results.updated.push(...dirResult.updated);
|
|
284
|
+
results.added.push(...dirResult.added);
|
|
285
|
+
results.unchanged.push(...dirResult.unchanged);
|
|
286
|
+
results.removed.push(...dirResult.removed);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// 2. Process template-only directories
|
|
290
|
+
for (const dir of TEMPLATE_ONLY_DIRS) {
|
|
291
|
+
const dirResult = upgradeDir(dir);
|
|
292
|
+
results.updated.push(...dirResult.updated);
|
|
293
|
+
results.added.push(...dirResult.added);
|
|
294
|
+
results.unchanged.push(...dirResult.unchanged);
|
|
295
|
+
results.removed.push(...dirResult.removed);
|
|
296
|
+
}
|
|
297
|
+
|
|
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
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// 4. Smart-merge settings.json
|
|
309
|
+
const settingsStatus = upgradeSettings();
|
|
310
|
+
if (settingsStatus === 'updated') {
|
|
311
|
+
results.updated.push(SETTINGS_FILE);
|
|
312
|
+
} else if (settingsStatus === 'unchanged') {
|
|
313
|
+
results.unchanged.push(SETTINGS_FILE);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Report results
|
|
317
|
+
const totalChanges =
|
|
318
|
+
results.updated.length + results.added.length + results.needsReview.length;
|
|
319
|
+
|
|
320
|
+
if (totalChanges === 0 && results.removed.length === 0) {
|
|
321
|
+
console.log(' Already up to date.\n');
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (results.updated.length > 0) {
|
|
326
|
+
console.log(` Updated (${results.updated.length}):`);
|
|
327
|
+
for (const f of results.updated) {
|
|
328
|
+
console.log(` ${f}`);
|
|
329
|
+
}
|
|
330
|
+
console.log();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (results.added.length > 0) {
|
|
334
|
+
console.log(` Added (${results.added.length}):`);
|
|
335
|
+
for (const f of results.added) {
|
|
336
|
+
console.log(` ${f}`);
|
|
337
|
+
}
|
|
338
|
+
console.log();
|
|
339
|
+
}
|
|
340
|
+
|
|
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
|
+
if (results.removed.length > 0) {
|
|
350
|
+
console.log(` Removed from template (${results.removed.length}):`);
|
|
351
|
+
for (const f of results.removed) {
|
|
352
|
+
console.log(` ${f} (still in your project — delete manually if unused)`);
|
|
353
|
+
}
|
|
354
|
+
console.log();
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
console.log(` Upgraded to v${pkgVersion}\n`);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// --- Entry point ---
|
|
361
|
+
|
|
362
|
+
const subcommand = process.argv[2];
|
|
363
|
+
|
|
364
|
+
if (subcommand === 'upgrade') {
|
|
365
|
+
upgrade().catch((err) => {
|
|
366
|
+
console.error('Error:', err.message);
|
|
367
|
+
process.exit(1);
|
|
368
|
+
});
|
|
369
|
+
} else {
|
|
370
|
+
install().catch((err) => {
|
|
371
|
+
console.error('Error:', err.message);
|
|
372
|
+
process.exit(1);
|
|
373
|
+
});
|
|
374
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"forge": {
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"default_tier": "standard",
|
|
5
5
|
"beads_integration": false,
|
|
6
6
|
"context_gates": {
|
|
@@ -10,7 +10,14 @@
|
|
|
10
10
|
"constitution_md_max_kb": 10
|
|
11
11
|
},
|
|
12
12
|
"commit_format": "{type}({scope}): {description}",
|
|
13
|
-
"commit_types": [
|
|
13
|
+
"commit_types": [
|
|
14
|
+
"feat",
|
|
15
|
+
"fix",
|
|
16
|
+
"test",
|
|
17
|
+
"refactor",
|
|
18
|
+
"chore",
|
|
19
|
+
"docs"
|
|
20
|
+
]
|
|
14
21
|
},
|
|
15
22
|
"hooks": {
|
|
16
23
|
"PostToolUse": [
|
|
@@ -23,9 +30,27 @@
|
|
|
23
30
|
"async": true
|
|
24
31
|
}
|
|
25
32
|
]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"matcher": "Skill",
|
|
36
|
+
"hooks": [
|
|
37
|
+
{
|
|
38
|
+
"type": "command",
|
|
39
|
+
"command": "mkdir -p .forge && echo \"$TOOL_INPUT\" > .forge/.active-skill"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
26
42
|
}
|
|
27
43
|
],
|
|
28
44
|
"PreToolUse": [
|
|
45
|
+
{
|
|
46
|
+
"matcher": "Write|Edit",
|
|
47
|
+
"hooks": [
|
|
48
|
+
{
|
|
49
|
+
"type": "command",
|
|
50
|
+
"command": "if [ ! -f .forge/.active-skill ]; then echo \"[Forge] No active skill. Invoke /forge or /quick-tasking before editing code. To bypass: touch .forge/.active-skill\" >&2; exit 2; fi"
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
},
|
|
29
54
|
{
|
|
30
55
|
"matcher": "Bash(git commit)",
|
|
31
56
|
"hooks": [
|
|
@@ -44,44 +44,79 @@ Read: .forge/constitution.md → active gates (if exists)
|
|
|
44
44
|
|
|
45
45
|
Check if research findings were written to files (`.forge/phases/` or similar). If so, read them. If research was inline-only (conversation context), the findings may need to be re-summarized from the user — ask briefly: *"We're picking up after the research phase. Can you summarize the key findings, or should I re-scan the relevant areas?"*
|
|
46
46
|
|
|
47
|
-
### Step 1: Present
|
|
47
|
+
### Step 1: Present Decisions with AskUserQuestion
|
|
48
48
|
|
|
49
49
|
Summarize what research found, structured around decisions the user needs to make — not a data dump.
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
**Use the `AskUserQuestion` tool for every decision point.** This gives users a clean, scannable interface instead of walls of text. You can batch up to 4 questions per `AskUserQuestion` call.
|
|
52
52
|
|
|
53
|
-
For each decision
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
53
|
+
For each decision:
|
|
54
|
+
1. Write a **brief prose intro** (2-3 sentences max) setting context for the decision — what's the problem, why does it matter.
|
|
55
|
+
2. Then immediately call `AskUserQuestion` with:
|
|
56
|
+
- `question`: The decision stated plainly, ending with `?`
|
|
57
|
+
- `header`: Short label (e.g., "Strategy", "Approach", "Scope")
|
|
58
|
+
- `options`: 2-4 realistic approaches. Each option gets:
|
|
59
|
+
- `label`: Concise name (1-5 words). Put your recommendation first with "(Recommended)" suffix.
|
|
60
|
+
- `description`: Trade-offs — what you gain and what you lose. Be honest about costs.
|
|
61
|
+
- `multiSelect`: false for mutually exclusive choices, true when combinations are valid.
|
|
58
62
|
|
|
59
|
-
|
|
63
|
+
**Batch related decisions.** If you have 3-5 decisions, group them into 1-2 `AskUserQuestion` calls (max 4 questions each) rather than asking one at a time. This lets the user see the full landscape and make coherent choices.
|
|
64
|
+
|
|
65
|
+
**When NOT to use AskUserQuestion:** For open-ended exploration questions where the answer isn't one of a few discrete options (e.g., "Walk me through the ideal user flow"), use regular prose. The tool is for decisions with concrete choices, not brainstorming.
|
|
66
|
+
|
|
67
|
+
Example structure for a discussion with 3 decisions:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
Brief context paragraph explaining the landscape from research.
|
|
71
|
+
|
|
72
|
+
→ AskUserQuestion with questions:
|
|
73
|
+
1. "Which recovery strategy should we use?" (header: "Recovery")
|
|
74
|
+
- "Sweep timer (Recommended)" / "Sweep timer + queue refactor" / ...
|
|
75
|
+
2. "Where should observability live?" (header: "Observability")
|
|
76
|
+
- "Server-side logs only" / "PostHog events" / "Both" / ...
|
|
77
|
+
3. "How should we handle the 704 contradictory records?" (header: "Data cleanup")
|
|
78
|
+
- "Migration + constraints" / "Let sweep handle it" / "Migration only" / ...
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Surface the 3-5 decisions that actually matter. Don't present a 20-item matrix.
|
|
60
82
|
|
|
61
83
|
### Step 2: Facilitate, Don't Dictate
|
|
62
84
|
|
|
63
|
-
|
|
85
|
+
After the user responds to decisions, your role is to help them think deeper — not to push your preference.
|
|
86
|
+
|
|
87
|
+
**Use `AskUserQuestion` for follow-up decisions** that emerge from their answers. Use prose for open-ended exploration.
|
|
64
88
|
|
|
65
89
|
Good facilitation patterns:
|
|
66
|
-
- *"The main tension here is between X and Y.
|
|
67
|
-
- *"Option A
|
|
68
|
-
-
|
|
69
|
-
- *"You mentioned [earlier decision] — that makes Option B a more natural fit. Does that match your thinking?"*
|
|
90
|
+
- *"The main tension here is between X and Y."* → then `AskUserQuestion` with the concrete options
|
|
91
|
+
- Referencing earlier decisions: *"You chose Option A for recovery — that makes X a more natural fit for observability."* → then `AskUserQuestion` with refined options
|
|
92
|
+
- When trade-offs need explicit weighing → `AskUserQuestion` with `description` fields that name the costs
|
|
70
93
|
|
|
71
94
|
Bad facilitation patterns:
|
|
72
|
-
- Presenting options
|
|
73
|
-
- Asking "what do you think?" without giving the user something to react to
|
|
95
|
+
- Presenting options as prose paragraphs when they could be `AskUserQuestion` choices
|
|
96
|
+
- Asking "what do you think?" without giving the user something concrete to react to
|
|
74
97
|
- Overwhelming with edge cases before the main path is clear
|
|
75
98
|
- Treating every decision as equally important
|
|
76
99
|
|
|
77
100
|
### Step 3: Probe for Hidden Constraints
|
|
78
101
|
|
|
79
|
-
Research often misses things the user knows but hasn't mentioned.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
102
|
+
Research often misses things the user knows but hasn't mentioned. Use `AskUserQuestion` for structured probes where the answer shapes the plan:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
AskUserQuestion:
|
|
106
|
+
question: "What's the timeline pressure for this work?"
|
|
107
|
+
header: "Timeline"
|
|
108
|
+
options:
|
|
109
|
+
- label: "Ship this week"
|
|
110
|
+
description: "Minimal scope, skip nice-to-haves"
|
|
111
|
+
- label: "Ship this month"
|
|
112
|
+
description: "Room for polish and edge cases"
|
|
113
|
+
- label: "No hard deadline"
|
|
114
|
+
description: "Do it right, scope is flexible"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
For open-ended probes where you need the user to explain (not choose), use prose:
|
|
118
|
+
- *"Have you tried something similar before? What went wrong?"*
|
|
119
|
+
- *"Anything you definitely want or definitely don't want?"*
|
|
85
120
|
|
|
86
121
|
One or two questions at a time. Don't interrogate.
|
|
87
122
|
|
|
@@ -136,6 +171,21 @@ Don't mechanically walk through all 5 layers for every requirement — that woul
|
|
|
136
171
|
|
|
137
172
|
Ask 2-3 questions at a time, let the user respond, then go deeper where their answers reveal uncertainty. The conversation should feel like a collaborative design session, not an interrogation.
|
|
138
173
|
|
|
174
|
+
**Use `AskUserQuestion` for behavior decisions within distillation.** When a question has discrete answers (retry vs. fail vs. alert, real-time vs. polling, roles A/B/C), use the tool. When you need the user to describe or explain something open-ended, use prose.
|
|
175
|
+
|
|
176
|
+
Example — Layer 3 question as `AskUserQuestion`:
|
|
177
|
+
```
|
|
178
|
+
question: "When the external enrichment API is down, what should the system do?"
|
|
179
|
+
header: "Failure mode"
|
|
180
|
+
options:
|
|
181
|
+
- label: "Retry with backoff (Recommended)"
|
|
182
|
+
description: "Queue retries at 1m/5m/30m intervals. Adds complexity but self-heals."
|
|
183
|
+
- label: "Fail and alert"
|
|
184
|
+
description: "Mark as failed, send alert. Simple but requires manual re-trigger."
|
|
185
|
+
- label: "Skip and continue"
|
|
186
|
+
description: "Process remaining items, revisit failures in next sweep."
|
|
187
|
+
```
|
|
188
|
+
|
|
139
189
|
**What you're listening for:**
|
|
140
190
|
|
|
141
191
|
- **Contradictions** — "It should be simple" but also "it needs to handle 12 different states." Surface these gently.
|
|
@@ -145,14 +195,25 @@ Ask 2-3 questions at a time, let the user respond, then go deeper where their an
|
|
|
145
195
|
|
|
146
196
|
### Step 5: Converge on Decisions
|
|
147
197
|
|
|
148
|
-
When the conversation has covered the key points, summarize what's been decided:
|
|
198
|
+
When the conversation has covered the key points, summarize what's been decided as a brief prose list, then use `AskUserQuestion` for final confirmation:
|
|
149
199
|
|
|
150
|
-
*"Here's where I think we've landed
|
|
200
|
+
*"Here's where I think we've landed:"*
|
|
151
201
|
- *[Decision 1]: [what was decided and why]*
|
|
152
202
|
- *[Decision 2]: [what was decided and why]*
|
|
153
203
|
- *[Open question]: [what's still unresolved and how to handle it]*
|
|
154
204
|
|
|
155
|
-
|
|
205
|
+
Then confirm with `AskUserQuestion`:
|
|
206
|
+
```
|
|
207
|
+
question: "Does this match your understanding? Ready to move to planning?"
|
|
208
|
+
header: "Confirm"
|
|
209
|
+
options:
|
|
210
|
+
- label: "Looks good, proceed"
|
|
211
|
+
description: "Lock these decisions and move to planning phase."
|
|
212
|
+
- label: "I want to adjust something"
|
|
213
|
+
description: "Revisit one or more decisions before locking."
|
|
214
|
+
- label: "More to discuss"
|
|
215
|
+
description: "There are topics we haven't covered yet."
|
|
216
|
+
```
|
|
156
217
|
|
|
157
218
|
These decisions flow into `context.md` as **Locked Decisions** when the `planning` skill runs next.
|
|
158
219
|
|
|
@@ -182,12 +243,12 @@ Don't just recite the plan back. Translate it into what it means:
|
|
|
182
243
|
|
|
183
244
|
### Step 3: Surface What's Worth Discussing
|
|
184
245
|
|
|
185
|
-
Don't wait for the user to spot issues. Proactively surface
|
|
246
|
+
Don't wait for the user to spot issues. Proactively surface concerns, then **use `AskUserQuestion` for any that have discrete choices:**
|
|
186
247
|
|
|
187
|
-
- **
|
|
188
|
-
- **
|
|
189
|
-
- **Risks the plan doesn't address**
|
|
190
|
-
- **
|
|
248
|
+
- **Decisions that could go either way** → `AskUserQuestion` with the options and trade-offs
|
|
249
|
+
- **Scope questions** → `AskUserQuestion` (e.g., "Ship admin features in v1?" with "Yes, include" / "Defer to v2" options)
|
|
250
|
+
- **Risks the plan doesn't address** → `AskUserQuestion` (e.g., "Worth adding a fallback?" with "Add fallback" / "Accept risk" options)
|
|
251
|
+
- **Assumptions you're not confident about** → Prose, since these need the user to confirm or correct rather than choose
|
|
191
252
|
|
|
192
253
|
### Step 4: Drill into Functionality
|
|
193
254
|
|
|
@@ -201,23 +262,43 @@ This is where post-planning discussion earns its keep — the plan makes the fea
|
|
|
201
262
|
|
|
202
263
|
### Step 5: Discuss and Revise Direction
|
|
203
264
|
|
|
204
|
-
The user may want to:
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
265
|
+
The user may want to change approach, adjust scope, reorder priorities, ask questions, or approve as-is. Use `AskUserQuestion` to give them a clear way to signal their intent:
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
question: "How would you like to proceed with this plan?"
|
|
269
|
+
header: "Direction"
|
|
270
|
+
options:
|
|
271
|
+
- label: "Approve as-is"
|
|
272
|
+
description: "Lock decisions and move to execution."
|
|
273
|
+
- label: "Adjust scope"
|
|
274
|
+
description: "Defer or add features before building."
|
|
275
|
+
- label: "Change approach"
|
|
276
|
+
description: "Revisit a technical decision in the plan."
|
|
277
|
+
- label: "More questions"
|
|
278
|
+
description: "I want to discuss specific parts further."
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Based on their response, either drill deeper with follow-up `AskUserQuestion` calls or move to summarizing.
|
|
210
282
|
|
|
211
283
|
### Step 6: Summarize Changes
|
|
212
284
|
|
|
213
|
-
If the discussion produced changes to the plan direction:
|
|
285
|
+
If the discussion produced changes to the plan direction, summarize as prose:
|
|
214
286
|
|
|
215
287
|
*"Based on our discussion:*
|
|
216
288
|
- *[Change 1]: [what changed and why]*
|
|
217
289
|
- *[Change 2]: [what changed and why]*
|
|
218
290
|
- *[Unchanged]: [what stays the same]*
|
|
219
291
|
|
|
220
|
-
|
|
292
|
+
Then confirm next steps with `AskUserQuestion`:
|
|
293
|
+
```
|
|
294
|
+
question: "Ready to update the plans, or more to discuss?"
|
|
295
|
+
header: "Next step"
|
|
296
|
+
options:
|
|
297
|
+
- label: "Update plans"
|
|
298
|
+
description: "Re-plan affected areas with the revised decisions."
|
|
299
|
+
- label: "More to discuss"
|
|
300
|
+
description: "There are topics we haven't covered yet."
|
|
301
|
+
```
|
|
221
302
|
|
|
222
303
|
If re-planning is needed, route back to the `planning` skill with the discussion summary as input. The planning skill will update plans, requirements, and context.md accordingly.
|
|
223
304
|
|
|
@@ -481,6 +481,12 @@ Match ANY:
|
|
|
481
481
|
→ Add `designing` if UI work involved
|
|
482
482
|
→ Add `securing` if auth/data/API touched
|
|
483
483
|
|
|
484
|
+
### Direct Utility Skills
|
|
485
|
+
Match ANY:
|
|
486
|
+
- User says "upgrade", "update forge", "sync forge"
|
|
487
|
+
|
|
488
|
+
→ Route to `upgrading` skill (bypasses tier detection)
|
|
489
|
+
|
|
484
490
|
### User Override
|
|
485
491
|
If user explicitly says "Use Quick/Standard/Full tier" — honor it. No arguments.
|
|
486
492
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: upgrading
|
|
3
|
+
description: "Sync Forge framework files from a local dev repo or NPM. Use when developing Forge itself or applying updates to an installed project."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Upgrading: Local Dev Sync
|
|
7
|
+
|
|
8
|
+
Sync framework files from a local Forge source repo into the current project. Use this during Forge development to test changes without publishing to NPM.
|
|
9
|
+
|
|
10
|
+
For published upgrades, use `npx forge-orkes upgrade` instead.
|
|
11
|
+
|
|
12
|
+
## Step 1: Resolve Source Path
|
|
13
|
+
|
|
14
|
+
Check if `.forge/dev-source` exists in the project root.
|
|
15
|
+
|
|
16
|
+
- **If it exists:** read the path from the file (first line, trimmed). Verify the path exists and contains `packages/create-forge/template/`.
|
|
17
|
+
- **If it doesn't exist:** ask the user: *"Where is your local Forge repo? (e.g., ~/Dev/forge)"*
|
|
18
|
+
- Validate the path has `packages/create-forge/template/`
|
|
19
|
+
- Save the path to `.forge/dev-source` for next time
|
|
20
|
+
|
|
21
|
+
The template directory is `{source}/packages/create-forge/template/`.
|
|
22
|
+
|
|
23
|
+
## Step 2: File Classification
|
|
24
|
+
|
|
25
|
+
Files are classified into three categories:
|
|
26
|
+
|
|
27
|
+
| Category | Paths | Behavior |
|
|
28
|
+
|----------|-------|----------|
|
|
29
|
+
| **Framework-owned** | `.claude/agents/*.md`, `.claude/skills/*/SKILL.md` | Overwrite — these are Forge's |
|
|
30
|
+
| **Merge-owned** | `CLAUDE.md`, `.claude/settings.json` | Never auto-overwrite |
|
|
31
|
+
| **Template-only** | `.forge/templates/**` | Overwrite — reference templates |
|
|
32
|
+
|
|
33
|
+
**Never touch** user-generated files: `.forge/project.yml`, `.forge/state/`, `.forge/constitution.md`, `.forge/context.md`, `.forge/requirements.yml`, `.forge/roadmap.yml`, `.forge/design-system.md`, `.forge/refactor-backlog.yml`.
|
|
34
|
+
|
|
35
|
+
## Step 3: Sync Framework-Owned Files
|
|
36
|
+
|
|
37
|
+
For each framework-owned file in the source template:
|
|
38
|
+
|
|
39
|
+
1. Read the source file content
|
|
40
|
+
2. Read the local file content (if it exists)
|
|
41
|
+
3. If different → overwrite local with source, report as **updated**
|
|
42
|
+
4. If same → report as **unchanged**
|
|
43
|
+
5. If source has a new file not in local → copy it, report as **added**
|
|
44
|
+
6. If local has a file not in source → report as **removed from template** (don't delete — let user decide)
|
|
45
|
+
|
|
46
|
+
## Step 4: Sync Template-Only Files
|
|
47
|
+
|
|
48
|
+
Same process as Step 3, but for `.forge/templates/**`.
|
|
49
|
+
|
|
50
|
+
## Step 5: Handle Merge-Owned Files
|
|
51
|
+
|
|
52
|
+
For `CLAUDE.md`:
|
|
53
|
+
1. Read source and local versions
|
|
54
|
+
2. If different → **do not overwrite**. Instead, summarize what changed in prose (new sections, removed sections, modified text)
|
|
55
|
+
3. Present the summary to the user and let them decide how to merge
|
|
56
|
+
|
|
57
|
+
For `.claude/settings.json`:
|
|
58
|
+
1. Read source and local versions
|
|
59
|
+
2. Compare the `forge.*` keys only
|
|
60
|
+
3. If forge keys differ → update only `forge.*` keys in local, preserve user's `hooks` and any other custom keys
|
|
61
|
+
4. Update `forge.version` to match the source package version
|
|
62
|
+
|
|
63
|
+
## Step 6: Report
|
|
64
|
+
|
|
65
|
+
Present a summary:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
Forge Local Sync Complete
|
|
69
|
+
─────────────────────────
|
|
70
|
+
Source: {path}
|
|
71
|
+
Version: {old} → {new}
|
|
72
|
+
|
|
73
|
+
Updated: {N} files
|
|
74
|
+
- .claude/skills/executing/SKILL.md
|
|
75
|
+
- ...
|
|
76
|
+
|
|
77
|
+
Added: {N} files
|
|
78
|
+
- .claude/skills/new-skill/SKILL.md
|
|
79
|
+
- ...
|
|
80
|
+
|
|
81
|
+
Removed from template: {N} files
|
|
82
|
+
- .claude/agents/old-agent.md (still in your project)
|
|
83
|
+
- ...
|
|
84
|
+
|
|
85
|
+
Needs manual review: {N} files
|
|
86
|
+
- CLAUDE.md (new sections: "Upgrade Mechanism", modified: "Skill Routing")
|
|
87
|
+
- ...
|
|
88
|
+
|
|
89
|
+
Unchanged: {N} files
|
|
90
|
+
```
|
package/template/CLAUDE.md
CHANGED
|
@@ -46,6 +46,7 @@ Forge auto-detects complexity. Override with: "Use Quick/Standard/Full tier."
|
|
|
46
46
|
| Build UI with design system consistency | `designing` | When UI involved |
|
|
47
47
|
| Review security before shipping | `securing` | When auth/data/API involved |
|
|
48
48
|
| Debug systematically with hypotheses | `debugging` | When stuck |
|
|
49
|
+
| Upgrade Forge framework files | `upgrading` | On-demand |
|
|
49
50
|
| Use Beads for cross-session memory | `beads-integration` | When Beads installed |
|
|
50
51
|
|
|
51
52
|
## Context Engineering
|