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.
- package/README.md +117 -44
- package/dist/agents/base/BaseAgent.d.ts +31 -0
- package/dist/agents/base/BaseAgent.d.ts.map +1 -1
- package/dist/agents/base/BaseAgent.js +57 -0
- package/dist/agents/base/BaseAgent.js.map +1 -1
- package/dist/agents/core/Architect.d.ts +21 -5
- package/dist/agents/core/Architect.d.ts.map +1 -1
- package/dist/agents/core/Architect.js +413 -150
- package/dist/agents/core/Architect.js.map +1 -1
- package/dist/agents/core/Chaos.d.ts +4 -0
- package/dist/agents/core/Chaos.d.ts.map +1 -1
- package/dist/agents/core/Chaos.js +46 -11
- package/dist/agents/core/Chaos.js.map +1 -1
- package/dist/agents/core/Consistency.d.ts +1 -0
- package/dist/agents/core/Consistency.d.ts.map +1 -1
- package/dist/agents/core/Consistency.js +86 -11
- package/dist/agents/core/Consistency.js.map +1 -1
- package/dist/agents/core/DocumentUpdater.d.ts +13 -0
- package/dist/agents/core/DocumentUpdater.d.ts.map +1 -0
- package/dist/agents/core/DocumentUpdater.js +165 -0
- package/dist/agents/core/DocumentUpdater.js.map +1 -0
- package/dist/agents/core/Modifier.d.ts +13 -0
- package/dist/agents/core/Modifier.d.ts.map +1 -0
- package/dist/agents/core/Modifier.js +141 -0
- package/dist/agents/core/Modifier.js.map +1 -0
- package/dist/agents/core/Remediation.d.ts +3 -1
- package/dist/agents/core/Remediation.d.ts.map +1 -1
- package/dist/agents/core/Remediation.js +63 -3
- package/dist/agents/core/Remediation.js.map +1 -1
- package/dist/agents/specialists/CreativeSpecialist.d.ts.map +1 -1
- package/dist/agents/specialists/CreativeSpecialist.js +162 -25
- package/dist/agents/specialists/CreativeSpecialist.js.map +1 -1
- package/dist/agents/specialists/EntitySpecialist.d.ts.map +1 -1
- package/dist/agents/specialists/EntitySpecialist.js +79 -25
- package/dist/agents/specialists/EntitySpecialist.js.map +1 -1
- package/dist/agents/specialists/FeatureSpecialist.d.ts +4 -0
- package/dist/agents/specialists/FeatureSpecialist.d.ts.map +1 -1
- package/dist/agents/specialists/FeatureSpecialist.js +114 -39
- package/dist/agents/specialists/FeatureSpecialist.js.map +1 -1
- package/dist/agents/specialists/TechSpecialist.d.ts.map +1 -1
- package/dist/agents/specialists/TechSpecialist.js +169 -32
- package/dist/agents/specialists/TechSpecialist.js.map +1 -1
- package/dist/config/schema.d.ts +1319 -709
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +142 -52
- package/dist/config/schema.js.map +1 -1
- package/dist/config/templates.d.ts.map +1 -1
- package/dist/config/templates.js +6 -66
- package/dist/config/templates.js.map +1 -1
- package/dist/core/Orchestrator.d.ts +17 -3
- package/dist/core/Orchestrator.d.ts.map +1 -1
- package/dist/core/Orchestrator.js +46 -16
- package/dist/core/Orchestrator.js.map +1 -1
- package/dist/index.js +544 -226
- package/dist/index.js.map +1 -1
- package/dist/types/issueReview.d.ts +19 -0
- package/dist/types/issueReview.d.ts.map +1 -0
- package/dist/types/issueReview.js +3 -0
- package/dist/types/issueReview.js.map +1 -0
- package/dist/utils/costTracker.d.ts +28 -0
- package/dist/utils/costTracker.d.ts.map +1 -1
- package/dist/utils/costTracker.js +71 -1
- package/dist/utils/costTracker.js.map +1 -1
- package/dist/utils/disambiguationHelper.d.ts +54 -0
- package/dist/utils/disambiguationHelper.d.ts.map +1 -0
- package/dist/utils/disambiguationHelper.js +262 -0
- package/dist/utils/disambiguationHelper.js.map +1 -0
- package/dist/utils/fileManager.d.ts +7 -0
- package/dist/utils/fileManager.d.ts.map +1 -1
- package/dist/utils/fileManager.js +47 -0
- package/dist/utils/fileManager.js.map +1 -1
- package/dist/utils/issueReviewer.d.ts +10 -0
- package/dist/utils/issueReviewer.d.ts.map +1 -0
- package/dist/utils/issueReviewer.js +206 -0
- package/dist/utils/issueReviewer.js.map +1 -0
- package/dist/utils/issueSelector.d.ts +26 -0
- package/dist/utils/issueSelector.d.ts.map +1 -0
- package/dist/utils/issueSelector.js +132 -0
- package/dist/utils/issueSelector.js.map +1 -0
- package/dist/utils/pdfGenerator.d.ts +12 -0
- package/dist/utils/pdfGenerator.d.ts.map +1 -0
- package/dist/utils/pdfGenerator.js +341 -0
- package/dist/utils/pdfGenerator.js.map +1 -0
- package/package.json +20 -15
- package/dist/core/CheckpointManager.d.ts +0 -16
- package/dist/core/CheckpointManager.d.ts.map +0 -1
- package/dist/core/CheckpointManager.js +0 -52
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
275
|
+
if (!chaosSpinnerPaused) {
|
|
276
|
+
chaosSpinner.text = `Chaos Agent: ${message}`;
|
|
277
|
+
}
|
|
211
278
|
});
|
|
212
279
|
chaosSpinner.succeed(`Found ${currentChaosIssues.length} design issues`);
|
|
213
|
-
//
|
|
214
|
-
const
|
|
215
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
console.log(picocolors_1.default.
|
|
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
|
-
|
|
232
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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.
|
|
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('
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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
|
-
|
|
720
|
-
|
|
802
|
+
},
|
|
803
|
+
resume: (text) => {
|
|
804
|
+
if (chaosSpinnerPaused) {
|
|
805
|
+
chaosSpinner.start(text || chaosSpinner.text);
|
|
806
|
+
chaosSpinnerPaused = false;
|
|
721
807
|
}
|
|
722
808
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
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
|
-
|
|
738
|
-
|
|
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
|
-
|
|
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
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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
|
-
|
|
807
|
-
|
|
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
|
-
|
|
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
|