gameforge-cli 0.2.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 (40) hide show
  1. package/README.md +38 -5
  2. package/dist/agents/core/Architect.d.ts +4 -0
  3. package/dist/agents/core/Architect.d.ts.map +1 -1
  4. package/dist/agents/core/Architect.js +150 -0
  5. package/dist/agents/core/Architect.js.map +1 -1
  6. package/dist/agents/core/DocumentUpdater.d.ts +13 -0
  7. package/dist/agents/core/DocumentUpdater.d.ts.map +1 -0
  8. package/dist/agents/core/DocumentUpdater.js +165 -0
  9. package/dist/agents/core/DocumentUpdater.js.map +1 -0
  10. package/dist/agents/specialists/CreativeSpecialist.d.ts.map +1 -1
  11. package/dist/agents/specialists/CreativeSpecialist.js +68 -0
  12. package/dist/agents/specialists/CreativeSpecialist.js.map +1 -1
  13. package/dist/agents/specialists/EntitySpecialist.d.ts.map +1 -1
  14. package/dist/agents/specialists/EntitySpecialist.js +16 -0
  15. package/dist/agents/specialists/EntitySpecialist.js.map +1 -1
  16. package/dist/agents/specialists/FeatureSpecialist.d.ts +4 -0
  17. package/dist/agents/specialists/FeatureSpecialist.d.ts.map +1 -1
  18. package/dist/agents/specialists/FeatureSpecialist.js +61 -0
  19. package/dist/agents/specialists/FeatureSpecialist.js.map +1 -1
  20. package/dist/agents/specialists/TechSpecialist.d.ts.map +1 -1
  21. package/dist/agents/specialists/TechSpecialist.js +100 -0
  22. package/dist/agents/specialists/TechSpecialist.js.map +1 -1
  23. package/dist/index.js +316 -138
  24. package/dist/index.js.map +1 -1
  25. package/dist/utils/disambiguationHelper.d.ts.map +1 -1
  26. package/dist/utils/disambiguationHelper.js +18 -8
  27. package/dist/utils/disambiguationHelper.js.map +1 -1
  28. package/dist/utils/fileManager.d.ts +7 -0
  29. package/dist/utils/fileManager.d.ts.map +1 -1
  30. package/dist/utils/fileManager.js +47 -0
  31. package/dist/utils/fileManager.js.map +1 -1
  32. package/dist/utils/issueReviewer.d.ts +4 -0
  33. package/dist/utils/issueReviewer.d.ts.map +1 -1
  34. package/dist/utils/issueReviewer.js +47 -0
  35. package/dist/utils/issueReviewer.js.map +1 -1
  36. package/dist/utils/pdfGenerator.d.ts +12 -0
  37. package/dist/utils/pdfGenerator.d.ts.map +1 -0
  38. package/dist/utils/pdfGenerator.js +341 -0
  39. package/dist/utils/pdfGenerator.js.map +1 -0
  40. package/package.json +20 -15
package/dist/index.js CHANGED
@@ -31,7 +31,18 @@ const Orchestrator_1 = require("./core/Orchestrator");
31
31
  const debugLogger_1 = require("./utils/debugLogger");
32
32
  const issueReviewer_1 = require("./utils/issueReviewer");
33
33
  const Modifier_1 = require("./agents/core/Modifier");
34
+ const DocumentUpdater_1 = require("./agents/core/DocumentUpdater");
34
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
+ }
35
46
  const program = new commander_1.Command();
36
47
  program
37
48
  .name('gameforge')
@@ -168,11 +179,6 @@ async function runCreationFlow(options) {
168
179
  console.log(picocolors_1.default.cyan('\nšŸ“Š Concept Review\n'));
169
180
  stateMachine.transition(StateMachine_1.GameForgePhase.REVIEW_CONCEPT);
170
181
  showConceptBrief(transcript);
171
- const proceed1 = await confirmGate('Proceed to architecture phase?');
172
- if (!proceed1) {
173
- console.log(picocolors_1.default.yellow('Exiting...'));
174
- return;
175
- }
176
182
  await sessionManager.saveCheckpoint(sessionId, stateMachine.getState(), 'after_discovery');
177
183
  // Phase 2: Architecture
178
184
  console.log(picocolors_1.default.cyan('\nšŸ—ļø Phase 2: Architecture\n'));
@@ -180,8 +186,26 @@ async function runCreationFlow(options) {
180
186
  costTracker.startPhase('architecture');
181
187
  const spinner = (0, ora_1.default)('Generating Game Bible...').start();
182
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
+ });
183
205
  let bible = await architect.execute(transcript, (message) => {
184
- spinner.text = `Generating Game Bible: ${message}`;
206
+ if (!spinnerPaused) {
207
+ spinner.text = `Generating Game Bible: ${message}`;
208
+ }
185
209
  });
