gameforge-cli 0.1.0 → 0.2.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.
Files changed (88) hide show
  1. package/README.md +117 -44
  2. package/dist/agents/base/BaseAgent.d.ts +31 -0
  3. package/dist/agents/base/BaseAgent.d.ts.map +1 -1
  4. package/dist/agents/base/BaseAgent.js +57 -0
  5. package/dist/agents/base/BaseAgent.js.map +1 -1
  6. package/dist/agents/core/Architect.d.ts +21 -5
  7. package/dist/agents/core/Architect.d.ts.map +1 -1
  8. package/dist/agents/core/Architect.js +413 -150
  9. package/dist/agents/core/Architect.js.map +1 -1
  10. package/dist/agents/core/Chaos.d.ts +4 -0
  11. package/dist/agents/core/Chaos.d.ts.map +1 -1
  12. package/dist/agents/core/Chaos.js +46 -11
  13. package/dist/agents/core/Chaos.js.map +1 -1
  14. package/dist/agents/core/Consistency.d.ts +1 -0
  15. package/dist/agents/core/Consistency.d.ts.map +1 -1
  16. package/dist/agents/core/Consistency.js +86 -11
  17. package/dist/agents/core/Consistency.js.map +1 -1
  18. package/dist/agents/core/DocumentUpdater.d.ts +13 -0
  19. package/dist/agents/core/DocumentUpdater.d.ts.map +1 -0
  20. package/dist/agents/core/DocumentUpdater.js +165 -0
  21. package/dist/agents/core/DocumentUpdater.js.map +1 -0
  22. package/dist/agents/core/Modifier.d.ts +13 -0
  23. package/dist/agents/core/Modifier.d.ts.map +1 -0
  24. package/dist/agents/core/Modifier.js +141 -0
  25. package/dist/agents/core/Modifier.js.map +1 -0
  26. package/dist/agents/core/Remediation.d.ts +3 -1
  27. package/dist/agents/core/Remediation.d.ts.map +1 -1
  28. package/dist/agents/core/Remediation.js +63 -3
  29. package/dist/agents/core/Remediation.js.map +1 -1
  30. package/dist/agents/specialists/CreativeSpecialist.d.ts.map +1 -1
  31. package/dist/agents/specialists/CreativeSpecialist.js +162 -25
  32. package/dist/agents/specialists/CreativeSpecialist.js.map +1 -1
  33. package/dist/agents/specialists/EntitySpecialist.d.ts.map +1 -1
  34. package/dist/agents/specialists/EntitySpecialist.js +79 -25
  35. package/dist/agents/specialists/EntitySpecialist.js.map +1 -1
  36. package/dist/agents/specialists/FeatureSpecialist.d.ts +4 -0
  37. package/dist/agents/specialists/FeatureSpecialist.d.ts.map +1 -1
  38. package/dist/agents/specialists/FeatureSpecialist.js +114 -39
  39. package/dist/agents/specialists/FeatureSpecialist.js.map +1 -1
  40. package/dist/agents/specialists/TechSpecialist.d.ts.map +1 -1
  41. package/dist/agents/specialists/TechSpecialist.js +169 -32
  42. package/dist/agents/specialists/TechSpecialist.js.map +1 -1
  43. package/dist/config/schema.d.ts +1319 -709
  44. package/dist/config/schema.d.ts.map +1 -1
  45. package/dist/config/schema.js +142 -52
  46. package/dist/config/schema.js.map +1 -1
  47. package/dist/config/templates.d.ts.map +1 -1
  48. package/dist/config/templates.js +6 -66
  49. package/dist/config/templates.js.map +1 -1
  50. package/dist/core/Orchestrator.d.ts +17 -3
  51. package/dist/core/Orchestrator.d.ts.map +1 -1
  52. package/dist/core/Orchestrator.js +46 -16
  53. package/dist/core/Orchestrator.js.map +1 -1
  54. package/dist/index.js +544 -226
  55. package/dist/index.js.map +1 -1
  56. package/dist/types/issueReview.d.ts +19 -0
  57. package/dist/types/issueReview.d.ts.map +1 -0
  58. package/dist/types/issueReview.js +3 -0
  59. package/dist/types/issueReview.js.map +1 -0
  60. package/dist/utils/costTracker.d.ts +28 -0
  61. package/dist/utils/costTracker.d.ts.map +1 -1
  62. package/dist/utils/costTracker.js +71 -1
  63. package/dist/utils/costTracker.js.map +1 -1
  64. package/dist/utils/disambiguationHelper.d.ts +54 -0
  65. package/dist/utils/disambiguationHelper.d.ts.map +1 -0
  66. package/dist/utils/disambiguationHelper.js +262 -0
  67. package/dist/utils/disambiguationHelper.js.map +1 -0
  68. package/dist/utils/fileManager.d.ts +7 -0
  69. package/dist/utils/fileManager.d.ts.map +1 -1
  70. package/dist/utils/fileManager.js +47 -0
  71. package/dist/utils/fileManager.js.map +1 -1
  72. package/dist/utils/issueReviewer.d.ts +10 -0
  73. package/dist/utils/issueReviewer.d.ts.map +1 -0
  74. package/dist/utils/issueReviewer.js +206 -0
  75. package/dist/utils/issueReviewer.js.map +1 -0
  76. package/dist/utils/issueSelector.d.ts +26 -0
  77. package/dist/utils/issueSelector.d.ts.map +1 -0
  78. package/dist/utils/issueSelector.js +132 -0
  79. package/dist/utils/issueSelector.js.map +1 -0
  80. package/dist/utils/pdfGenerator.d.ts +12 -0
  81. package/dist/utils/pdfGenerator.d.ts.map +1 -0
  82. package/dist/utils/pdfGenerator.js +341 -0
  83. package/dist/utils/pdfGenerator.js.map +1 -0
  84. package/package.json +20 -15
  85. package/dist/core/CheckpointManager.d.ts +0 -16
  86. package/dist/core/CheckpointManager.d.ts.map +0 -1
  87. package/dist/core/CheckpointManager.js +0 -52
  88. package/dist/core/CheckpointManager.js.map +0 -1
package/dist/index.js CHANGED
@@ -29,7 +29,20 @@ const TechSpecialist_1 = require("./agents/specialists/TechSpecialist");
29
29
  const CreativeSpecialist_1 = require("./agents/specialists/CreativeSpecialist");
30
30
  const Orchestrator_1 = require("./core/Orchestrator");
31
31
  const debugLogger_1 = require("./utils/debugLogger");
32
+ const issueReviewer_1 = require("./utils/issueReviewer");
33
+ const Modifier_1 = require("./agents/core/Modifier");
34
+ const DocumentUpdater_1 = require("./agents/core/DocumentUpdater");
32
35
  dotenv_1.default.config();
36
+ /**
37
+ * Creates a clickable file link for terminals that support OSC 8 hyperlinks.
38
+ * Falls back to plain text in unsupported terminals.
39
+ */
40
+ function fileLink(filePath, displayText) {
41
+ const absolutePath = path_1.default.resolve(filePath);
42
+ const fileUrl = `file://${absolutePath}`;
43
+ const text = displayText || filePath;
44
+ return `\x1b]8;;${fileUrl}\x1b\\${text}\x1b]8;;\x1b\\`;
45
+ }
33
46
  const program = new commander_1.Command();
34
47
  program
35
48
  .name('gameforge')
@@ -77,6 +90,13 @@ program
77
90
  });
78
91
  console.log(picocolors_1.default.dim('Usage: gameforge create --template <id>\n'));
79
92
  });
93
+ program
94
+ .command('modify <sessionId>')
95
+ .description('Modify an existing game design document')
96
+ .option('-d, --debug', 'Enable debug logging to file')
97
+ .action(async (sessionId, options) => {
98
+ await runModifyFlow(sessionId, options);
99
+ });
80
100
  program.parse();
