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.
@@ -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
- async function main() {
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
- console.log('\n Forge is ready. Start with: /forge\n');
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
- main().catch((err) => {
101
- console.error('Error:', err.message);
102
- process.exit(1);
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
  "name": "forge-orkes",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
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,6 +1,6 @@
1
1
  {
2
2
  "forge": {
3
- "version": "0.1.0",
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": ["feat", "fix", "test", "refactor", "chore", "docs"]
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 the Landscape
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
- *"Based on what I found, here are the key decisions before we plan:"*
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 point:
54
- - **What needs deciding** — the question, stated plainly
55
- - **Options** — 2-3 realistic approaches (not exhaustive lists)
56
- - **Trade-offs** what you gain and what you lose with each
57
- - **Recommendation** if you have one, say so and say why. If you don't, say that too.
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
- Keep it conversational. Don't present a 20-item matrix. Surface the 3-5 decisions that actually matter for this work.
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
- Your role is to help the user think, not to push them toward your preference.
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. Which matters more for your project?"*
67
- - *"Option A is simpler now but harder to change later. Option B is more work upfront but more flexible. What's your timeline pressure like?"*
68
- - *"I'd lean toward X because [reason], but Y makes sense if [condition]. What's your read?"*
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 without trade-offs (just a list)
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. Ask about:
80
- - **Timeline pressure** — does this need to ship by a date?
81
- - **Audience/users** — who actually uses this? (affects complexity trade-offs)
82
- - **Future direction** — is this a throwaway or the foundation for more?
83
- - **Past experience** have they tried something similar before? What went wrong?
84
- - **Strong preferences** — anything they definitely want or definitely don't want?
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
- *Does this match your understanding? If so, I'll carry these into planning."*
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
- - **Assumptions you're not confident about** "Plan 01 assumes the API returns paginated results. I didn't verify this."
188
- - **Decisions that could go either way** — "I split this into 3 plans for parallelism, but you could also do it as 2 larger plans if you prefer fewer context switches."
189
- - **Risks the plan doesn't address** "There's no fallback if the external API is slow. Worth adding, or accept the risk?"
190
- - **Scope questions** "Plan 03 includes admin-only features. Ship those in v1, or defer?"
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
- - **Change approach** — "Let's use WebSockets instead of polling." → Note this. Planning skill will rebuild the affected plans.
206
- - **Adjust scope** — "Defer the admin features." → Note this for deferred items.
207
- - **Reorder priorities** "Do the dashboard before the settings page." → Note the new wave order.
208
- - **Ask questions** — "What happens if we skip the caching layer?" → Discuss implications honestly.
209
- - **Approve as-is** — "Looks good, proceed." → Move to executing.
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
- *Next step: I'll update the plans to reflect this. Want me to proceed with re-planning, or is there more to discuss?"*
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
+ ```
@@ -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