186
210
  // Merge template features if a template was selected
187
211
  if (selectedTemplate && selectedTemplate.partialBible.features) {
@@ -202,11 +226,6 @@ async function runCreationFlow(options) {
202
226
  stateMachine.transition(StateMachine_1.GameForgePhase.REVIEW_DATA);
203
227
  console.log(picocolors_1.default.green(`āœ“ Generated ${bible.features.length} features`));
204
228
  console.log(picocolors_1.default.green(`āœ“ Generated ${bible.gameObjects.length} game objects`));
205
- const proceed2 = await confirmGate('Proceed to production phase?');
206
- if (!proceed2) {
207
- console.log(picocolors_1.default.yellow('Exiting...'));
208
- return;
209
- }
210
229
  // Phase 3: Production (Specialists)
211
230
  console.log(picocolors_1.default.cyan('\nšŸŽØ Phase 3: Production\n'));
212
231
  stateMachine.transition(StateMachine_1.GameForgePhase.PRODUCTION);
@@ -236,8 +255,26 @@ async function runCreationFlow(options) {
236
255
  // Run chaos analysis
237
256
  let chaosSpinner = (0, ora_1.default)('Running Chaos Agent...').start();
238
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
+ });
239
274
  let currentChaosIssues = await chaosAgent.execute(bible, (message) => {
240
- chaosSpinner.text = `Chaos Agent: ${message}`;
275
+ if (!chaosSpinnerPaused) {
276
+ chaosSpinner.text = `Chaos Agent: ${message}`;
277
+ }
241
278
  });
242
279
  chaosSpinner.succeed(`Found ${currentChaosIssues.length} design issues`);
243
280
  // End validation phase and show cost
@@ -248,6 +285,8 @@ async function runCreationFlow(options) {
248
285
  // Gate 3: Review Issues One by One
249
286
  stateMachine.transition(StateMachine_1.GameForgePhase.REVIEW_ISSUES);
250
287
  let fixesApplied = false;
288
+ let gapAnalysisConsistencyIssues = currentConsistencyIssues;
289
+ let gapAnalysisChaosIssues = currentChaosIssues;
251
290
  const totalIssues = currentConsistencyIssues.length + currentChaosIssues.length;
252
291
  if (totalIssues > 0) {
253
292
  console.log(picocolors_1.default.yellow(`\nāš ļø ${totalIssues} issues found. Let's review them one by one.\n`));
@@ -289,6 +328,10 @@ async function runCreationFlow(options) {
289
328
  // Update bible
290
329
  bible = result.updatedBible;
291
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;
292
335
  }
293
336
  catch (error) {
294
337
  remediationSpinner.fail('Remediation failed');
@@ -298,23 +341,36 @@ async function runCreationFlow(options) {
298
341
  console.log(picocolors_1.default.yellow('Exiting...'));
299
342
  return;
300
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);
301
354
  }
302
355
  }
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);
364
+ }
303
365
  }
304
- // Final confirmation to save
305
- const saveConfirm = await confirmGate('Save and export GDD?');
306
- if (!saveConfirm) {
307
- console.log(picocolors_1.default.yellow('Exiting...'));
308
- return;
366
+ else {
367
+ // All issues were ignored, don't show any in gap analysis
368
+ gapAnalysisConsistencyIssues = [];
369
+ gapAnalysisChaosIssues = [];
309
370
  }
310
371
  }
311
372
  else {
312
373
  console.log(picocolors_1.default.green('\nāœ“ No issues found!'));
313
- const proceed = await confirmGate('Save and export GDD?');
314
- if (!proceed) {
315
- console.log(picocolors_1.default.yellow('Exiting...'));
316
- return;
317
- }
318
374
  }
319
375
  // If fixes were applied, re-run specialists to regenerate markdown
