gameforge-cli 0.1.0
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/LICENSE +21 -0
- package/README.md +366 -0
- package/dist/agents/base/BaseAgent.d.ts +45 -0
- package/dist/agents/base/BaseAgent.d.ts.map +1 -0
- package/dist/agents/base/BaseAgent.js +179 -0
- package/dist/agents/base/BaseAgent.js.map +1 -0
- package/dist/agents/core/Architect.d.ts +16 -0
- package/dist/agents/core/Architect.d.ts.map +1 -0
- package/dist/agents/core/Architect.js +487 -0
- package/dist/agents/core/Architect.js.map +1 -0
- package/dist/agents/core/Chaos.d.ts +14 -0
- package/dist/agents/core/Chaos.d.ts.map +1 -0
- package/dist/agents/core/Chaos.js +67 -0
- package/dist/agents/core/Chaos.js.map +1 -0
- package/dist/agents/core/Consistency.d.ts +16 -0
- package/dist/agents/core/Consistency.d.ts.map +1 -0
- package/dist/agents/core/Consistency.js +132 -0
- package/dist/agents/core/Consistency.js.map +1 -0
- package/dist/agents/core/Inquisitor.d.ts +20 -0
- package/dist/agents/core/Inquisitor.d.ts.map +1 -0
- package/dist/agents/core/Inquisitor.js +159 -0
- package/dist/agents/core/Inquisitor.js.map +1 -0
- package/dist/agents/core/Remediation.d.ts +16 -0
- package/dist/agents/core/Remediation.d.ts.map +1 -0
- package/dist/agents/core/Remediation.js +151 -0
- package/dist/agents/core/Remediation.js.map +1 -0
- package/dist/agents/specialists/CreativeSpecialist.d.ts +7 -0
- package/dist/agents/specialists/CreativeSpecialist.d.ts.map +1 -0
- package/dist/agents/specialists/CreativeSpecialist.js +70 -0
- package/dist/agents/specialists/CreativeSpecialist.js.map +1 -0
- package/dist/agents/specialists/EntitySpecialist.d.ts +8 -0
- package/dist/agents/specialists/EntitySpecialist.d.ts.map +1 -0
- package/dist/agents/specialists/EntitySpecialist.js +74 -0
- package/dist/agents/specialists/EntitySpecialist.js.map +1 -0
- package/dist/agents/specialists/FeatureSpecialist.d.ts +8 -0
- package/dist/agents/specialists/FeatureSpecialist.d.ts.map +1 -0
- package/dist/agents/specialists/FeatureSpecialist.js +83 -0
- package/dist/agents/specialists/FeatureSpecialist.js.map +1 -0
- package/dist/agents/specialists/TechSpecialist.d.ts +7 -0
- package/dist/agents/specialists/TechSpecialist.d.ts.map +1 -0
- package/dist/agents/specialists/TechSpecialist.js +62 -0
- package/dist/agents/specialists/TechSpecialist.js.map +1 -0
- package/dist/config/budget.d.ts +36 -0
- package/dist/config/budget.d.ts.map +1 -0
- package/dist/config/budget.js +37 -0
- package/dist/config/budget.js.map +1 -0
- package/dist/config/schema.d.ts +1336 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +134 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/templates.d.ts +10 -0
- package/dist/config/templates.d.ts.map +1 -0
- package/dist/config/templates.js +202 -0
- package/dist/config/templates.js.map +1 -0
- package/dist/core/CheckpointManager.d.ts +16 -0
- package/dist/core/CheckpointManager.d.ts.map +1 -0
- package/dist/core/CheckpointManager.js +52 -0
- package/dist/core/CheckpointManager.js.map +1 -0
- package/dist/core/Orchestrator.d.ts +11 -0
- package/dist/core/Orchestrator.d.ts.map +1 -0
- package/dist/core/Orchestrator.js +46 -0
- package/dist/core/Orchestrator.js.map +1 -0
- package/dist/core/SessionManager.d.ts +68 -0
- package/dist/core/SessionManager.d.ts.map +1 -0
- package/dist/core/SessionManager.js +162 -0
- package/dist/core/SessionManager.js.map +1 -0
- package/dist/core/StateMachine.d.ts +46 -0
- package/dist/core/StateMachine.d.ts.map +1 -0
- package/dist/core/StateMachine.js +82 -0
- package/dist/core/StateMachine.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +830 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/costTracker.d.ts +30 -0
- package/dist/utils/costTracker.d.ts.map +1 -0
- package/dist/utils/costTracker.js +64 -0
- package/dist/utils/costTracker.js.map +1 -0
- package/dist/utils/debugLogger.d.ts +48 -0
- package/dist/utils/debugLogger.d.ts.map +1 -0
- package/dist/utils/debugLogger.js +179 -0
- package/dist/utils/debugLogger.js.map +1 -0
- package/dist/utils/fileManager.d.ts +14 -0
- package/dist/utils/fileManager.d.ts.map +1 -0
- package/dist/utils/fileManager.js +135 -0
- package/dist/utils/fileManager.js.map +1 -0
- package/dist/utils/modelSelector.d.ts +11 -0
- package/dist/utils/modelSelector.d.ts.map +1 -0
- package/dist/utils/modelSelector.js +38 -0
- package/dist/utils/modelSelector.js.map +1 -0
- package/package.json +49 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,830 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
9
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
10
|
+
const ora_1 = __importDefault(require("ora"));
|
|
11
|
+
const enquirer_1 = __importDefault(require("enquirer"));
|
|
12
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const StateMachine_1 = require("./core/StateMachine");
|
|
15
|
+
const SessionManager_1 = require("./core/SessionManager");
|
|
16
|
+
const costTracker_1 = require("./utils/costTracker");
|
|
17
|
+
const modelSelector_1 = require("./utils/modelSelector");
|
|
18
|
+
const fileManager_1 = require("./utils/fileManager");
|
|
19
|
+
const budget_1 = require("./config/budget");
|
|
20
|
+
const templates_1 = require("./config/templates");
|
|
21
|
+
const Inquisitor_1 = require("./agents/core/Inquisitor");
|
|
22
|
+
const Architect_1 = require("./agents/core/Architect");
|
|
23
|
+
const Consistency_1 = require("./agents/core/Consistency");
|
|
24
|
+
const Chaos_1 = require("./agents/core/Chaos");
|
|
25
|
+
const Remediation_1 = require("./agents/core/Remediation");
|
|
26
|
+
const FeatureSpecialist_1 = require("./agents/specialists/FeatureSpecialist");
|
|
27
|
+
const EntitySpecialist_1 = require("./agents/specialists/EntitySpecialist");
|
|
28
|
+
const TechSpecialist_1 = require("./agents/specialists/TechSpecialist");
|
|
29
|
+
const CreativeSpecialist_1 = require("./agents/specialists/CreativeSpecialist");
|
|
30
|
+
const Orchestrator_1 = require("./core/Orchestrator");
|
|
31
|
+
const debugLogger_1 = require("./utils/debugLogger");
|
|
32
|
+
dotenv_1.default.config();
|
|
33
|
+
const program = new commander_1.Command();
|
|
34
|
+
program
|
|
35
|
+
.name('gameforge')
|
|
36
|
+
.description('AI-powered Game Design Document generator')
|
|
37
|
+
.version('0.1.0');
|
|
38
|
+
program
|
|
39
|
+
.command('create')
|
|
40
|
+
.description('Create a new game design document')
|
|
41
|
+
.option('-b, --budget <tier>', 'Budget tier: quick | standard | deep', 'standard')
|
|
42
|
+
.option('-t, --template <id>', 'Start from template')
|
|
43
|
+
.option('-d, --debug', 'Enable debug logging to file')
|
|
44
|
+
.action(async (options) => {
|
|
45
|
+
await runCreationFlow(options);
|
|
46
|
+
});
|
|
47
|
+
program
|
|
48
|
+
.command('resume <checkpointId>')
|
|
49
|
+
.description('Resume from a saved checkpoint')
|
|
50
|
+
.option('-d, --debug', 'Enable debug logging to file')
|
|
51
|
+
.action(async (checkpointId, options) => {
|
|
52
|
+
await resumeFlow(checkpointId, options);
|
|
53
|
+
});
|
|
54
|
+
program
|
|
55
|
+
.command('sessions')
|
|
56
|
+
.description('List all saved sessions')
|
|
57
|
+
.action(async () => {
|
|
58
|
+
await listSessions();
|
|
59
|
+
});
|
|
60
|
+
program
|
|
61
|
+
.command('session <sessionId>')
|
|
62
|
+
.description('Show details and checkpoints for a session')
|
|
63
|
+
.action(async (sessionId) => {
|
|
64
|
+
await showSession(sessionId);
|
|
65
|
+
});
|
|
66
|
+
program
|
|
67
|
+
.command('templates')
|
|
68
|
+
.description('List available genre templates')
|
|
69
|
+
.action(() => {
|
|
70
|
+
console.log(picocolors_1.default.cyan('\nš Available Templates\n'));
|
|
71
|
+
templates_1.GENRE_TEMPLATES.forEach(template => {
|
|
72
|
+
console.log(picocolors_1.default.green(`${template.id}`));
|
|
73
|
+
console.log(picocolors_1.default.bold(` ${template.name}`));
|
|
74
|
+
console.log(picocolors_1.default.dim(` ${template.description}`));
|
|
75
|
+
console.log(picocolors_1.default.dim(` Tags: ${template.tags.join(', ')}`));
|
|
76
|
+
console.log(picocolors_1.default.dim(` Features: ${template.partialBible.features?.length || 0}\n`));
|
|
77
|
+
});
|
|
78
|
+
console.log(picocolors_1.default.dim('Usage: gameforge create --template <id>\n'));
|
|
79
|
+
});
|
|
80
|
+
program.parse();
|
|
81
|
+
async function runCreationFlow(options) {
|
|
82
|
+
console.clear();
|
|
83
|
+
console.log(picocolors_1.default.cyan('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
84
|
+
console.log(picocolors_1.default.cyan('ā GameForge CLI ā'));
|
|
85
|
+
console.log(picocolors_1.default.cyan('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n'));
|
|
86
|
+
console.log(picocolors_1.default.dim('AI-Powered Game Design Documents\n'));
|
|
87
|
+
// Enable debug logging if requested
|
|
88
|
+
if (options.debug) {
|
|
89
|
+
const projectId = `gameforge-${Date.now()}`;
|
|
90
|
+
debugLogger_1.debugLogger.enable(projectId);
|
|
91
|
+
console.log(picocolors_1.default.yellow(`š Debug mode enabled`));
|
|
92
|
+
console.log(picocolors_1.default.dim(` Log file: ${debugLogger_1.debugLogger.getLogFilePath()}\n`));
|
|
93
|
+
}
|
|
94
|
+
// Setup
|
|
95
|
+
const budgetTier = budget_1.BUDGET_TIERS[options.budget];
|
|
96
|
+
if (!budgetTier) {
|
|
97
|
+
console.error(picocolors_1.default.red(`Unknown budget tier: ${options.budget}`));
|
|
98
|
+
console.log(picocolors_1.default.dim('Available tiers: quick, standard, deep'));
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
102
|
+
if (!apiKey) {
|
|
103
|
+
console.error(picocolors_1.default.red('Error: ANTHROPIC_API_KEY not set in .env'));
|
|
104
|
+
console.log(picocolors_1.default.yellow('\nPlease create a .env file with your Anthropic API key:'));
|
|
105
|
+
console.log(picocolors_1.default.dim('ANTHROPIC_API_KEY=your_api_key_here\n'));
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
// Prompt for session title
|
|
109
|
+
const { title } = await enquirer_1.default.prompt({
|
|
110
|
+
type: 'input',
|
|
111
|
+
name: 'title',
|
|
112
|
+
message: 'Session name (e.g., "My Card Game", "RPG Project"):',
|
|
113
|
+
initial: 'New Game Design'
|
|
114
|
+
});
|
|
115
|
+
const costTracker = new costTracker_1.CostTracker(budgetTier.maxBudget);
|
|
116
|
+
const modelSelector = new modelSelector_1.ModelSelector();
|
|
117
|
+
const stateMachine = new StateMachine_1.StateMachine(budgetTier.maxBudget);
|
|
118
|
+
const sessionManager = new SessionManager_1.SessionManager();
|
|
119
|
+
const fileManager = new fileManager_1.FileManager();
|
|
120
|
+
// Create a new session
|
|
121
|
+
const sessionId = await sessionManager.createSession(title);
|
|
122
|
+
const projectId = sessionId; // Use session ID as project ID for consistency
|
|
123
|
+
console.log(picocolors_1.default.green(`\nā Session created: ${sessionId}`));
|
|
124
|
+
console.log(picocolors_1.default.green(`Budget: ${budgetTier.name} ($${budgetTier.maxBudget})`));
|
|
125
|
+
console.log(picocolors_1.default.dim(`Model: ${budgetTier.preferredModel}\n`));
|
|
126
|
+
try {
|
|
127
|
+
// Check if template was requested
|
|
128
|
+
let selectedTemplate = null;
|
|
129
|
+
if (options.template) {
|
|
130
|
+
selectedTemplate = templates_1.GENRE_TEMPLATES.find(t => t.id === options.template);
|
|
131
|
+
if (!selectedTemplate) {
|
|
132
|
+
console.error(picocolors_1.default.red(`Template not found: ${options.template}`));
|
|
133
|
+
console.log(picocolors_1.default.yellow('\nAvailable templates:'));
|
|
134
|
+
templates_1.GENRE_TEMPLATES.forEach(t => {
|
|
135
|
+
console.log(picocolors_1.default.dim(` - ${t.id}: ${t.name}`));
|
|
136
|
+
});
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
console.log(picocolors_1.default.green(`Using template: ${selectedTemplate.name}`));
|
|
140
|
+
console.log(picocolors_1.default.dim(`${selectedTemplate.description}\n`));
|
|
141
|
+
}
|
|
142
|
+
// Phase 1: Discovery
|
|
143
|
+
console.log(picocolors_1.default.cyan('\nš Phase 1: Discovery\n'));
|
|
144
|
+
stateMachine.transition(StateMachine_1.GameForgePhase.DISCOVERY);
|
|
145
|
+
const inquisitor = new Inquisitor_1.Inquisitor(apiKey, costTracker, modelSelector);
|
|
146
|
+
const questions = getInquisitorQuestions(options.template);
|
|
147
|
+
const transcript = await inquisitor.execute(questions);
|
|
148
|
+
transcript.forEach(entry => {
|
|
149
|
+
stateMachine.addTranscriptEntry(entry.question, entry.answer, entry.autoGenerated);
|
|
150
|
+
});
|
|
151
|
+
// Gate 1: Review Concept
|
|
152
|
+
console.log(picocolors_1.default.cyan('\nš Concept Review\n'));
|
|
153
|
+
stateMachine.transition(StateMachine_1.GameForgePhase.REVIEW_CONCEPT);
|
|
154
|
+
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
|
+
await sessionManager.saveCheckpoint(sessionId, stateMachine.getState(), 'after_discovery');
|
|
161
|
+
// Phase 2: Architecture
|
|
162
|
+
console.log(picocolors_1.default.cyan('\nšļø Phase 2: Architecture\n'));
|
|
163
|
+
stateMachine.transition(StateMachine_1.GameForgePhase.ARCHITECTURE);
|
|
164
|
+
const spinner = (0, ora_1.default)('Generating Game Bible...').start();
|
|
165
|
+
const architect = new Architect_1.Architect(apiKey, costTracker, modelSelector);
|
|
166
|
+
let bible = await architect.execute(transcript, (message) => {
|
|
167
|
+
spinner.text = `Generating Game Bible: ${message}`;
|
|
168
|
+
});
|
|
169
|
+
// Merge template features if a template was selected
|
|
170
|
+
if (selectedTemplate && selectedTemplate.partialBible.features) {
|
|
171
|
+
console.log(picocolors_1.default.dim(` Merging ${selectedTemplate.partialBible.features.length} template features...`));
|
|
172
|
+
bible.features = [...selectedTemplate.partialBible.features, ...bible.features];
|
|
173
|
+
}
|
|
174
|
+
stateMachine.updateBible(bible);
|
|
175
|
+
spinner.succeed('Game Bible generated');
|
|
176
|
+
// Save checkpoint
|
|
177
|
+
await sessionManager.saveCheckpoint(sessionId, stateMachine.getState(), 'after_architecture');
|
|
178
|
+
// Gate 2: Review Data
|
|
179
|
+
console.log(picocolors_1.default.cyan('\nš Data Review\n'));
|
|
180
|
+
stateMachine.transition(StateMachine_1.GameForgePhase.REVIEW_DATA);
|
|
181
|
+
console.log(picocolors_1.default.green(`ā Generated ${bible.features.length} features`));
|
|
182
|
+
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
|
+
// Phase 3: Production (Specialists)
|
|
189
|
+
console.log(picocolors_1.default.cyan('\nšØ Phase 3: Production\n'));
|
|
190
|
+
stateMachine.transition(StateMachine_1.GameForgePhase.PRODUCTION);
|
|
191
|
+
const orchestrator = new Orchestrator_1.SpecialistOrchestrator(2); // Concurrency of 2
|
|
192
|
+
const specialists = [
|
|
193
|
+
{ name: 'Creative Direction', agent: new CreativeSpecialist_1.CreativeSpecialist(apiKey, costTracker, modelSelector) },
|
|
194
|
+
{ name: 'Feature Specifications', agent: new FeatureSpecialist_1.FeatureSpecialist(apiKey, costTracker, modelSelector) },
|
|
195
|
+
{ name: 'Entity Specifications', agent: new EntitySpecialist_1.EntitySpecialist(apiKey, costTracker, modelSelector) },
|
|
196
|
+
{ name: 'Technical Specifications', agent: new TechSpecialist_1.TechSpecialist(apiKey, costTracker, modelSelector) }
|
|
197
|
+
];
|
|
198
|
+
let markdownSections = await orchestrator.runAllSpecialists(bible, specialists);
|
|
199
|
+
// Phase 4: Validation
|
|
200
|
+
console.log(picocolors_1.default.cyan('\nā
Phase 4: Validation\n'));
|
|
201
|
+
stateMachine.transition(StateMachine_1.GameForgePhase.VALIDATION);
|
|
202
|
+
// Run consistency check
|
|
203
|
+
const consistencyAgent = new Consistency_1.ConsistencyAgent();
|
|
204
|
+
let currentConsistencyIssues = consistencyAgent.validate(bible);
|
|
205
|
+
console.log(picocolors_1.default.yellow(`Found ${currentConsistencyIssues.length} consistency issues`));
|
|
206
|
+
// Run chaos analysis
|
|
207
|
+
let chaosSpinner = (0, ora_1.default)('Running Chaos Agent...').start();
|
|
208
|
+
const chaosAgent = new Chaos_1.Chaos(apiKey, costTracker, modelSelector);
|
|
209
|
+
let currentChaosIssues = await chaosAgent.execute(bible, (message) => {
|
|
210
|
+
chaosSpinner.text = `Chaos Agent: ${message}`;
|
|
211
|
+
});
|
|
212
|
+
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;
|
|
216
|
+
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`));
|
|
230
|
+
}
|
|
231
|
+
if (fixAttempts > 0) {
|
|
232
|
+
console.log(picocolors_1.default.cyan(`\n(Fix attempt ${fixAttempts}/${MAX_FIX_ATTEMPTS} complete)`));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
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;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// If fixes were applied, re-run specialists to regenerate markdown
|
|
310
|
+
if (fixesApplied) {
|
|
311
|
+
console.log(picocolors_1.default.cyan('\nš Regenerating documentation with fixed Bible...\n'));
|
|
312
|
+
markdownSections = await orchestrator.runAllSpecialists(bible, specialists);
|
|
313
|
+
}
|
|
314
|
+
// Save all outputs
|
|
315
|
+
console.log(picocolors_1.default.cyan('\nš¾ Saving outputs...\n'));
|
|
316
|
+
const projectDir = await fileManager.initialize(projectId);
|
|
317
|
+
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'));
|
|
327
|
+
// Complete
|
|
328
|
+
stateMachine.transition(StateMachine_1.GameForgePhase.COMPLETE);
|
|
329
|
+
// Save final checkpoint
|
|
330
|
+
await sessionManager.saveCheckpoint(sessionId, stateMachine.getState(), 'complete');
|
|
331
|
+
// Show final report
|
|
332
|
+
const report = costTracker.getReport();
|
|
333
|
+
console.log(picocolors_1.default.green('\nā
Complete!\n'));
|
|
334
|
+
console.log(picocolors_1.default.dim(`Session ID: ${sessionId}`));
|
|
335
|
+
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)`));
|
|
337
|
+
console.log(picocolors_1.default.dim(`Budget remaining: $${report.remaining.toFixed(2)}\n`));
|
|
338
|
+
if (debugLogger_1.debugLogger.isEnabled()) {
|
|
339
|
+
console.log(picocolors_1.default.yellow(`š Debug log saved to: ${debugLogger_1.debugLogger.getLogFilePath()}\n`));
|
|
340
|
+
debugLogger_1.debugLogger.disable();
|
|
341
|
+
}
|
|
342
|
+
console.log(picocolors_1.default.dim('View this session anytime with:'));
|
|
343
|
+
console.log(picocolors_1.default.dim(` gameforge session ${sessionId}\n`));
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
if (debugLogger_1.debugLogger.isEnabled()) {
|
|
347
|
+
debugLogger_1.debugLogger.logError('RunCreationFlow', error);
|
|
348
|
+
console.log(picocolors_1.default.yellow(`\nš Debug log with error details: ${debugLogger_1.debugLogger.getLogFilePath()}\n`));
|
|
349
|
+
debugLogger_1.debugLogger.disable();
|
|
350
|
+
}
|
|
351
|
+
console.error(picocolors_1.default.red('\nā Error:'), error);
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function getInquisitorQuestions(templateId) {
|
|
356
|
+
return [
|
|
357
|
+
{
|
|
358
|
+
question: 'What is your game\'s core concept? (Elevator pitch)',
|
|
359
|
+
category: 'meta',
|
|
360
|
+
allowCustom: true,
|
|
361
|
+
allowAIDecide: false
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
question: 'Select primary genre:',
|
|
365
|
+
category: 'meta',
|
|
366
|
+
suggestedAnswers: ['Roguelike', 'FPS', 'RPG', 'Platformer', 'Puzzle', 'Strategy', 'Simulation'],
|
|
367
|
+
allowCustom: true,
|
|
368
|
+
allowAIDecide: false
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
question: 'What platforms are you targeting?',
|
|
372
|
+
category: 'technical',
|
|
373
|
+
suggestedAnswers: ['PC Only', 'PC + Consoles', 'Mobile', 'Multi-platform'],
|
|
374
|
+
allowCustom: true,
|
|
375
|
+
allowAIDecide: true
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
question: 'What is the estimated scope?',
|
|
379
|
+
category: 'meta',
|
|
380
|
+
suggestedAnswers: ['Prototype', 'Vertical Slice', 'MVP', 'Full Game'],
|
|
381
|
+
allowCustom: false,
|
|
382
|
+
allowAIDecide: true
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
question: 'Which game engine?',
|
|
386
|
+
category: 'technical',
|
|
387
|
+
suggestedAnswers: ['Unreal Engine 5', 'Unity', 'Godot', 'Custom'],
|
|
388
|
+
allowCustom: true,
|
|
389
|
+
allowAIDecide: true
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
question: 'Describe your art style:',
|
|
393
|
+
category: 'creative',
|
|
394
|
+
suggestedAnswers: ['2D Pixel Art', '3D Low-poly', '3D Realistic', 'Hand-drawn 2D', 'Voxel'],
|
|
395
|
+
allowCustom: true,
|
|
396
|
+
allowAIDecide: true
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
question: 'What is the core gameplay loop?',
|
|
400
|
+
category: 'gameplay',
|
|
401
|
+
allowCustom: true,
|
|
402
|
+
allowAIDecide: false
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
question: 'Who is the target audience?',
|
|
406
|
+
category: 'meta',
|
|
407
|
+
suggestedAnswers: ['Casual players', 'Hardcore gamers', 'Families', 'Competitive players'],
|
|
408
|
+
allowCustom: true,
|
|
409
|
+
allowAIDecide: true
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
question: 'Monetization strategy?',
|
|
413
|
+
category: 'meta',
|
|
414
|
+
suggestedAnswers: ['Premium (one-time purchase)', 'Free-to-play + IAP', 'Free with Ads', 'Subscription'],
|
|
415
|
+
allowCustom: true,
|
|
416
|
+
allowAIDecide: true
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
question: 'Any key features you want to include?',
|
|
420
|
+
category: 'gameplay',
|
|
421
|
+
allowCustom: true,
|
|
422
|
+
allowAIDecide: false
|
|
423
|
+
}
|
|
424
|
+
];
|
|
425
|
+
}
|
|
426
|
+
function showConceptBrief(transcript) {
|
|
427
|
+
console.log(picocolors_1.default.bold('Concept Brief:'));
|
|
428
|
+
transcript.forEach(entry => {
|
|
429
|
+
const marker = entry.autoGenerated ? picocolors_1.default.yellow(' [AI]') : '';
|
|
430
|
+
console.log(picocolors_1.default.dim(` ${entry.question}`));
|
|
431
|
+
console.log(picocolors_1.default.green(` ā ${entry.answer}${marker}`));
|
|
432
|
+
});
|
|
433
|
+
console.log('');
|
|
434
|
+
}
|
|
435
|
+
async function confirmGate(message) {
|
|
436
|
+
const response = await enquirer_1.default.prompt({
|
|
437
|
+
type: 'confirm',
|
|
438
|
+
name: 'confirm',
|
|
439
|
+
message,
|
|
440
|
+
initial: true
|
|
441
|
+
});
|
|
442
|
+
return response.confirm;
|
|
443
|
+
}
|
|
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
|
+
async function listSessions() {
|
|
458
|
+
console.log(picocolors_1.default.cyan('\nš Saved Sessions\n'));
|
|
459
|
+
const sessionManager = new SessionManager_1.SessionManager();
|
|
460
|
+
try {
|
|
461
|
+
const sessions = await sessionManager.listSessions();
|
|
462
|
+
if (sessions.length === 0) {
|
|
463
|
+
console.log(picocolors_1.default.dim('No sessions found.'));
|
|
464
|
+
console.log(picocolors_1.default.dim('\nCreate a new session with: gameforge create\n'));
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
sessions.forEach((session) => {
|
|
468
|
+
console.log(picocolors_1.default.green(session.sessionId));
|
|
469
|
+
console.log(picocolors_1.default.bold(` ${session.title}`));
|
|
470
|
+
console.log(picocolors_1.default.dim(` Phase: ${session.currentPhase}`));
|
|
471
|
+
console.log(picocolors_1.default.dim(` Checkpoints: ${session.checkpointCount}`));
|
|
472
|
+
console.log(picocolors_1.default.dim(` Created: ${new Date(session.createdAt).toLocaleString()}`));
|
|
473
|
+
console.log(picocolors_1.default.dim(` Modified: ${new Date(session.lastModified).toLocaleString()}\n`));
|
|
474
|
+
});
|
|
475
|
+
console.log(picocolors_1.default.dim('Usage:'));
|
|
476
|
+
console.log(picocolors_1.default.dim(' gameforge resume <session-id> Resume from latest checkpoint'));
|
|
477
|
+
console.log(picocolors_1.default.dim(' gameforge session <session-id> View session details\n'));
|
|
478
|
+
}
|
|
479
|
+
catch (error) {
|
|
480
|
+
console.error(picocolors_1.default.red('Error listing sessions:'), error);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
async function showSession(sessionId) {
|
|
484
|
+
console.log(picocolors_1.default.cyan(`\nš Session Details: ${sessionId}\n`));
|
|
485
|
+
const sessionManager = new SessionManager_1.SessionManager();
|
|
486
|
+
try {
|
|
487
|
+
const session = await sessionManager.getSession(sessionId);
|
|
488
|
+
if (!session) {
|
|
489
|
+
console.error(picocolors_1.default.red(`Session not found: ${sessionId}`));
|
|
490
|
+
console.log(picocolors_1.default.dim('\nList all sessions with: gameforge sessions\n'));
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
console.log(picocolors_1.default.bold(session.title));
|
|
494
|
+
console.log(picocolors_1.default.dim(`Session ID: ${session.sessionId}`));
|
|
495
|
+
console.log(picocolors_1.default.dim(`Current Phase: ${session.currentPhase}`));
|
|
496
|
+
console.log(picocolors_1.default.dim(`Created: ${new Date(session.createdAt).toLocaleString()}`));
|
|
497
|
+
console.log(picocolors_1.default.dim(`Last Modified: ${new Date(session.lastModified).toLocaleString()}`));
|
|
498
|
+
console.log(picocolors_1.default.dim(`Total Checkpoints: ${session.checkpointCount}\n`));
|
|
499
|
+
// List checkpoints for this session
|
|
500
|
+
const checkpoints = await sessionManager.listCheckpoints(sessionId);
|
|
501
|
+
if (checkpoints.length === 0) {
|
|
502
|
+
console.log(picocolors_1.default.dim('No checkpoints found for this session.'));
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
console.log(picocolors_1.default.cyan('Checkpoints:\n'));
|
|
506
|
+
checkpoints.forEach((cp, idx) => {
|
|
507
|
+
const isLatest = cp.checkpointId === session.latestCheckpointId;
|
|
508
|
+
const prefix = isLatest ? picocolors_1.default.green('ā ') : ' ';
|
|
509
|
+
console.log(`${prefix}${picocolors_1.default.bold(cp.label)} ${isLatest ? picocolors_1.default.green('(latest)') : ''}`);
|
|
510
|
+
console.log(` ${picocolors_1.default.dim('ID:')} ${cp.checkpointId}`);
|
|
511
|
+
console.log(` ${picocolors_1.default.dim('Phase:')} ${cp.phase}`);
|
|
512
|
+
console.log(` ${picocolors_1.default.dim('Budget:')} $${cp.budgetUsed.toFixed(2)} / $${cp.budgetLimit.toFixed(2)}`);
|
|
513
|
+
console.log(` ${picocolors_1.default.dim('Saved:')} ${new Date(cp.timestamp).toLocaleString()}\n`);
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
console.log(picocolors_1.default.dim('Usage:'));
|
|
517
|
+
console.log(picocolors_1.default.dim(` gameforge resume ${sessionId} Resume from latest checkpoint`));
|
|
518
|
+
console.log(picocolors_1.default.dim(` gameforge resume <checkpoint-id> Resume from specific checkpoint\n`));
|
|
519
|
+
}
|
|
520
|
+
catch (error) {
|
|
521
|
+
console.error(picocolors_1.default.red('Error showing session:'), error);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
async function resumeFlow(idOrCheckpoint, options = {}) {
|
|
525
|
+
console.clear();
|
|
526
|
+
console.log(picocolors_1.default.cyan('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
527
|
+
console.log(picocolors_1.default.cyan('ā GameForge CLI - Resume ā'));
|
|
528
|
+
console.log(picocolors_1.default.cyan('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n'));
|
|
529
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
530
|
+
if (!apiKey) {
|
|
531
|
+
console.error(picocolors_1.default.red('Error: ANTHROPIC_API_KEY not set in .env'));
|
|
532
|
+
process.exit(1);
|
|
533
|
+
}
|
|
534
|
+
const sessionManager = new SessionManager_1.SessionManager();
|
|
535
|
+
try {
|
|
536
|
+
// Determine if this is a session ID or checkpoint ID
|
|
537
|
+
let sessionId;
|
|
538
|
+
let checkpointId;
|
|
539
|
+
let savedState;
|
|
540
|
+
if (idOrCheckpoint.startsWith('session-')) {
|
|
541
|
+
// It's a session ID - load latest checkpoint
|
|
542
|
+
sessionId = idOrCheckpoint;
|
|
543
|
+
console.log(picocolors_1.default.yellow(`Loading latest checkpoint for session: ${sessionId}...`));
|
|
544
|
+
const latest = await sessionManager.getLatestCheckpoint(sessionId);
|
|
545
|
+
if (!latest) {
|
|
546
|
+
console.error(picocolors_1.default.red(`No checkpoints found for session: ${sessionId}`));
|
|
547
|
+
console.log(picocolors_1.default.dim('\nList all sessions with: gameforge sessions\n'));
|
|
548
|
+
process.exit(1);
|
|
549
|
+
}
|
|
550
|
+
checkpointId = latest.id;
|
|
551
|
+
savedState = latest.state;
|
|
552
|
+
console.log(picocolors_1.default.green(`ā Using checkpoint: ${checkpointId}`));
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
// It's a checkpoint ID - load it directly
|
|
556
|
+
checkpointId = idOrCheckpoint;
|
|
557
|
+
console.log(picocolors_1.default.yellow(`Loading checkpoint: ${checkpointId}...`));
|
|
558
|
+
savedState = await sessionManager.loadCheckpoint(checkpointId);
|
|
559
|
+
// Extract session ID from checkpoint ID
|
|
560
|
+
sessionId = checkpointId.split('_')[0];
|
|
561
|
+
// Check if this is an old-format checkpoint (starts with "gameforge-" instead of "session-")
|
|
562
|
+
if (sessionId.startsWith('gameforge-')) {
|
|
563
|
+
// Old checkpoint format - create a session for it if it doesn't exist
|
|
564
|
+
const existingSession = await sessionManager.getSession(sessionId);
|
|
565
|
+
if (!existingSession) {
|
|
566
|
+
console.log(picocolors_1.default.dim('Migrating old checkpoint to session-based format...'));
|
|
567
|
+
// Extract the checkpoint label to create a meaningful session title
|
|
568
|
+
const checkpointData = await fs_extra_1.default.readJson(path_1.default.join('.gameforge', 'checkpoints', `${checkpointId}.json`));
|
|
569
|
+
const sessionTitle = `Migrated Session (${checkpointData.label})`;
|
|
570
|
+
// Create a new session with the old project ID as the session ID for backward compatibility
|
|
571
|
+
const sessionPath = path_1.default.join('.gameforge', 'sessions', `${sessionId}.json`);
|
|
572
|
+
const sessionMetadata = {
|
|
573
|
+
sessionId: sessionId,
|
|
574
|
+
title: sessionTitle,
|
|
575
|
+
createdAt: checkpointData.timestamp,
|
|
576
|
+
lastModified: checkpointData.timestamp,
|
|
577
|
+
currentPhase: savedState.phase,
|
|
578
|
+
checkpointCount: 1,
|
|
579
|
+
latestCheckpointId: checkpointId
|
|
580
|
+
};
|
|
581
|
+
await fs_extra_1.default.writeJson(sessionPath, sessionMetadata, { spaces: 2 });
|
|
582
|
+
console.log(picocolors_1.default.green(`ā Created session: ${sessionId}`));
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
// Enable debug logging if requested
|
|
587
|
+
if (options.debug) {
|
|
588
|
+
debugLogger_1.debugLogger.enable(sessionId);
|
|
589
|
+
console.log(picocolors_1.default.yellow(`š Debug mode enabled`));
|
|
590
|
+
console.log(picocolors_1.default.dim(` Log file: ${debugLogger_1.debugLogger.getLogFilePath()}\n`));
|
|
591
|
+
}
|
|
592
|
+
console.log(picocolors_1.default.green(`ā Loaded checkpoint from phase: ${savedState.phase}`));
|
|
593
|
+
console.log(picocolors_1.default.dim(` Budget used: $${savedState.costAccumulated.toFixed(2)}/$${savedState.budgetLimit.toFixed(2)}\n`));
|
|
594
|
+
// Recreate the state machine and restore state
|
|
595
|
+
const stateMachine = new StateMachine_1.StateMachine(savedState.budgetLimit);
|
|
596
|
+
stateMachine.restoreState(savedState);
|
|
597
|
+
const costTracker = new costTracker_1.CostTracker(savedState.budgetLimit);
|
|
598
|
+
const modelSelector = new modelSelector_1.ModelSelector();
|
|
599
|
+
const fileManager = new fileManager_1.FileManager();
|
|
600
|
+
const projectId = sessionId;
|
|
601
|
+
// Determine what phase we're resuming from
|
|
602
|
+
const currentPhase = savedState.phase;
|
|
603
|
+
// If we have a transcript from discovery/review phase, we can use or regenerate the Game Bible
|
|
604
|
+
if (currentPhase === StateMachine_1.GameForgePhase.REVIEW_CONCEPT || currentPhase === StateMachine_1.GameForgePhase.DISCOVERY ||
|
|
605
|
+
currentPhase === StateMachine_1.GameForgePhase.ARCHITECTURE || currentPhase === StateMachine_1.GameForgePhase.REVIEW_DATA) {
|
|
606
|
+
if (!savedState.transcript || savedState.transcript.length === 0) {
|
|
607
|
+
console.log(picocolors_1.default.yellow('Checkpoint has no transcript data. Please restart with a new session.'));
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
let bible;
|
|
611
|
+
// Check if we already have a generated bible in the checkpoint
|
|
612
|
+
if (savedState.bible && savedState.bible.features && savedState.bible.features.length > 0) {
|
|
613
|
+
// Use existing bible from checkpoint
|
|
614
|
+
console.log(picocolors_1.default.cyan('\nšļø Loading Game Bible from checkpoint\n'));
|
|
615
|
+
console.log(picocolors_1.default.green(`ā Found existing Game Bible with ${savedState.bible.features.length} features`));
|
|
616
|
+
console.log(picocolors_1.default.green(`ā Found ${savedState.bible.gameObjects.length} game objects\n`));
|
|
617
|
+
bible = savedState.bible;
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
// No bible in checkpoint, need to regenerate (backward compatibility)
|
|
621
|
+
console.log(picocolors_1.default.cyan('\nšļø Generating Game Bible\n'));
|
|
622
|
+
console.log(picocolors_1.default.dim(`Found ${savedState.transcript.length} interview questions/answers\n`));
|
|
623
|
+
// Transition to architecture phase if we're coming from review_concept or discovery
|
|
624
|
+
if (currentPhase === StateMachine_1.GameForgePhase.REVIEW_CONCEPT || currentPhase === StateMachine_1.GameForgePhase.DISCOVERY) {
|
|
625
|
+
stateMachine.transition(StateMachine_1.GameForgePhase.ARCHITECTURE);
|
|
626
|
+
}
|
|
627
|
+
const architect = new Architect_1.Architect(apiKey, costTracker, modelSelector);
|
|
628
|
+
const spinner = (0, ora_1.default)('Generating Game Bible...').start();
|
|
629
|
+
bible = await architect.execute(savedState.transcript);
|
|
630
|
+
spinner.succeed('Game Bible generated');
|
|
631
|
+
}
|
|
632
|
+
stateMachine.updateBible(bible);
|
|
633
|
+
// Transition to review_data phase
|
|
634
|
+
stateMachine.transition(StateMachine_1.GameForgePhase.REVIEW_DATA);
|
|
635
|
+
// Save checkpoint after successful generation (only if we regenerated)
|
|
636
|
+
if (!savedState.bible || !savedState.bible.features) {
|
|
637
|
+
await sessionManager.saveCheckpoint(sessionId, stateMachine.getState(), 'after_architecture');
|
|
638
|
+
}
|
|
639
|
+
// Transition to production and continue
|
|
640
|
+
stateMachine.transition(StateMachine_1.GameForgePhase.PRODUCTION);
|
|
641
|
+
await continueToProduction(stateMachine, bible, apiKey, costTracker, modelSelector, fileManager, sessionId, sessionManager);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (currentPhase === StateMachine_1.GameForgePhase.PRODUCTION || currentPhase === StateMachine_1.GameForgePhase.VALIDATION || currentPhase === StateMachine_1.GameForgePhase.REVIEW_ISSUES) {
|
|
645
|
+
console.log(picocolors_1.default.cyan('\nšØ Resuming from Production/Validation\n'));
|
|
646
|
+
const bible = savedState.bible; // GameBible
|
|
647
|
+
if (!bible || !bible.features) {
|
|
648
|
+
console.error(picocolors_1.default.red('Error: No GameBible found in checkpoint'));
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
// Handle phase transitions based on current phase
|
|
652
|
+
if (currentPhase === StateMachine_1.GameForgePhase.REVIEW_ISSUES) {
|
|
653
|
+
// REVIEW_ISSUES can transition back to PRODUCTION for retry
|
|
654
|
+
console.log(picocolors_1.default.yellow('Regenerating all outputs from production phase...'));
|
|
655
|
+
stateMachine.transition(StateMachine_1.GameForgePhase.PRODUCTION);
|
|
656
|
+
}
|
|
657
|
+
else if (currentPhase === StateMachine_1.GameForgePhase.VALIDATION) {
|
|
658
|
+
// VALIDATION cannot go back, but we're already past production
|
|
659
|
+
console.log(picocolors_1.default.yellow('Checkpoint is at validation phase. Continuing from there...'));
|
|
660
|
+
// We'll skip the production phase and go straight to validation in continueToProduction
|
|
661
|
+
}
|
|
662
|
+
// If currentPhase is already PRODUCTION, no transition needed
|
|
663
|
+
await continueToProduction(stateMachine, bible, apiKey, costTracker, modelSelector, fileManager, sessionId, sessionManager);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
console.log(picocolors_1.default.green('Checkpoint already complete!'));
|
|
667
|
+
}
|
|
668
|
+
catch (error) {
|
|
669
|
+
if (debugLogger_1.debugLogger.isEnabled()) {
|
|
670
|
+
debugLogger_1.debugLogger.logError('ResumeFlow', error);
|
|
671
|
+
console.log(picocolors_1.default.yellow(`\nš Debug log with error details: ${debugLogger_1.debugLogger.getLogFilePath()}\n`));
|
|
672
|
+
debugLogger_1.debugLogger.disable();
|
|
673
|
+
}
|
|
674
|
+
console.error(picocolors_1.default.red('\nā Error resuming:'), error);
|
|
675
|
+
process.exit(1);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
async function continueToProduction(stateMachine, bible, apiKey, costTracker, modelSelector, fileManager, sessionId, sessionManager) {
|
|
679
|
+
// Phase 3: Production (Specialists)
|
|
680
|
+
console.log(picocolors_1.default.cyan('\nšØ Phase 3: Production\n'));
|
|
681
|
+
// Note: Caller should have already transitioned to PRODUCTION phase
|
|
682
|
+
const projectId = sessionId;
|
|
683
|
+
const orchestrator = new Orchestrator_1.SpecialistOrchestrator(2);
|
|
684
|
+
const specialists = [
|
|
685
|
+
{ name: 'Creative Direction', agent: new CreativeSpecialist_1.CreativeSpecialist(apiKey, costTracker, modelSelector) },
|
|
686
|
+
{ name: 'Feature Specifications', agent: new FeatureSpecialist_1.FeatureSpecialist(apiKey, costTracker, modelSelector) },
|
|
687
|
+
{ name: 'Entity Specifications', agent: new EntitySpecialist_1.EntitySpecialist(apiKey, costTracker, modelSelector) },
|
|
688
|
+
{ name: 'Technical Specifications', agent: new TechSpecialist_1.TechSpecialist(apiKey, costTracker, modelSelector) }
|
|
689
|
+
];
|
|
690
|
+
let markdownSections = await orchestrator.runAllSpecialists(bible, specialists);
|
|
691
|
+
// Phase 4: Validation
|
|
692
|
+
console.log(picocolors_1.default.cyan('\nā
Phase 4: Validation\n'));
|
|
693
|
+
stateMachine.transition(StateMachine_1.GameForgePhase.VALIDATION);
|
|
694
|
+
const consistencyAgent = new Consistency_1.ConsistencyAgent();
|
|
695
|
+
let currentConsistencyIssues = consistencyAgent.validate(bible);
|
|
696
|
+
console.log(picocolors_1.default.yellow(`Found ${currentConsistencyIssues.length} consistency issues`));
|
|
697
|
+
let chaosSpinner = (0, ora_1.default)('Running Chaos Agent...').start();
|
|
698
|
+
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`));
|
|
718
|
+
}
|
|
719
|
+
if (fixAttempts > 0) {
|
|
720
|
+
console.log(picocolors_1.default.cyan(`\n(Fix attempt ${fixAttempts}/${MAX_FIX_ATTEMPTS} complete)`));
|
|
721
|
+
}
|
|
722
|
+
}
|
|
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';
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
action = await selectReviewAction();
|
|
739
|
+
}
|
|
740
|
+
if (action === 'exit') {
|
|
741
|
+
console.log(picocolors_1.default.yellow('Exiting...'));
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
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;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
// If fixes were applied, re-run specialists to regenerate markdown
|
|
798
|
+
if (fixesApplied) {
|
|
799
|
+
console.log(picocolors_1.default.cyan('\nš Regenerating documentation with fixed Bible...\n'));
|
|
800
|
+
markdownSections = await orchestrator.runAllSpecialists(bible, specialists);
|
|
801
|
+
}
|
|
802
|
+
// Save all outputs
|
|
803
|
+
console.log(picocolors_1.default.cyan('\nš¾ Saving outputs...\n'));
|
|
804
|
+
const projectDir = await fileManager.initialize(projectId);
|
|
805
|
+
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
|
|
813
|
+
await fileManager.saveCombinedGDD(projectDir, bible, markdownSections, currentChaosIssues, currentConsistencyIssues);
|
|
814
|
+
stateMachine.transition(StateMachine_1.GameForgePhase.COMPLETE);
|
|
815
|
+
// Save final checkpoint
|
|
816
|
+
await sessionManager.saveCheckpoint(sessionId, stateMachine.getState(), 'complete');
|
|
817
|
+
const report = costTracker.getReport();
|
|
818
|
+
console.log(picocolors_1.default.green('\nā
Complete!\n'));
|
|
819
|
+
console.log(picocolors_1.default.dim(`Session ID: ${sessionId}`));
|
|
820
|
+
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)`));
|
|
822
|
+
console.log(picocolors_1.default.dim(`Budget remaining: $${report.remaining.toFixed(2)}\n`));
|
|
823
|
+
if (debugLogger_1.debugLogger.isEnabled()) {
|
|
824
|
+
console.log(picocolors_1.default.yellow(`š Debug log saved to: ${debugLogger_1.debugLogger.getLogFilePath()}\n`));
|
|
825
|
+
debugLogger_1.debugLogger.disable();
|
|
826
|
+
}
|
|
827
|
+
console.log(picocolors_1.default.dim('View this session anytime with:'));
|
|
828
|
+
console.log(picocolors_1.default.dim(` gameforge session ${sessionId}\n`));
|
|
829
|
+
}
|
|
830
|
+
//# sourceMappingURL=index.js.map
|