81
101
  async function runCreationFlow(options) {
82
102
  console.clear();
@@ -142,29 +162,50 @@ async function runCreationFlow(options) {
142
162
  // Phase 1: Discovery
143
163
  console.log(picocolors_1.default.cyan('\n📋 Phase 1: Discovery\n'));
144
164
  stateMachine.transition(StateMachine_1.GameForgePhase.DISCOVERY);
165
+ costTracker.startPhase('discovery');
145
166
  const inquisitor = new Inquisitor_1.Inquisitor(apiKey, costTracker, modelSelector);
146
167
  const questions = getInquisitorQuestions(options.template);
147
168
  const transcript = await inquisitor.execute(questions);
148
169
  transcript.forEach(entry => {
149
170
  stateMachine.addTranscriptEntry(entry.question, entry.answer, entry.autoGenerated);
150
171
  });
172
+ // End discovery phase and show cost
173
+ const discoveryCost = costTracker.endPhase();
174
+ if (discoveryCost && discoveryCost.operations > 0) {
175
+ console.log(picocolors_1.default.green('\n✓ Discovery complete'));
176
+ costTracker.printPhaseSummary(discoveryCost);
177
+ }
151
178
  // Gate 1: Review Concept
152
179
  console.log(picocolors_1.default.cyan('\n📊 Concept Review\n'));
153
180
  stateMachine.transition(StateMachine_1.GameForgePhase.REVIEW_CONCEPT);
154
181
  showConceptBrief(transcript);
155
- const proceed1 = await confirmGate('Proceed to architecture phase?');
156
- if (!proceed1) {
157
- console.log(picocolors_1.default.yellow('Exiting...'));
158
- return;
159
- }
160
182
  await sessionManager.saveCheckpoint(sessionId, stateMachine.getState(), 'after_discovery');
161
183
  // Phase 2: Architecture
162
184
  console.log(picocolors_1.default.cyan('\n🏗️ Phase 2: Architecture\n'));
163
185
  stateMachine.transition(StateMachine_1.GameForgePhase.ARCHITECTURE);
186
+ costTracker.startPhase('architecture');
164
187
  const spinner = (0, ora_1.default)('Generating Game Bible...').start();
165
188
  const architect = new Architect_1.Architect(apiKey, costTracker, modelSelector);
189
+ // Provide spinner control so the agent can pause during user prompts
190
+ let spinnerPaused = false;
191
+ architect.setSpinnerControl({
192
+ pause: () => {
193
+ if (!spinnerPaused) {
194
+ spinner.stop();
195
+ spinnerPaused = true;
196
+ }
197
+ },
198
+ resume: (text) => {
199
+ if (spinnerPaused) {
200
+ spinner.start(text || spinner.text);
201
+ spinnerPaused = false;
202
+ }
203
+ }
204
+ });
166
205
  let bible = await architect.execute(transcript, (message) => {
167
- spinner.text = `Generating Game Bible: ${message}`;
206
+ if (!spinnerPaused) {
207
+ spinner.text = `Generating Game Bible: ${message}`;
208
+ }
168
209
  });
169
210
  // Merge template features if a template was selected
170
211
  if (selectedTemplate && selectedTemplate.partialBible.features) {
@@ -173,6 +214,11 @@ async function runCreationFlow(options) {
173
214
  }
174
215
  stateMachine.updateBible(bible);
175
216
  spinner.succeed('Game Bible generated');
217
+ // End architecture phase and show cost
218
+ const architectureCost = costTracker.endPhase();
219
+ if (architectureCost) {
220
+ costTracker.printPhaseSummary(architectureCost);
221
+ }
176
222
  // Save checkpoint
177
223
  await sessionManager.saveCheckpoint(sessionId, stateMachine.getState(), 'after_architecture');
178
224
  // Gate 2: Review Data
@@ -180,15 +226,11 @@ async function runCreationFlow(options) {
180
226
  stateMachine.transition(StateMachine_1.GameForgePhase.REVIEW_DATA);
181
227
  console.log(picocolors_1.default.green(`✓ Generated ${bible.features.length} features`));
182
228
  console.log(picocolors_1.default.green(`✓ Generated ${bible.gameObjects.length} game objects`));
183
- const proceed2 = await confirmGate('Proceed to production phase?');
184
- if (!proceed2) {
185
- console.log(picocolors_1.default.yellow('Exiting...'));
186
- return;
187
- }
188
229
  // Phase 3: Production (Specialists)
189
230
  console.log(picocolors_1.default.cyan('\n🎨 Phase 3: Production\n'));
190
231
  stateMachine.transition(StateMachine_1.GameForgePhase.PRODUCTION);
191
- const orchestrator = new Orchestrator_1.SpecialistOrchestrator(2); // Concurrency of 2
232
+ costTracker.startPhase('production');
233
+ const orchestrator = new Orchestrator_1.SpecialistOrchestrator();
192
234
  const specialists = [
193
235
  { name: 'Creative Direction', agent: new CreativeSpecialist_1.CreativeSpecialist(apiKey, costTracker, modelSelector) },
194
236
  { name: 'Feature Specifications', agent: new FeatureSpecialist_1.FeatureSpecialist(apiKey, costTracker, modelSelector) },
@@ -196,9 +238,16 @@ async function runCreationFlow(options) {
196
238
  { name: 'Technical Specifications', agent: new TechSpecialist_1.TechSpecialist(apiKey, costTracker, modelSelector) }
197
239
  ];
198
240
  let markdownSections = await orchestrator.runAllSpecialists(bible, specialists);
241
+ // End production phase and show cost
242
+ const productionCost = costTracker.endPhase();
243
+ if (productionCost) {
244
+ console.log(picocolors_1.default.green('\n✓ Production complete'));
245
+ costTracker.printPhaseSummary(productionCost);
246
+ }
199
247
  // Phase 4: Validation
200
248
  console.log(picocolors_1.default.cyan('\n✅ Phase 4: Validation\n'));
201
249
  stateMachine.transition(StateMachine_1.GameForgePhase.VALIDATION);
250
+ costTracker.startPhase('validation');
202
251
  // Run consistency check
203
252
  const consistencyAgent = new Consistency_1.ConsistencyAgent();
204
253
  let currentConsistencyIssues = consistencyAgent.validate(bible);
@@ -206,124 +255,137 @@ async function runCreationFlow(options) {
206
255
  // Run chaos analysis
207
256
  let chaosSpinner = (0, ora_1.default)('Running Chaos Agent...').start();
208
257
  const chaosAgent = new Chaos_1.Chaos(apiKey, costTracker, modelSelector);
258
+ // Provide spinner control so the agent can pause during user prompts
259
+ let chaosSpinnerPaused = false;
260
+ chaosAgent.setSpinnerControl({
261
+ pause: () => {
262
+ if (!chaosSpinnerPaused) {
263
+ chaosSpinner.stop();
264
+ chaosSpinnerPaused = true;
265
+ }
266
+ },
267
+ resume: (text) => {
268
+ if (chaosSpinnerPaused) {
269
+ chaosSpinner.start(text || chaosSpinner.text);
270
+ chaosSpinnerPaused = false;
271
+ }
272
+ }
273
+ });
209
274
  let currentChaosIssues = await chaosAgent.execute(bible, (message) => {
210
- chaosSpinner.text = `Chaos Agent: ${message}`;
275
+ if (!chaosSpinnerPaused) {
276
+ chaosSpinner.text = `Chaos Agent: ${message}`;
277
+ }
211
278
  });
212
279
  chaosSpinner.succeed(`Found ${currentChaosIssues.length} design issues`);
213
- // Gate 3: Review Issues with Fix Option
214
- const MAX_FIX_ATTEMPTS = 3;
215
- let fixAttempts = 0;
280
+ // End validation phase and show cost
281
+ const validationCost = costTracker.endPhase();
282
+ if (validationCost) {
283
+ costTracker.printPhaseSummary(validationCost);
284
+ }
285
+ // Gate 3: Review Issues One by One
286
+ stateMachine.transition(StateMachine_1.GameForgePhase.REVIEW_ISSUES);
216
287
  let fixesApplied = false;
217
- while (true) {
218
- stateMachine.transition(StateMachine_1.GameForgePhase.REVIEW_ISSUES);
219
- const totalIssues = currentConsistencyIssues.length + currentChaosIssues.length;
220
- if (totalIssues > 0) {
221
- console.log(picocolors_1.default.yellow(`\n⚠️ ${totalIssues} issues found:`));
222
- currentConsistencyIssues.slice(0, 3).forEach((issue) => {
223
- console.log(picocolors_1.default.dim(` - [${issue.severity}] ${issue.message}`));
224
- });
225
- currentChaosIssues.slice(0, 3).forEach((issue) => {
226
- console.log(picocolors_1.default.dim(` - [${issue.severity}] ${issue.description}`));
227
- });
228
- if (totalIssues > 6) {
229
- console.log(picocolors_1.default.dim(` ... and ${totalIssues - 6} more issues`));
288
+ let gapAnalysisConsistencyIssues = currentConsistencyIssues;
289
+ let gapAnalysisChaosIssues = currentChaosIssues;
290
+ const totalIssues = currentConsistencyIssues.length + currentChaosIssues.length;
291
+ if (totalIssues > 0) {
292
+ console.log(picocolors_1.default.yellow(`\n⚠️ ${totalIssues} issues found. Let's review them one by one.\n`));
293
+ // Review each issue one by one
294
+ const reviewed = await (0, issueReviewer_1.reviewIssuesOneByOne)(currentConsistencyIssues, currentChaosIssues);
295
+ // If user selected any issues to fix
296
+ if (reviewed.toFix.length > 0) {
297
+ const proceed = await confirmGate(`Fix ${reviewed.toFix.length} selected issue(s)?`);
298
+ if (proceed) {
299
+ fixesApplied = true;
300
+ console.log(picocolors_1.default.cyan(`\n🔧 Fixing ${reviewed.toFix.length} issues...\n`));
301
+ costTracker.startPhase('remediation');
302
+ const remediationSpinner = (0, ora_1.default)('Running Remediation Agent...').start();
303
+ const remediationAgent = new Remediation_1.Remediation(apiKey, costTracker, modelSelector);
304
+ try {
305
+ // Convert to IssueWithNotes format for Remediation
306
+ const issuesToFix = (0, issueReviewer_1.convertToIssuesWithNotes)(reviewed);
307
+ const result = await remediationAgent.execute(bible, issuesToFix, (message) => {
308
+ remediationSpinner.text = `Remediation: ${message}`;
309
+ });
310
+ remediationSpinner.succeed(`Fixed ${result.fixedIssues.length} issues`);
311
+ // End remediation phase and show cost
312
+ const remediationCost = costTracker.endPhase();
313
+ if (remediationCost) {
314
+ costTracker.printPhaseSummary(remediationCost);
315
+ }
316
+ if (result.fixedIssues.length > 0) {
317
+ console.log(picocolors_1.default.green('\nFixes applied:'));
318
+ result.fixedIssues.forEach((fix) => {
319
+ console.log(picocolors_1.default.dim(` ✓ ${fix}`));
320
+ });
321
+ }
322
+ if (result.unfixableIssues.length > 0) {
323
+ console.log(picocolors_1.default.yellow('\nCould not fix:'));
324
+ result.unfixableIssues.forEach((issue) => {
325
+ console.log(picocolors_1.default.dim(` ✗ ${issue}`));
326
+ });
327
+ }
328
+ // Update bible
329
+ bible = result.updatedBible;
330
+ stateMachine.updateBible(bible);
331
+ // Filter issues for gap analysis: only show unfixable issues
332
+ const filtered = (0, issueReviewer_1.filterIssuesForGapAnalysis)(currentConsistencyIssues, currentChaosIssues, reviewed, result.unfixableIssues);
333
+ gapAnalysisConsistencyIssues = filtered.consistencyIssues;
334
+ gapAnalysisChaosIssues = filtered.chaosIssues;
335
+ }
336
+ catch (error) {
337
+ remediationSpinner.fail('Remediation failed');
338
+ console.error(picocolors_1.default.red('Error during remediation:'), error);
339
+ const continueAnyway = await confirmGate('Continue to save despite remediation failure?');
340
+ if (!continueAnyway) {
341
+ console.log(picocolors_1.default.yellow('Exiting...'));
342
+ return;
343
+ }
344
+ // Remediation failed - show all issues that were marked for fixing as unfixable
345
+ const filtered = (0, issueReviewer_1.filterIssuesForGapAnalysis)(currentConsistencyIssues, currentChaosIssues, reviewed, [] // Treat all toFix issues as unfixable since remediation failed
346
+ );
347
+ // Show the issues that were marked to fix since they remain unfixed
348
+ gapAnalysisConsistencyIssues = reviewed.toFix
349
+ .filter(r => r.issueType === 'consistency')
350
+ .map(r => r.issue);
351
+ gapAnalysisChaosIssues = reviewed.toFix
352
+ .filter(r => r.issueType === 'chaos')
353
+ .map(r => r.issue);
354
+ }
230
355
  }
231
- if (fixAttempts > 0) {
232
- console.log(picocolors_1.default.cyan(`\n(Fix attempt ${fixAttempts}/${MAX_FIX_ATTEMPTS} complete)`));
356
+ else {
357
+ // User declined to run remediation - show issues marked to fix as they remain unfixed
358
+ gapAnalysisConsistencyIssues = reviewed.toFix
359
+ .filter(r => r.issueType === 'consistency')
360
+ .map(r => r.issue);
361
+ gapAnalysisChaosIssues = reviewed.toFix
362
+ .filter(r => r.issueType === 'chaos')
363
+ .map(r => r.issue);
233
364
  }
234
365
  }
235
366
  else {
236
- console.log(picocolors_1.default.green('\n✓ All issues resolved!'));
237
- }
238
- // Determine action based on state
239
- let action;
240
- if (totalIssues === 0) {
241
- const proceed = await confirmGate('Save and export GDD?');
242
- action = proceed ? 'save' : 'exit';
243
- }
244
- else if (fixAttempts >= MAX_FIX_ATTEMPTS) {
245
- console.log(picocolors_1.default.yellow(`\nMaximum fix attempts (${MAX_FIX_ATTEMPTS}) reached.`));
246
- const proceed = await confirmGate('Save and export GDD with remaining issues?');
247
- action = proceed ? 'save' : 'exit';
248
- }
249
- else {
250
- action = await selectReviewAction();
251
- }
252
- if (action === 'exit') {
253
- console.log(picocolors_1.default.yellow('Exiting...'));
254
- return;
255
- }
256
- if (action === 'save') {
257
- break;
258
- }
259
- // action === 'fix': Run remediation
260
- fixAttempts++;
261
- fixesApplied = true;
262
- console.log(picocolors_1.default.cyan(`\n🔧 Fixing issues (attempt ${fixAttempts}/${MAX_FIX_ATTEMPTS})...\n`));
263
- const remediationSpinner = (0, ora_1.default)('Running Remediation Agent...').start();
264
- const remediationAgent = new Remediation_1.Remediation(apiKey, costTracker, modelSelector);
265
- try {
266
- const result = await remediationAgent.execute(bible, currentConsistencyIssues, currentChaosIssues, (message) => { remediationSpinner.text = `Remediation: ${message}`; });
267
- remediationSpinner.succeed(`Fixed ${result.fixedIssues.length} issues`);
268
- if (result.fixedIssues.length > 0) {
269
- console.log(picocolors_1.default.green('\nFixes applied:'));
270
- result.fixedIssues.forEach(fix => {
271
- console.log(picocolors_1.default.dim(` ✓ ${fix}`));
272
- });
273
- }
274
- if (result.unfixableIssues.length > 0) {
275
- console.log(picocolors_1.default.yellow('\nCould not fix:'));
276
- result.unfixableIssues.forEach(issue => {
277
- console.log(picocolors_1.default.dim(` ✗ ${issue}`));
278
- });
279
- }
280
- // Update bible
281
- bible = result.updatedBible;
282
- stateMachine.updateBible(bible);
283
- // Transition back to production for re-validation
284
- stateMachine.transition(StateMachine_1.GameForgePhase.PRODUCTION);
285
- // Re-run validation
286
- console.log(picocolors_1.default.cyan('\n🔄 Re-validating...\n'));
287
- stateMachine.transition(StateMachine_1.GameForgePhase.VALIDATION);
288
- const newConsistencyAgent = new Consistency_1.ConsistencyAgent();
289
- currentConsistencyIssues = newConsistencyAgent.validate(bible);
290
- console.log(picocolors_1.default.yellow(`Found ${currentConsistencyIssues.length} consistency issues`));
291
- chaosSpinner = (0, ora_1.default)('Re-running Chaos Agent...').start();
292
- const newChaosAgent = new Chaos_1.Chaos(apiKey, costTracker, modelSelector);
293
- currentChaosIssues = await newChaosAgent.execute(bible, (message) => {
294
- chaosSpinner.text = `Chaos Agent: ${message}`;
295
- });
296
- chaosSpinner.succeed(`Found ${currentChaosIssues.length} design issues`);
297
- }
298
- catch (error) {
299
- remediationSpinner.fail('Remediation failed');
300
- console.error(picocolors_1.default.red('Error during remediation:'), error);
301
- const continueAnyway = await confirmGate('Continue to save despite remediation failure?');
302
- if (!continueAnyway) {
303
- console.log(picocolors_1.default.yellow('Exiting...'));
304
- return;
305
- }
306
- break;
367
+ // All issues were ignored, don't show any in gap analysis
368
+ gapAnalysisConsistencyIssues = [];
369
+ gapAnalysisChaosIssues = [];
307
370
  }
308
371
  }
372
+ else {
373
+ console.log(picocolors_1.default.green('\n✓ No issues found!'));
374
+ }
309
375
  // If fixes were applied, re-run specialists to regenerate markdown
310
376
  if (fixesApplied) {
311
377
  console.log(picocolors_1.default.cyan('\n🔄 Regenerating documentation with fixed Bible...\n'));
312
- markdownSections = await orchestrator.runAllSpecialists(bible, specialists);
378
+ markdownSections = await orchestrator.runAllSpecialists(bible, specialists, { skipDisambiguation: true });
313
379
  }
314
380
  // Save all outputs
315
381
  console.log(picocolors_1.default.cyan('\n💾 Saving outputs...\n'));
316
382
  const projectDir = await fileManager.initialize(projectId);
383
+ // Save game bible JSON
317
384
  await fileManager.saveGameBible(projectDir, bible);
318
- await fileManager.saveCoverPage(projectDir, bible);
319
- for (const [name, content] of markdownSections) {
320
- const filename = name.replace(/\s+/g, '_') + '.md';
321
- await fileManager.saveMarkdown(projectDir, filename, content);
322
- }
323
- await fileManager.saveGapAnalysis(projectDir, currentChaosIssues, currentConsistencyIssues);
324
- // Save combined GDD
325
- await fileManager.saveCombinedGDD(projectDir, bible, markdownSections, currentChaosIssues, currentConsistencyIssues);
326
- console.log(picocolors_1.default.green('✓ Saved combined Game Design Document'));
385
+ console.log(picocolors_1.default.green('✓ Saved game_bible.json'));
386
+ // Save combined GDD (markdown)
387
+ await fileManager.saveCombinedGDD(projectDir, bible, markdownSections, gapAnalysisChaosIssues, gapAnalysisConsistencyIssues);
388
+ console.log(picocolors_1.default.green('✓ Saved Game_Design_Document.md'));
327
389
  // Complete
328
390
  stateMachine.transition(StateMachine_1.GameForgePhase.COMPLETE);
329
391
  // Save final checkpoint
@@ -333,13 +395,31 @@ async function runCreationFlow(options) {
333
395
  console.log(picocolors_1.default.green('\n✅ Complete!\n'));
334
396
  console.log(picocolors_1.default.dim(`Session ID: ${sessionId}`));
335
397
  console.log(picocolors_1.default.dim(`Output directory: ${projectDir}`));
336
- console.log(picocolors_1.default.dim(`Total cost: $${report.total.toFixed(2)} (${report.operations} operations)`));
398
+ console.log(picocolors_1.default.cyan('\n📄 Generated Files:'));
399
+ console.log(picocolors_1.default.green(` → ${fileLink(path_1.default.join(projectDir, 'game_bible.json'))}`));
400
+ console.log(picocolors_1.default.green(` → ${fileLink(path_1.default.join(projectDir, 'Game_Design_Document.md'))}`));
401
+ // Show per-phase cost breakdown
402
+ costTracker.printFinalBreakdown();
403
+ console.log(picocolors_1.default.dim(`Total cost: $${report.total.toFixed(4)} (${report.operations} operations)`));
337
404
  console.log(picocolors_1.default.dim(`Budget remaining: $${report.remaining.toFixed(2)}\n`));
405
+ // Ask if user wants to make changes
406
+ const wantChanges = await confirmGate('Would you like to make any changes to the design?');
407
+ if (wantChanges) {
408
+ try {
409
+ await runModifyFlowWithBible(sessionId, bible, costTracker, modelSelector, fileManager, sessionManager, stateMachine, options);
410
+ }
411
+ catch (modifyError) {
412
+ console.error(picocolors_1.default.red('Error during modification:'), modifyError);
413
+ if (debugLogger_1.debugLogger.isEnabled()) {
414
+ debugLogger_1.debugLogger.logError('ModifyFlowFromCreate', modifyError);
415
+ }
416
+ }
417
+ }
338
418
  if (debugLogger_1.debugLogger.isEnabled()) {
339
419
  console.log(picocolors_1.default.yellow(`🐛 Debug log saved to: ${debugLogger_1.debugLogger.getLogFilePath()}\n`));
340
420
  debugLogger_1.debugLogger.disable();
341
421
  }
342
- console.log(picocolors_1.default.dim('View this session anytime with:'));
422
+ console.log(picocolors_1.default.dim('\nView this session anytime with:'));
343
423
  console.log(picocolors_1.default.dim(` gameforge session ${sessionId}\n`));
344
424
  }
345
425
  catch (error) {
@@ -441,19 +521,6 @@ async function confirmGate(message) {
441
521
  });
442
522
  return response.confirm;
443
523
  }
444
- async function selectReviewAction() {
445
- const response = await enquirer_1.default.prompt({
446
- type: 'select',
447
- name: 'action',
448
- message: 'What would you like to do?',
449
- choices: [
450
- { name: 'save', message: 'Save and export GDD' },
451
- { name: 'fix', message: 'Fix issues and re-validate' },
452
- { name: 'exit', message: 'Exit without saving' }
453
- ]
454
- });
455
- return response.action;
456
- }
457
524
  async function listSessions() {
458
525
  console.log(picocolors_1.default.cyan('\n📋 Saved Sessions\n'));
459
526
  const sessionManager = new SessionManager_1.SessionManager();
@@ -626,7 +693,27 @@ async function resumeFlow(idOrCheckpoint, options = {}) {
626
693
  }
627
694
  const architect = new Architect_1.Architect(apiKey, costTracker, modelSelector);
628
695
  const spinner = (0, ora_1.default)('Generating Game Bible...').start();
629
- bible = await architect.execute(savedState.transcript);
696
+ // Provide spinner control so the agent can pause during user prompts
697
+ let spinnerPaused = false;
698
+ architect.setSpinnerControl({
699
+ pause: () => {
700
+ if (!spinnerPaused) {
701
+ spinner.stop();
702
+ spinnerPaused = true;
703
+ }
704
+ },
705
+ resume: (text) => {
706
+ if (spinnerPaused) {
707
+ spinner.start(text || spinner.text);
708
+ spinnerPaused = false;
709
+ }
710
+ }
711
+ });
712
+ bible = await architect.execute(savedState.transcript, (message) => {
713
+ if (!spinnerPaused) {
714
+ spinner.text = `Generating Game Bible: ${message}`;
715
+ }
716
+ });
630
717
  spinner.succeed('Game Bible generated');
631
718
  }
632
719
  stateMachine.updateBible(bible);
@@ -678,9 +765,10 @@ async function resumeFlow(idOrCheckpoint, options = {}) {
678
765
  async function continueToProduction(stateMachine, bible, apiKey, costTracker, modelSelector, fileManager, sessionId, sessionManager) {
679
766
  // Phase 3: Production (Specialists)
680
767
  console.log(picocolors_1.default.cyan('\n🎨 Phase 3: Production\n'));
768
+ costTracker.startPhase('production');
681
769
  // Note: Caller should have already transitioned to PRODUCTION phase
682
770
  const projectId = sessionId;
683
- const orchestrator = new Orchestrator_1.SpecialistOrchestrator(2);
771
+ const orchestrator = new Orchestrator_1.SpecialistOrchestrator();
684
772
  const specialists = [
685
773
  { name: 'Creative Direction', agent: new CreativeSpecialist_1.CreativeSpecialist(apiKey, costTracker, modelSelector) },
686
774
  { name: 'Feature Specifications', agent: new FeatureSpecialist_1.FeatureSpecialist(apiKey, costTracker, modelSelector) },
@@ -688,129 +776,133 @@ async function continueToProduction(stateMachine, bible, apiKey, costTracker, mo
688
776
  { name: 'Technical Specifications', agent: new TechSpecialist_1.TechSpecialist(apiKey, costTracker, modelSelector) }
689
777
  ];
690
778
  let markdownSections = await orchestrator.runAllSpecialists(bible, specialists);
779
+ // End production phase and show cost
780
+ const productionCost = costTracker.endPhase();
781
+ if (productionCost) {
782
+ console.log(picocolors_1.default.green('\n✓ Production complete'));
783
+ costTracker.printPhaseSummary(productionCost);
784
+ }
691
785
  // Phase 4: Validation
692
786
  console.log(picocolors_1.default.cyan('\n✅ Phase 4: Validation\n'));
693
787
  stateMachine.transition(StateMachine_1.GameForgePhase.VALIDATION);
788
+ costTracker.startPhase('validation');
694
789
  const consistencyAgent = new Consistency_1.ConsistencyAgent();
695
790
  let currentConsistencyIssues = consistencyAgent.validate(bible);
696
791
  console.log(picocolors_1.default.yellow(`Found ${currentConsistencyIssues.length} consistency issues`));
697
792
  let chaosSpinner = (0, ora_1.default)('Running Chaos Agent...').start();
698
793
  const chaosAgent = new Chaos_1.Chaos(apiKey, costTracker, modelSelector);
699
- let currentChaosIssues = await chaosAgent.execute(bible);
700
- chaosSpinner.succeed(`Found ${currentChaosIssues.length} design issues`);
701
- // Gate 3: Review Issues with Fix Option
702
- const MAX_FIX_ATTEMPTS = 3;
703
- let fixAttempts = 0;
704
- let fixesApplied = false;
705
- while (true) {
706
- stateMachine.transition(StateMachine_1.GameForgePhase.REVIEW_ISSUES);
707
- const totalIssues = currentConsistencyIssues.length + currentChaosIssues.length;
708
- if (totalIssues > 0) {
709
- console.log(picocolors_1.default.yellow(`\n⚠️ ${totalIssues} issues found:`));
710
- currentConsistencyIssues.slice(0, 3).forEach((issue) => {
711
- console.log(picocolors_1.default.dim(` - [${issue.severity}] ${issue.message}`));
712
- });
713
- currentChaosIssues.slice(0, 3).forEach((issue) => {
714
- console.log(picocolors_1.default.dim(` - [${issue.severity}] ${issue.description}`));
715
- });
716
- if (totalIssues > 6) {
717
- console.log(picocolors_1.default.dim(` ... and ${totalIssues - 6} more issues`));
794
+ // Provide spinner control so the agent can pause during user prompts
795
+ let chaosSpinnerPaused = false;
796
+ chaosAgent.setSpinnerControl({
797
+ pause: () => {
798
+ if (!chaosSpinnerPaused) {
799
+ chaosSpinner.stop();
800
+ chaosSpinnerPaused = true;
718
801
  }
719
- if (fixAttempts > 0) {
720
- console.log(picocolors_1.default.cyan(`\n(Fix attempt ${fixAttempts}/${MAX_FIX_ATTEMPTS} complete)`));
802
+ },
803
+ resume: (text) => {
804
+ if (chaosSpinnerPaused) {
805
+ chaosSpinner.start(text || chaosSpinner.text);
806
+ chaosSpinnerPaused = false;
721
807
  }
722
808
  }
723
- else {
724
- console.log(picocolors_1.default.green('\n✓ All issues resolved!'));
725
- }
726
- // Determine action based on state
727
- let action;
728
- if (totalIssues === 0) {
729
- const proceed = await confirmGate('Save and export GDD?');
730
- action = proceed ? 'save' : 'exit';
731
- }
732
- else if (fixAttempts >= MAX_FIX_ATTEMPTS) {
733
- console.log(picocolors_1.default.yellow(`\nMaximum fix attempts (${MAX_FIX_ATTEMPTS}) reached.`));
734
- const proceed = await confirmGate('Save and export GDD with remaining issues?');
735
- action = proceed ? 'save' : 'exit';
809
+ });
810
+ let currentChaosIssues = await chaosAgent.execute(bible, (message) => {
811
+ if (!chaosSpinnerPaused) {
812
+ chaosSpinner.text = `Chaos Agent: ${message}`;
736
813
  }
737
- else {
738
- action = await selectReviewAction();
814
+ });
815
+ chaosSpinner.succeed(`Found ${currentChaosIssues.length} design issues`);
816
+ // End validation phase and show cost
817
+ const validationCost = costTracker.endPhase();
818
+ if (validationCost) {
819
+ costTracker.printPhaseSummary(validationCost);
820
+ }
821
+ // Gate 3: Review Issues One by One
822
+ stateMachine.transition(StateMachine_1.GameForgePhase.REVIEW_ISSUES);
823
+ let fixesApplied = false;
824
+ const totalIssues = currentConsistencyIssues.length + currentChaosIssues.length;
825
+ if (totalIssues > 0) {
826
+ console.log(picocolors_1.default.yellow(`\n⚠️ ${totalIssues} issues found. Let's review them one by one.\n`));
827
+ // Review each issue one by one
828
+ const reviewed = await (0, issueReviewer_1.reviewIssuesOneByOne)(currentConsistencyIssues, currentChaosIssues);
829
+ // If user selected any issues to fix
830
+ if (reviewed.toFix.length > 0) {
831
+ const proceed = await confirmGate(`Fix ${reviewed.toFix.length} selected issue(s)?`);
832
+ if (proceed) {
833
+ fixesApplied = true;
834
+ console.log(picocolors_1.default.cyan(`\n🔧 Fixing ${reviewed.toFix.length} issues...\n`));
835
+ costTracker.startPhase('remediation');
836
+ const remediationSpinner = (0, ora_1.default)('Running Remediation Agent...').start();
837
+ const remediationAgent = new Remediation_1.Remediation(apiKey, costTracker, modelSelector);
838
+ try {
839
+ // Convert to IssueWithNotes format for Remediation
840
+ const issuesToFix = (0, issueReviewer_1.convertToIssuesWithNotes)(reviewed);
841
+ const result = await remediationAgent.execute(bible, issuesToFix, (message) => {
842
+ remediationSpinner.text = `Remediation: ${message}`;
843
+ });
844
+ remediationSpinner.succeed(`Fixed ${result.fixedIssues.length} issues`);
845
+ // End remediation phase and show cost
846
+ const remediationCost = costTracker.endPhase();
847
+ if (remediationCost) {
848
+ costTracker.printPhaseSummary(remediationCost);
849
+ }
850
+ if (result.fixedIssues.length > 0) {
851
+ console.log(picocolors_1.default.green('\nFixes applied:'));
852
+ result.fixedIssues.forEach((fix) => {
853
+ console.log(picocolors_1.default.dim(` ✓ ${fix}`));
854
+ });
855
+ }
856
+ if (result.unfixableIssues.length > 0) {
857
+ console.log(picocolors_1.default.yellow('\nCould not fix:'));
858
+ result.unfixableIssues.forEach((issue) => {
859
+ console.log(picocolors_1.default.dim(` ✗ ${issue}`));
860
+ });
861
+ }
862
+ // Update bible
863
+ bible = result.updatedBible;
864
+ stateMachine.updateBible(bible);
865
+ }
866
+ catch (error) {
867
+ remediationSpinner.fail('Remediation failed');
868
+ console.error(picocolors_1.default.red('Error during remediation:'), error);
869
+ const continueAnyway = await confirmGate('Continue to save despite remediation failure?');
870
+ if (!continueAnyway) {
871
+ console.log(picocolors_1.default.yellow('Exiting...'));
872
+ return;
873
+ }
874
+ }
875
+ }
739
876
  }
740
- if (action === 'exit') {
877
+ // Final confirmation to save
878
+ const saveConfirm = await confirmGate('Save and export GDD?');
879
+ if (!saveConfirm) {
741
880
  console.log(picocolors_1.default.yellow('Exiting...'));
742
881
  return;
743
882
  }
744
- if (action === 'save') {
745
- break;
746
- }
747
- // action === 'fix': Run remediation
748
- fixAttempts++;
749
- fixesApplied = true;
750
- console.log(picocolors_1.default.cyan(`\n🔧 Fixing issues (attempt ${fixAttempts}/${MAX_FIX_ATTEMPTS})...\n`));
751
- const remediationSpinner = (0, ora_1.default)('Running Remediation Agent...').start();
752
- const remediationAgent = new Remediation_1.Remediation(apiKey, costTracker, modelSelector);
753
- try {
754
- const result = await remediationAgent.execute(bible, currentConsistencyIssues, currentChaosIssues, (message) => { remediationSpinner.text = `Remediation: ${message}`; });
755
- remediationSpinner.succeed(`Fixed ${result.fixedIssues.length} issues`);
756
- if (result.fixedIssues.length > 0) {
757
- console.log(picocolors_1.default.green('\nFixes applied:'));
758
- result.fixedIssues.forEach(fix => {
759
- console.log(picocolors_1.default.dim(` ✓ ${fix}`));
760
- });
761
- }
762
- if (result.unfixableIssues.length > 0) {
763
- console.log(picocolors_1.default.yellow('\nCould not fix:'));
764
- result.unfixableIssues.forEach(issue => {
765
- console.log(picocolors_1.default.dim(` ✗ ${issue}`));
766
- });
767
- }
768
- // Update bible
769
- bible = result.updatedBible;
770
- stateMachine.updateBible(bible);
771
- // Transition back to production for re-validation
772
- stateMachine.transition(StateMachine_1.GameForgePhase.PRODUCTION);
773
- // Re-run validation
774
- console.log(picocolors_1.default.cyan('\n🔄 Re-validating...\n'));
775
- stateMachine.transition(StateMachine_1.GameForgePhase.VALIDATION);
776
- const newConsistencyAgent = new Consistency_1.ConsistencyAgent();
777
- currentConsistencyIssues = newConsistencyAgent.validate(bible);
778
- console.log(picocolors_1.default.yellow(`Found ${currentConsistencyIssues.length} consistency issues`));
779
- chaosSpinner = (0, ora_1.default)('Re-running Chaos Agent...').start();
780
- const newChaosAgent = new Chaos_1.Chaos(apiKey, costTracker, modelSelector);
781
- currentChaosIssues = await newChaosAgent.execute(bible, (message) => {
782
- chaosSpinner.text = `Chaos Agent: ${message}`;
783
- });
784
- chaosSpinner.succeed(`Found ${currentChaosIssues.length} design issues`);
785
- }
786
- catch (error) {
787
- remediationSpinner.fail('Remediation failed');
788
- console.error(picocolors_1.default.red('Error during remediation:'), error);
789
- const continueAnyway = await confirmGate('Continue to save despite remediation failure?');
790
- if (!continueAnyway) {
791
- console.log(picocolors_1.default.yellow('Exiting...'));
792
- return;
793
- }
794
- break;
883
+ }
884
+ else {
885
+ console.log(picocolors_1.default.green('\n✓ No issues found!'));
886
+ const proceed = await confirmGate('Save and export GDD?');
887
+ if (!proceed) {
888
+ console.log(picocolors_1.default.yellow('Exiting...'));
889
+ return;
795
890
  }
796
891
  }
797
892
  // If fixes were applied, re-run specialists to regenerate markdown
798
893
  if (fixesApplied) {
799
894
  console.log(picocolors_1.default.cyan('\n🔄 Regenerating documentation with fixed Bible...\n'));
800
- markdownSections = await orchestrator.runAllSpecialists(bible, specialists);
895
+ markdownSections = await orchestrator.runAllSpecialists(bible, specialists, { skipDisambiguation: true });
801
896
  }
802
897
  // Save all outputs
803
898
  console.log(picocolors_1.default.cyan('\n💾 Saving outputs...\n'));
804
899
  const projectDir = await fileManager.initialize(projectId);
900
+ // Save game bible JSON
805
901
  await fileManager.saveGameBible(projectDir, bible);
806
- await fileManager.saveCoverPage(projectDir, bible);
807
- for (const [name, content] of markdownSections) {
808
- const filename = name.replace(/\s+/g, '_') + '.md';
809
- await fileManager.saveMarkdown(projectDir, filename, content);
810
- }
811
- await fileManager.saveGapAnalysis(projectDir, currentChaosIssues, currentConsistencyIssues);
812
- // Save combined GDD
902
+ console.log(picocolors_1.default.green('✓ Saved game_bible.json'));
903
+ // Save combined GDD (markdown)
813
904
  await fileManager.saveCombinedGDD(projectDir, bible, markdownSections, currentChaosIssues, currentConsistencyIssues);
905
+ console.log(picocolors_1.default.green('✓ Saved Game_Design_Document.md'));
814
906
  stateMachine.transition(StateMachine_1.GameForgePhase.COMPLETE);
815
907
  // Save final checkpoint
816
908
  await sessionManager.saveCheckpoint(sessionId, stateMachine.getState(), 'complete');
@@ -818,7 +910,9 @@ async function continueToProduction(stateMachine, bible, apiKey, costTracker, mo
818
910
  console.log(picocolors_1.default.green('\n✅ Complete!\n'));
819
911
  console.log(picocolors_1.default.dim(`Session ID: ${sessionId}`));
820
912
  console.log(picocolors_1.default.dim(`Output directory: ${projectDir}`));
821
- console.log(picocolors_1.default.dim(`Total cost: $${report.total.toFixed(2)} (${report.operations} operations)`));
913
+ // Show per-phase cost breakdown
914
+ costTracker.printFinalBreakdown();
915
+ console.log(picocolors_1.default.dim(`Total cost: $${report.total.toFixed(4)} (${report.operations} operations)`));
822
916
  console.log(picocolors_1.default.dim(`Budget remaining: $${report.remaining.toFixed(2)}\n`));
823
917
  if (debugLogger_1.debugLogger.isEnabled()) {
824
918
  console.log(picocolors_1.default.yellow(`🐛 Debug log saved to: ${debugLogger_1.debugLogger.getLogFilePath()}\n`));
@@ -827,4 +921,228 @@ async function continueToProduction(stateMachine, bible, apiKey, costTracker, mo
827
921
  console.log(picocolors_1.default.dim('View this session anytime with:'));
828
922
  console.log(picocolors_1.default.dim(` gameforge session ${sessionId}\n`));
829
923
  }
924
+ async function runModifyFlow(sessionId, options) {
925
+ console.clear();
926
+ console.log(picocolors_1.default.cyan('\n╔══════════════════════════════════════╗'));
927
+ console.log(picocolors_1.default.cyan('║ GameForge CLI - Modify Document ║'));
928
+ console.log(picocolors_1.default.cyan('╚══════════════════════════════════════╝\n'));
929
+ // Enable debug logging if requested
930
+ if (options.debug) {
931
+ debugLogger_1.debugLogger.enable();
932
+ debugLogger_1.debugLogger.log('Starting modify flow for session: ' + sessionId);
933
+ }
934
+ const apiKey = process.env.ANTHROPIC_API_KEY;
935
+ if (!apiKey) {
936
+ console.error(picocolors_1.default.red('Error: ANTHROPIC_API_KEY environment variable is not set'));
937
+ console.log(picocolors_1.default.dim('\nSet your API key:'));
938
+ console.log(picocolors_1.default.dim(' export ANTHROPIC_API_KEY=your-key-here\n'));
939
+ return;
940
+ }
941
+ const sessionManager = new SessionManager_1.SessionManager();
942
+ const fileManager = new fileManager_1.FileManager();
943
+ // Load latest checkpoint for this session
944
+ console.log(picocolors_1.default.cyan('Loading session...\n'));
945
+ try {
946
+ const latest = await sessionManager.getLatestCheckpoint(sessionId);
947
+ if (!latest || !latest.state.bible) {
948
+ console.error(picocolors_1.default.red(`No existing GDD found for session: ${sessionId}`));
949
+ console.log(picocolors_1.default.dim('\nList all sessions with: gameforge sessions\n'));
950
+ return;
951
+ }
952
+ const bible = latest.state.bible;
953
+ console.log(picocolors_1.default.green(`✓ Loaded GDD: ${bible.meta?.title || 'Untitled'}`));
954
+ console.log(picocolors_1.default.dim(` Genre: ${bible.meta?.genre || 'Unknown'}`));
955
+ console.log(picocolors_1.default.dim(` Features: ${bible.features?.length || 0}`));
956
+ console.log(picocolors_1.default.dim(` Session: ${sessionId}\n`));
957
+ // Set up cost tracking with remaining budget from checkpoint
958
+ const budgetLimit = latest.state.budgetLimit || 5.0;
959
+ const costTracker = new costTracker_1.CostTracker(budgetLimit);
960
+ const modelSelector = new modelSelector_1.ModelSelector();
961
+ // Create state machine for checkpoints
962
+ const stateMachine = new StateMachine_1.StateMachine(budgetLimit);
963
+ stateMachine.restoreState(latest.state);
964
+ // Run the modification loop
965
+ await runModifyFlowWithBible(sessionId, bible, costTracker, modelSelector, fileManager, sessionManager, stateMachine, options);
966
+ }
967
+ catch (error) {
968
+ console.error(picocolors_1.default.red('Error during modification:'), error);
969
+ if (debugLogger_1.debugLogger.isEnabled()) {
970
+ debugLogger_1.debugLogger.log('Modification flow failed: ' + String(error));
971
+ debugLogger_1.debugLogger.disable();
972
+ }
973
+ }
974
+ }
975
+ async function runModifyFlowWithBible(sessionId, bible, costTracker, modelSelector, fileManager, sessionManager, stateMachine, options) {
976
+ const apiKey = process.env.ANTHROPIC_API_KEY;
977
+ // Prompt for modification description
978
+ const response = await enquirer_1.default.prompt({
979
+ type: 'input',
980
+ name: 'modification',
981
+ message: 'Describe the changes you want to make:'
982
+ });
983
+ const modification = response.modification.trim();
984
+ if (!modification) {
985
+ console.log(picocolors_1.default.yellow('\nNo modification provided.'));
986
+ showFinalSummary(costTracker, sessionId, options);
987
+ return;
988
+ }
989
+ // Apply modification via Modifier agent
990
+ console.log(picocolors_1.default.cyan('\n🔧 Applying modifications...\n'));
991
+ costTracker.startPhase('remediation');
992
+ const modifier = new Modifier_1.Modifier(apiKey, costTracker, modelSelector);
993
+ const modifySpinner = (0, ora_1.default)('Processing modification request...').start();
994
+ const result = await modifier.execute(bible, modification, (message) => {
995
+ modifySpinner.text = `Modifier: ${message}`;
996
+ });
997
+ modifySpinner.succeed('Modifications applied');
998
+ // End modification phase and show cost
999
+ const modificationCost = costTracker.endPhase();
1000
+ if (modificationCost) {
1001
+ costTracker.printPhaseSummary(modificationCost);
1002
+ }
1003
+ // Show summary of changes
1004
+ if (result.changesSummary.length > 0) {
1005
+ console.log(picocolors_1.default.green('\nChanges made:'));
1006
+ result.changesSummary.forEach((change) => {
1007
+ console.log(picocolors_1.default.dim(` ✓ ${change}`));
1008
+ });
1009
+ }
1010
+ if (result.errors.length > 0) {
1011
+ console.log(picocolors_1.default.yellow('\nWarnings/Errors:'));
1012
+ result.errors.forEach((err) => {
1013
+ console.log(picocolors_1.default.dim(` ⚠ ${err}`));
1014
+ });
1015
+ }
1016
+ const updatedBible = result.updatedBible;
1017
+ // Try to use targeted document updates instead of full regeneration
1018
+ console.log(picocolors_1.default.cyan('\n🎨 Updating documentation...\n'));
1019
+ costTracker.startPhase('production');
1020
+ const projectDir = await fileManager.initialize(sessionId);
1021
+ const existingGDD = await fileManager.readGDD(projectDir);
1022
+ let markdownSections = null;
1023
+ let updatedGDD = null;
1024
+ if (existingGDD && result.changesSummary.length > 0) {
1025
+ // Use targeted document updates - preserve existing content
1026
+ console.log(picocolors_1.default.dim(' Using targeted updates to preserve existing content...\n'));
1027
+ const documentUpdater = new DocumentUpdater_1.DocumentUpdater(apiKey, costTracker, modelSelector);
1028
+ const updateSpinner = (0, ora_1.default)('Applying targeted document updates...').start();
1029
+ const updateResult = await documentUpdater.execute(existingGDD, result.changesSummary, updatedBible, (message) => {
1030
+ updateSpinner.text = `Document Updater: ${message}`;
1031
+ });
1032
+ // Require both: no errors AND document was actually modified
1033
+ const updateSuccessful = updateResult.errors.length === 0 && updateResult.updatedDocument !== existingGDD;
1034
+ if (updateSuccessful) {
1035
+ updateSpinner.succeed(`Updated ${updateResult.sectionsModified.length} sections`);
1036
+ updatedGDD = updateResult.updatedDocument;
1037
+ if (updateResult.sectionsModified.length > 0) {
1038
+ console.log(picocolors_1.default.dim(' Modified sections:'));
1039
+ updateResult.sectionsModified.forEach(section => {
1040
+ console.log(picocolors_1.default.dim(` • ${section}`));
1041
+ });
1042
+ }
1043
+ }
1044
+ else {
1045
+ // Log specific reason for fallback
1046
+ if (updateResult.errors.length > 0) {
1047
+ updateSpinner.warn(`Targeted update failed: ${updateResult.errors[0]}`);
1048
+ }
1049
+ else {
1050
+ updateSpinner.warn('Targeted update produced no changes');
1051
+ }
1052
+ console.log(picocolors_1.default.yellow(' Falling back to full document regeneration...\n'));
1053
+ }
1054
+ }
1055
+ // Fall back to full regeneration if targeted update failed or no existing GDD
1056
+ if (!updatedGDD) {
1057
+ console.log(picocolors_1.default.dim(' Regenerating full documentation...\n'));
1058
+ const orchestrator = new Orchestrator_1.SpecialistOrchestrator();
1059
+ const specialists = [
1060
+ { name: 'Creative Direction', agent: new CreativeSpecialist_1.CreativeSpecialist(apiKey, costTracker, modelSelector) },
1061
+ { name: 'Feature Specifications', agent: new FeatureSpecialist_1.FeatureSpecialist(apiKey, costTracker, modelSelector) },
1062
+ { name: 'Entity Specifications', agent: new EntitySpecialist_1.EntitySpecialist(apiKey, costTracker, modelSelector) },
1063
+ { name: 'Technical Specifications', agent: new TechSpecialist_1.TechSpecialist(apiKey, costTracker, modelSelector) }
1064
+ ];
1065
+ markdownSections = await orchestrator.runAllSpecialists(updatedBible, specialists, {
1066
+ skipDisambiguation: true
1067
+ });
1068
+ }
1069
+ // End production phase
1070
+ const productionCost = costTracker.endPhase();
1071
+ if (productionCost) {
1072
+ costTracker.printPhaseSummary(productionCost);
1073
+ }
1074
+ // Run validation for gap analysis
1075
+ console.log(picocolors_1.default.cyan('\n🔍 Running validation...\n'));
1076
+ costTracker.startPhase('validation');
1077
+ const consistencyAgent = new Consistency_1.ConsistencyAgent();
1078
+ const consistencyIssues = consistencyAgent.validate(updatedBible);
1079
+ const chaosSpinner = (0, ora_1.default)('Running Chaos Agent...').start();
1080
+ const chaosAgent = new Chaos_1.Chaos(apiKey, costTracker, modelSelector);
1081
+ const chaosIssues = await chaosAgent.execute(updatedBible, (message) => {
1082
+ chaosSpinner.text = `Chaos Agent: ${message}`;
1083
+ });
1084
+ chaosSpinner.succeed(`Found ${chaosIssues.length} design issues`);
1085
+ const validationCost = costTracker.endPhase();
1086
+ if (validationCost) {
1087
+ costTracker.printPhaseSummary(validationCost);
1088
+ }
1089
+ // Save outputs
1090
+ console.log(picocolors_1.default.cyan('\n💾 Saving outputs...\n'));
1091
+ // Save game bible JSON
1092
+ await fileManager.saveGameBible(projectDir, updatedBible);
1093
+ console.log(picocolors_1.default.green('✓ Saved game_bible.json'));
1094
+ // Save GDD - either the updated version or regenerated
1095
+ if (updatedGDD) {
1096
+ // Update gap analysis section in the targeted-update GDD before saving
1097
+ const gddWithGapAnalysis = fileManager.updateGapAnalysisInDocument(updatedGDD, chaosIssues, consistencyIssues, sessionId);
1098
+ await fileManager.saveUpdatedGDD(projectDir, gddWithGapAnalysis);
1099
+ console.log(picocolors_1.default.green('✓ Saved Game_Design_Document.md (targeted update)'));
1100
+ }
1101
+ else if (markdownSections) {
1102
+ // Save the fully regenerated GDD
1103
+ await fileManager.saveCombinedGDD(projectDir, updatedBible, markdownSections, chaosIssues, consistencyIssues);
1104
+ console.log(picocolors_1.default.green('✓ Saved Game_Design_Document.md (full regeneration)'));
1105
+ }
1106
+ // Save checkpoint
1107
+ stateMachine.updateBible(updatedBible);
1108
+ await sessionManager.saveCheckpoint(sessionId, stateMachine.getState(), 'after_modification');
1109
+ // Show iteration summary (without final breakdown)
1110
+ const report = costTracker.getReport();
1111
+ console.log(picocolors_1.default.green('\n✅ Modification complete!\n'));
1112
+ console.log(picocolors_1.default.dim(`Session ID: ${sessionId}`));
1113
+ console.log(picocolors_1.default.dim(`Output directory: ${projectDir}`));
1114
+ console.log(picocolors_1.default.cyan('\n📄 Generated Files:'));
1115
+ console.log(picocolors_1.default.green(` → ${fileLink(path_1.default.join(projectDir, 'game_bible.json'))}`));
1116
+ console.log(picocolors_1.default.green(` → ${fileLink(path_1.default.join(projectDir, 'Game_Design_Document.md'))}`));
1117
+ console.log(picocolors_1.default.dim(`\nIteration cost: $${report.total.toFixed(4)} (${report.operations} operations)`));
1118
+ console.log(picocolors_1.default.dim(`Budget remaining: $${report.remaining.toFixed(2)}\n`));
1119
+ // Ask if user wants to make more changes
1120
+ const moreChanges = await confirmGate('Would you like to make more changes?');
1121
+ if (moreChanges) {
1122
+ // Loop back for more modifications with try-catch
1123
+ try {
1124
+ await runModifyFlowWithBible(sessionId, updatedBible, costTracker, modelSelector, fileManager, sessionManager, stateMachine, options);
1125
+ }
1126
+ catch (error) {
1127
+ console.error(picocolors_1.default.red('Error during modification:'), error);
1128
+ if (debugLogger_1.debugLogger.isEnabled()) {
1129
+ debugLogger_1.debugLogger.logError('ModifyFlowRecursive', error);
1130
+ }
1131
+ showFinalSummary(costTracker, sessionId, options);
1132
+ }
1133
+ }
1134
+ else {
1135
+ showFinalSummary(costTracker, sessionId, options);
1136
+ }
1137
+ }
1138
+ function showFinalSummary(costTracker, sessionId, options) {
1139
+ // Show final cost breakdown only once when exiting the loop
1140
+ costTracker.printFinalBreakdown();
1141
+ if (debugLogger_1.debugLogger.isEnabled()) {
1142
+ console.log(picocolors_1.default.yellow(`🐛 Debug log saved to: ${debugLogger_1.debugLogger.getLogFilePath()}\n`));
1143
+ debugLogger_1.debugLogger.disable();
1144
+ }
1145
+ console.log(picocolors_1.default.dim('\nView this session anytime with:'));
1146
+ console.log(picocolors_1.default.dim(` gameforge session ${sessionId}\n`));
1147
+ }
830
1148
  //# sourceMappingURL=index.js.map