320
376
  if (fixesApplied) {
@@ -324,16 +380,12 @@ async function runCreationFlow(options) {
324
380
  // Save all outputs
325
381
  console.log(picocolors_1.default.cyan('\nšŸ’¾ Saving outputs...\n'));
326
382
  const projectDir = await fileManager.initialize(projectId);
383
+ // Save game bible JSON
327
384
  await fileManager.saveGameBible(projectDir, bible);
328
- await fileManager.saveCoverPage(projectDir, bible);
329
- for (const [name, content] of markdownSections) {
330
- const filename = name.replace(/\s+/g, '_') + '.md';
331
- await fileManager.saveMarkdown(projectDir, filename, content);
332
- }
333
- await fileManager.saveGapAnalysis(projectDir, currentChaosIssues, currentConsistencyIssues);
334
- // Save combined GDD
335
- await fileManager.saveCombinedGDD(projectDir, bible, markdownSections, currentChaosIssues, currentConsistencyIssues);
336
- 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'));
337
389
  // Complete
338
390
  stateMachine.transition(StateMachine_1.GameForgePhase.COMPLETE);
339
391
  // Save final checkpoint
@@ -343,15 +395,31 @@ async function runCreationFlow(options) {
343
395
  console.log(picocolors_1.default.green('\nāœ… Complete!\n'));
344
396
  console.log(picocolors_1.default.dim(`Session ID: ${sessionId}`));
345
397
  console.log(picocolors_1.default.dim(`Output directory: ${projectDir}`));
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'))}`));
346
401
  // Show per-phase cost breakdown
347
402
  costTracker.printFinalBreakdown();
348
403
  console.log(picocolors_1.default.dim(`Total cost: $${report.total.toFixed(4)} (${report.operations} operations)`));
349
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
+ }
350
418
  if (debugLogger_1.debugLogger.isEnabled()) {
351
419
  console.log(picocolors_1.default.yellow(`šŸ› Debug log saved to: ${debugLogger_1.debugLogger.getLogFilePath()}\n`));
352
420
  debugLogger_1.debugLogger.disable();
353
421
  }
354
- console.log(picocolors_1.default.dim('View this session anytime with:'));
422
+ console.log(picocolors_1.default.dim('\nView this session anytime with:'));
355
423
  console.log(picocolors_1.default.dim(` gameforge session ${sessionId}\n`));
356
424
  }
357
425
  catch (error) {
@@ -625,7 +693,27 @@ async function resumeFlow(idOrCheckpoint, options = {}) {
625
693
  }
626
694
  const architect = new Architect_1.Architect(apiKey, costTracker, modelSelector);
627
695
  const spinner = (0, ora_1.default)('Generating Game Bible...').start();
628
- 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
+ });
629
717
  spinner.succeed('Game Bible generated');
630
718
  }
631
719
  stateMachine.updateBible(bible);
@@ -703,7 +791,27 @@ async function continueToProduction(stateMachine, bible, apiKey, costTracker, mo
703
791
  console.log(picocolors_1.default.yellow(`Found ${currentConsistencyIssues.length} consistency issues`));
704
792
  let chaosSpinner = (0, ora_1.default)('Running Chaos Agent...').start();
705
793
  const chaosAgent = new Chaos_1.Chaos(apiKey, costTracker, modelSelector);
706
- let currentChaosIssues = await chaosAgent.execute(bible);
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;
801
+ }
802
+ },
803
+ resume: (text) => {
804
+ if (chaosSpinnerPaused) {
805
+ chaosSpinner.start(text || chaosSpinner.text);
806
+ chaosSpinnerPaused = false;
807
+ }
808
+ }
809
+ });
810
+ let currentChaosIssues = await chaosAgent.execute(bible, (message) => {
811
+ if (!chaosSpinnerPaused) {
812
+ chaosSpinner.text = `Chaos Agent: ${message}`;
813
+ }
814
+ });
707
815
  chaosSpinner.succeed(`Found ${currentChaosIssues.length} design issues`);
708
816
  // End validation phase and show cost
709
817
  const validationCost = costTracker.endPhase();
@@ -789,15 +897,12 @@ async function continueToProduction(stateMachine, bible, apiKey, costTracker, mo
789
897
  // Save all outputs
790
898
  console.log(picocolors_1.default.cyan('\nšŸ’¾ Saving outputs...\n'));
791
899
  const projectDir = await fileManager.initialize(projectId);
900
+ // Save game bible JSON
792
901
  await fileManager.saveGameBible(projectDir, bible);
793
- await fileManager.saveCoverPage(projectDir, bible);
794
- for (const [name, content] of markdownSections) {
795
- const filename = name.replace(/\s+/g, '_') + '.md';
796
- await fileManager.saveMarkdown(projectDir, filename, content);
797
- }
798
- await fileManager.saveGapAnalysis(projectDir, currentChaosIssues, currentConsistencyIssues);
799
- // Save combined GDD
902
+ console.log(picocolors_1.default.green('āœ“ Saved game_bible.json'));
903
+ // Save combined GDD (markdown)
800
904
  await fileManager.saveCombinedGDD(projectDir, bible, markdownSections, currentChaosIssues, currentConsistencyIssues);
905
+ console.log(picocolors_1.default.green('āœ“ Saved Game_Design_Document.md'));
801
906
  stateMachine.transition(StateMachine_1.GameForgePhase.COMPLETE);
802
907
  // Save final checkpoint
803
908
  await sessionManager.saveCheckpoint(sessionId, stateMachine.getState(), 'complete');
@@ -844,7 +949,7 @@ async function runModifyFlow(sessionId, options) {
844
949
  console.log(picocolors_1.default.dim('\nList all sessions with: gameforge sessions\n'));
845
950
  return;
846
951
  }
847
- let bible = latest.state.bible;
952
+ const bible = latest.state.bible;
848
953
  console.log(picocolors_1.default.green(`āœ“ Loaded GDD: ${bible.meta?.title || 'Untitled'}`));
849
954
  console.log(picocolors_1.default.dim(` Genre: ${bible.meta?.genre || 'Unknown'}`));
850
955
  console.log(picocolors_1.default.dim(` Features: ${bible.features?.length || 0}`));
@@ -853,54 +958,103 @@ async function runModifyFlow(sessionId, options) {
853
958
  const budgetLimit = latest.state.budgetLimit || 5.0;
854
959
  const costTracker = new costTracker_1.CostTracker(budgetLimit);
855
960
  const modelSelector = new modelSelector_1.ModelSelector();
856
- // Prompt for modification description
857
- const response = await enquirer_1.default.prompt({
858
- type: 'input',
859
- name: 'modification',
860
- message: 'Describe the changes you want to make:'
861
- });
862
- const modification = response.modification.trim();
863
- if (!modification) {
864
- console.log(picocolors_1.default.yellow('\nNo modification provided. Exiting.'));
865
- return;
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();
866
972
  }
867
- // Apply modification via Modifier agent
868
- console.log(picocolors_1.default.cyan('\nšŸ”§ Applying modifications...\n'));
869
- costTracker.startPhase('remediation'); // Use 'remediation' as closest phase type
870
- const modifier = new Modifier_1.Modifier(apiKey, costTracker, modelSelector);
871
- const modifySpinner = (0, ora_1.default)('Processing modification request...').start();
872
- const result = await modifier.execute(bible, modification, (message) => {
873
- modifySpinner.text = `Modifier: ${message}`;
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}`));
874
1008
  });
875
- modifySpinner.succeed('Modifications applied');
876
- // End modification phase and show cost
877
- const modificationCost = costTracker.endPhase();
878
- if (modificationCost) {
879
- costTracker.printPhaseSummary(modificationCost);
880
- }
881
- // Show summary of changes
882
- if (result.changesSummary.length > 0) {
883
- console.log(picocolors_1.default.green('\nChanges made:'));
884
- result.changesSummary.forEach((change) => {
885
- console.log(picocolors_1.default.dim(` āœ“ ${change}`));
886
- });
887
- }
888
- if (result.errors.length > 0) {
889
- console.log(picocolors_1.default.yellow('\nWarnings/Errors:'));
890
- result.errors.forEach((err) => {
891
- console.log(picocolors_1.default.dim(` ⚠ ${err}`));
892
- });
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
+ }
893
1043
  }
894
- bible = result.updatedBible;
895
- // Confirm and regenerate documentation
896
- const proceed = await confirmGate('\nRegenerate documentation with these changes?');
897
- if (!proceed) {
898
- console.log(picocolors_1.default.yellow('Changes discarded.'));
899
- return;
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'));
900
1053
  }
901
- // Run specialists to regenerate markdown
902
- console.log(picocolors_1.default.cyan('\nšŸŽØ Regenerating documentation...\n'));
903
- costTracker.startPhase('production');
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'));
904
1058
  const orchestrator = new Orchestrator_1.SpecialistOrchestrator();
905
1059
  const specialists = [
906
1060
  { name: 'Creative Direction', agent: new CreativeSpecialist_1.CreativeSpecialist(apiKey, costTracker, modelSelector) },
@@ -908,63 +1062,87 @@ async function runModifyFlow(sessionId, options) {
908
1062
  { name: 'Entity Specifications', agent: new EntitySpecialist_1.EntitySpecialist(apiKey, costTracker, modelSelector) },
909
1063
  { name: 'Technical Specifications', agent: new TechSpecialist_1.TechSpecialist(apiKey, costTracker, modelSelector) }
910
1064
  ];
911
- const markdownSections = await orchestrator.runAllSpecialists(bible, specialists, {
1065
+ markdownSections = await orchestrator.runAllSpecialists(updatedBible, specialists, {
912
1066
  skipDisambiguation: true
913
1067
  });
914
- // End specialists phase
915
- const specialistsCost = costTracker.endPhase();
916
- if (specialistsCost) {
917
- costTracker.printPhaseSummary(specialistsCost);
918
- }
919
- // Run validation for gap analysis
920
- console.log(picocolors_1.default.cyan('\nšŸ” Running validation...\n'));
921
- costTracker.startPhase('validation');
922
- const consistencyAgent = new Consistency_1.ConsistencyAgent();
923
- const consistencyIssues = consistencyAgent.validate(bible);
924
- const chaosSpinner = (0, ora_1.default)('Running Chaos Agent...').start();
925
- const chaosAgent = new Chaos_1.Chaos(apiKey, costTracker, modelSelector);
926
- const chaosIssues = await chaosAgent.execute(bible, (message) => {
927
- chaosSpinner.text = `Chaos Agent: ${message}`;
928
- });
929
- chaosSpinner.succeed(`Found ${chaosIssues.length} design issues`);
930
- const validationCost = costTracker.endPhase();
931
- if (validationCost) {
932
- costTracker.printPhaseSummary(validationCost);
933
- }
934
- // Save outputs
935
- console.log(picocolors_1.default.cyan('\nšŸ’¾ Saving outputs...\n'));
936
- const projectDir = await fileManager.initialize(sessionId);
937
- await fileManager.saveGameBible(projectDir, bible);
938
- await fileManager.saveCoverPage(projectDir, bible);
939
- for (const [sectionName, content] of markdownSections) {
940
- await fileManager.saveMarkdown(projectDir, sectionName, content);
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);
941
1125
  }
942
- await fileManager.saveGapAnalysis(projectDir, chaosIssues, consistencyIssues);
943
- await fileManager.saveCombinedGDD(projectDir, bible, markdownSections, chaosIssues, consistencyIssues);
944
- // Save checkpoint
945
- const stateMachine = new StateMachine_1.StateMachine(budgetLimit);
946
- stateMachine.restoreState(latest.state);
947
- stateMachine.updateBible(bible);
948
- await sessionManager.saveCheckpoint(sessionId, stateMachine.getState(), 'after_modification');
949
- // Final output
950
- const report = costTracker.getReport();
951
- console.log(picocolors_1.default.green('\nāœ… Modification complete!\n'));
952
- console.log(picocolors_1.default.dim(`Session ID: ${sessionId}`));
953
- console.log(picocolors_1.default.dim(`Output directory: ${projectDir}`));
954
- costTracker.printFinalBreakdown();
955
- console.log(picocolors_1.default.dim(`Total cost: $${report.total.toFixed(4)} (${report.operations} operations)`));
956
- console.log(picocolors_1.default.dim(`Budget remaining: $${report.remaining.toFixed(2)}\n`));
957
- if (debugLogger_1.debugLogger.isEnabled()) {
958
- console.log(picocolors_1.default.yellow(`šŸ› Debug log saved to: ${debugLogger_1.debugLogger.getLogFilePath()}\n`));
959
- debugLogger_1.debugLogger.disable();
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);
960
1132
  }
961
1133
  }
962
- catch (error) {
963
- console.error(picocolors_1.default.red('Error during modification:'), error);
964
- if (debugLogger_1.debugLogger.isEnabled()) {
965
- debugLogger_1.debugLogger.log('Modification flow failed: ' + String(error));
966
- debugLogger_1.debugLogger.disable();
967
- }
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();
968
1144
  }
1145
+ console.log(picocolors_1.default.dim('\nView this session anytime with:'));
1146
+ console.log(picocolors_1.default.dim(` gameforge session ${sessionId}\n`));
969
1147
  }
970
1148
  //# sourceMappingURL=index.js.map