@codebakers/cli 3.7.2 → 3.8.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.
@@ -0,0 +1,605 @@
1
+ "use strict";
2
+ /**
3
+ * ENGINEERING STATE PERSISTENCE
4
+ *
5
+ * Manages the .codebakers/ folder structure for local state persistence.
6
+ * This keeps the project state in sync between local and server.
7
+ *
8
+ * Folder structure:
9
+ * .codebakers/
10
+ * project.json - Main project configuration and scope
11
+ * state.json - Current build state (phase, progress, etc.)
12
+ * graph.json - Dependency graph
13
+ * decisions/ - Agent decision log
14
+ * 001-scoping.json
15
+ * 002-architecture.json
16
+ * artifacts/ - Generated documents
17
+ * prd.md
18
+ * tech-spec.md
19
+ * api-docs.md
20
+ * messages/ - Agent communication log
21
+ * session-xxx.json
22
+ * snapshots/ - Rollback points
23
+ * snap-001/
24
+ */
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.EngineeringStateManager = void 0;
27
+ exports.getStateManager = getStateManager;
28
+ exports.hasEngineeringProject = hasEngineeringProject;
29
+ exports.getProjectSummary = getProjectSummary;
30
+ const fs_1 = require("fs");
31
+ const path_1 = require("path");
32
+ const crypto_1 = require("crypto");
33
+ // =============================================================================
34
+ // STATE MANAGER
35
+ // =============================================================================
36
+ class EngineeringStateManager {
37
+ cwd;
38
+ stateDir;
39
+ constructor(cwd = process.cwd()) {
40
+ this.cwd = cwd;
41
+ this.stateDir = (0, path_1.join)(cwd, '.codebakers');
42
+ }
43
+ // ========================================
44
+ // INITIALIZATION
45
+ // ========================================
46
+ /**
47
+ * Initialize the .codebakers folder structure
48
+ */
49
+ init() {
50
+ // Create main directory
51
+ if (!(0, fs_1.existsSync)(this.stateDir)) {
52
+ (0, fs_1.mkdirSync)(this.stateDir, { recursive: true });
53
+ }
54
+ // Create subdirectories
55
+ const subdirs = ['decisions', 'artifacts', 'messages', 'snapshots'];
56
+ for (const subdir of subdirs) {
57
+ const path = (0, path_1.join)(this.stateDir, subdir);
58
+ if (!(0, fs_1.existsSync)(path)) {
59
+ (0, fs_1.mkdirSync)(path, { recursive: true });
60
+ }
61
+ }
62
+ }
63
+ /**
64
+ * Check if project is initialized
65
+ */
66
+ isInitialized() {
67
+ return (0, fs_1.existsSync)((0, path_1.join)(this.stateDir, 'project.json'));
68
+ }
69
+ /**
70
+ * Get project hash from current directory
71
+ */
72
+ getProjectHash() {
73
+ // Try to get git remote first
74
+ try {
75
+ const gitDir = (0, path_1.join)(this.cwd, '.git');
76
+ if ((0, fs_1.existsSync)(gitDir)) {
77
+ const configPath = (0, path_1.join)(gitDir, 'config');
78
+ if ((0, fs_1.existsSync)(configPath)) {
79
+ const config = (0, fs_1.readFileSync)(configPath, 'utf-8');
80
+ const remoteMatch = config.match(/url = (.+)/);
81
+ if (remoteMatch) {
82
+ return (0, crypto_1.createHash)('sha256').update(remoteMatch[1]).digest('hex').slice(0, 16);
83
+ }
84
+ }
85
+ }
86
+ }
87
+ catch {
88
+ // Ignore git errors
89
+ }
90
+ // Fall back to directory path hash
91
+ return (0, crypto_1.createHash)('sha256').update(this.cwd).digest('hex').slice(0, 16);
92
+ }
93
+ // ========================================
94
+ // PROJECT CONFIG
95
+ // ========================================
96
+ /**
97
+ * Create a new project configuration
98
+ */
99
+ createProject(name, description, scope = {}) {
100
+ this.init();
101
+ const defaultScope = {
102
+ targetAudience: 'consumers',
103
+ isFullBusiness: false,
104
+ needsMarketing: false,
105
+ needsAnalytics: false,
106
+ needsTeamFeatures: false,
107
+ needsAdminDashboard: false,
108
+ platforms: ['web'],
109
+ hasRealtime: false,
110
+ hasPayments: false,
111
+ hasAuth: true,
112
+ hasFileUploads: false,
113
+ compliance: {
114
+ hipaa: false,
115
+ pci: false,
116
+ gdpr: false,
117
+ soc2: false,
118
+ coppa: false,
119
+ },
120
+ expectedUsers: 'small',
121
+ launchTimeline: 'flexible',
122
+ };
123
+ const project = {
124
+ id: (0, crypto_1.createHash)('sha256').update(Date.now().toString() + Math.random()).digest('hex').slice(0, 16),
125
+ name,
126
+ description,
127
+ projectHash: this.getProjectHash(),
128
+ scope: { ...defaultScope, ...scope },
129
+ stack: this.detectStack(),
130
+ createdAt: new Date().toISOString(),
131
+ updatedAt: new Date().toISOString(),
132
+ };
133
+ this.saveProject(project);
134
+ this.initializeState();
135
+ return project;
136
+ }
137
+ /**
138
+ * Get project configuration
139
+ */
140
+ getProject() {
141
+ const path = (0, path_1.join)(this.stateDir, 'project.json');
142
+ if (!(0, fs_1.existsSync)(path))
143
+ return null;
144
+ try {
145
+ return JSON.parse((0, fs_1.readFileSync)(path, 'utf-8'));
146
+ }
147
+ catch {
148
+ return null;
149
+ }
150
+ }
151
+ /**
152
+ * Save project configuration
153
+ */
154
+ saveProject(project) {
155
+ project.updatedAt = new Date().toISOString();
156
+ (0, fs_1.writeFileSync)((0, path_1.join)(this.stateDir, 'project.json'), JSON.stringify(project, null, 2));
157
+ }
158
+ /**
159
+ * Update project scope
160
+ */
161
+ updateScope(scope) {
162
+ const project = this.getProject();
163
+ if (!project)
164
+ return null;
165
+ project.scope = { ...project.scope, ...scope };
166
+ this.saveProject(project);
167
+ return project;
168
+ }
169
+ // ========================================
170
+ // BUILD STATE
171
+ // ========================================
172
+ /**
173
+ * Initialize build state
174
+ */
175
+ initializeState() {
176
+ const initialGates = {
177
+ scoping: { status: 'pending' },
178
+ requirements: { status: 'pending' },
179
+ architecture: { status: 'pending' },
180
+ design_review: { status: 'pending' },
181
+ implementation: { status: 'pending' },
182
+ code_review: { status: 'pending' },
183
+ testing: { status: 'pending' },
184
+ security_review: { status: 'pending' },
185
+ documentation: { status: 'pending' },
186
+ staging: { status: 'pending' },
187
+ launch: { status: 'pending' },
188
+ };
189
+ const state = {
190
+ sessionId: null,
191
+ currentPhase: 'scoping',
192
+ currentAgent: 'orchestrator',
193
+ isRunning: false,
194
+ gates: initialGates,
195
+ overallProgress: 0,
196
+ lastActivity: new Date().toISOString(),
197
+ pendingApprovals: [],
198
+ blockers: [],
199
+ };
200
+ this.saveState(state);
201
+ }
202
+ /**
203
+ * Get build state
204
+ */
205
+ getState() {
206
+ const path = (0, path_1.join)(this.stateDir, 'state.json');
207
+ if (!(0, fs_1.existsSync)(path))
208
+ return null;
209
+ try {
210
+ return JSON.parse((0, fs_1.readFileSync)(path, 'utf-8'));
211
+ }
212
+ catch {
213
+ return null;
214
+ }
215
+ }
216
+ /**
217
+ * Save build state
218
+ */
219
+ saveState(state) {
220
+ state.lastActivity = new Date().toISOString();
221
+ (0, fs_1.writeFileSync)((0, path_1.join)(this.stateDir, 'state.json'), JSON.stringify(state, null, 2));
222
+ }
223
+ /**
224
+ * Update current phase
225
+ */
226
+ setPhase(phase, agent) {
227
+ const state = this.getState();
228
+ if (!state)
229
+ return;
230
+ state.currentPhase = phase;
231
+ state.currentAgent = agent;
232
+ state.gates[phase] = { status: 'in_progress' };
233
+ this.saveState(state);
234
+ }
235
+ /**
236
+ * Pass a gate
237
+ */
238
+ passGate(phase, artifacts = [], approvedBy = 'auto') {
239
+ const state = this.getState();
240
+ if (!state)
241
+ return;
242
+ state.gates[phase] = {
243
+ status: 'passed',
244
+ passedAt: new Date().toISOString(),
245
+ approvedBy,
246
+ artifacts,
247
+ };
248
+ // Calculate progress
249
+ const phases = Object.keys(state.gates);
250
+ const passed = phases.filter(p => state.gates[p].status === 'passed').length;
251
+ state.overallProgress = Math.round((passed / phases.length) * 100);
252
+ this.saveState(state);
253
+ }
254
+ /**
255
+ * Fail a gate
256
+ */
257
+ failGate(phase, reason) {
258
+ const state = this.getState();
259
+ if (!state)
260
+ return;
261
+ state.gates[phase] = {
262
+ status: 'failed',
263
+ failedReason: reason,
264
+ };
265
+ this.saveState(state);
266
+ }
267
+ // ========================================
268
+ // DEPENDENCY GRAPH
269
+ // ========================================
270
+ /**
271
+ * Get dependency graph
272
+ */
273
+ getGraph() {
274
+ const path = (0, path_1.join)(this.stateDir, 'graph.json');
275
+ if (!(0, fs_1.existsSync)(path)) {
276
+ return { nodes: [], edges: [] };
277
+ }
278
+ try {
279
+ return JSON.parse((0, fs_1.readFileSync)(path, 'utf-8'));
280
+ }
281
+ catch {
282
+ return { nodes: [], edges: [] };
283
+ }
284
+ }
285
+ /**
286
+ * Save dependency graph
287
+ */
288
+ saveGraph(graph) {
289
+ (0, fs_1.writeFileSync)((0, path_1.join)(this.stateDir, 'graph.json'), JSON.stringify(graph, null, 2));
290
+ }
291
+ /**
292
+ * Add a node to the graph
293
+ */
294
+ addNode(node) {
295
+ const graph = this.getGraph();
296
+ const newNode = {
297
+ id: (0, crypto_1.createHash)('sha256').update(node.filePath + Date.now()).digest('hex').slice(0, 12),
298
+ ...node,
299
+ createdAt: new Date().toISOString(),
300
+ modifiedAt: new Date().toISOString(),
301
+ };
302
+ // Check if node with same path already exists
303
+ const existingIndex = graph.nodes.findIndex(n => n.filePath === node.filePath);
304
+ if (existingIndex >= 0) {
305
+ graph.nodes[existingIndex] = {
306
+ ...graph.nodes[existingIndex],
307
+ ...newNode,
308
+ id: graph.nodes[existingIndex].id, // Keep original ID
309
+ createdAt: graph.nodes[existingIndex].createdAt,
310
+ };
311
+ }
312
+ else {
313
+ graph.nodes.push(newNode);
314
+ }
315
+ this.saveGraph(graph);
316
+ return newNode;
317
+ }
318
+ /**
319
+ * Add an edge to the graph
320
+ */
321
+ addEdge(edge) {
322
+ const graph = this.getGraph();
323
+ // Check if edge already exists
324
+ const exists = graph.edges.some(e => e.sourceId === edge.sourceId && e.targetId === edge.targetId && e.type === edge.type);
325
+ if (exists) {
326
+ return graph.edges.find(e => e.sourceId === edge.sourceId && e.targetId === edge.targetId && e.type === edge.type);
327
+ }
328
+ const newEdge = {
329
+ id: (0, crypto_1.createHash)('sha256').update(edge.sourceId + edge.targetId + Date.now()).digest('hex').slice(0, 12),
330
+ ...edge,
331
+ };
332
+ graph.edges.push(newEdge);
333
+ this.saveGraph(graph);
334
+ return newEdge;
335
+ }
336
+ /**
337
+ * Find nodes affected by a change
338
+ */
339
+ findAffectedNodes(nodeId) {
340
+ const graph = this.getGraph();
341
+ // Find direct dependents (nodes that import this one)
342
+ const directEdges = graph.edges.filter(e => e.targetId === nodeId);
343
+ const direct = directEdges
344
+ .map(e => graph.nodes.find(n => n.id === e.sourceId))
345
+ .filter((n) => n !== undefined);
346
+ // Find transitive dependents (BFS)
347
+ const visited = new Set([nodeId]);
348
+ const queue = [...direct.map(n => n.id)];
349
+ const transitive = [];
350
+ while (queue.length > 0) {
351
+ const currentId = queue.shift();
352
+ if (visited.has(currentId))
353
+ continue;
354
+ visited.add(currentId);
355
+ const current = graph.nodes.find(n => n.id === currentId);
356
+ if (current && !direct.includes(current)) {
357
+ transitive.push(current);
358
+ }
359
+ // Add nodes that depend on current
360
+ const dependentEdges = graph.edges.filter(e => e.targetId === currentId);
361
+ for (const edge of dependentEdges) {
362
+ if (!visited.has(edge.sourceId)) {
363
+ queue.push(edge.sourceId);
364
+ }
365
+ }
366
+ }
367
+ return { direct, transitive };
368
+ }
369
+ // ========================================
370
+ // DECISIONS
371
+ // ========================================
372
+ /**
373
+ * Record a decision
374
+ */
375
+ recordDecision(decision) {
376
+ const decisionsDir = (0, path_1.join)(this.stateDir, 'decisions');
377
+ const files = (0, fs_1.existsSync)(decisionsDir) ? (0, fs_1.readdirSync)(decisionsDir) : [];
378
+ const index = String(files.length + 1).padStart(3, '0');
379
+ const fullDecision = {
380
+ id: (0, crypto_1.createHash)('sha256').update(Date.now().toString() + Math.random()).digest('hex').slice(0, 12),
381
+ timestamp: new Date().toISOString(),
382
+ ...decision,
383
+ };
384
+ const filename = `${index}-${decision.phase}.json`;
385
+ (0, fs_1.writeFileSync)((0, path_1.join)(decisionsDir, filename), JSON.stringify(fullDecision, null, 2));
386
+ return fullDecision;
387
+ }
388
+ /**
389
+ * Get all decisions
390
+ */
391
+ getDecisions() {
392
+ const decisionsDir = (0, path_1.join)(this.stateDir, 'decisions');
393
+ if (!(0, fs_1.existsSync)(decisionsDir))
394
+ return [];
395
+ const files = (0, fs_1.readdirSync)(decisionsDir).filter(f => f.endsWith('.json'));
396
+ return files.map(f => {
397
+ try {
398
+ return JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(decisionsDir, f), 'utf-8'));
399
+ }
400
+ catch {
401
+ return null;
402
+ }
403
+ }).filter((d) => d !== null);
404
+ }
405
+ // ========================================
406
+ // ARTIFACTS
407
+ // ========================================
408
+ /**
409
+ * Save an artifact (PRD, tech spec, etc.)
410
+ */
411
+ saveArtifact(name, content) {
412
+ (0, fs_1.writeFileSync)((0, path_1.join)(this.stateDir, 'artifacts', name), content);
413
+ }
414
+ /**
415
+ * Get an artifact
416
+ */
417
+ getArtifact(name) {
418
+ const path = (0, path_1.join)(this.stateDir, 'artifacts', name);
419
+ if (!(0, fs_1.existsSync)(path))
420
+ return null;
421
+ try {
422
+ return (0, fs_1.readFileSync)(path, 'utf-8');
423
+ }
424
+ catch {
425
+ return null;
426
+ }
427
+ }
428
+ /**
429
+ * List all artifacts
430
+ */
431
+ listArtifacts() {
432
+ const artifactsDir = (0, path_1.join)(this.stateDir, 'artifacts');
433
+ if (!(0, fs_1.existsSync)(artifactsDir))
434
+ return [];
435
+ return (0, fs_1.readdirSync)(artifactsDir);
436
+ }
437
+ // ========================================
438
+ // MESSAGES
439
+ // ========================================
440
+ /**
441
+ * Record a message
442
+ */
443
+ recordMessage(message) {
444
+ const state = this.getState();
445
+ const sessionId = state?.sessionId || 'default';
446
+ const messagesPath = (0, path_1.join)(this.stateDir, 'messages', `${sessionId}.json`);
447
+ let messages = [];
448
+ if ((0, fs_1.existsSync)(messagesPath)) {
449
+ try {
450
+ messages = JSON.parse((0, fs_1.readFileSync)(messagesPath, 'utf-8'));
451
+ }
452
+ catch {
453
+ messages = [];
454
+ }
455
+ }
456
+ const fullMessage = {
457
+ id: (0, crypto_1.createHash)('sha256').update(Date.now().toString() + Math.random()).digest('hex').slice(0, 12),
458
+ timestamp: new Date().toISOString(),
459
+ ...message,
460
+ };
461
+ messages.push(fullMessage);
462
+ (0, fs_1.writeFileSync)(messagesPath, JSON.stringify(messages, null, 2));
463
+ return fullMessage;
464
+ }
465
+ /**
466
+ * Get messages for current session
467
+ */
468
+ getMessages() {
469
+ const state = this.getState();
470
+ const sessionId = state?.sessionId || 'default';
471
+ const messagesPath = (0, path_1.join)(this.stateDir, 'messages', `${sessionId}.json`);
472
+ if (!(0, fs_1.existsSync)(messagesPath))
473
+ return [];
474
+ try {
475
+ return JSON.parse((0, fs_1.readFileSync)(messagesPath, 'utf-8'));
476
+ }
477
+ catch {
478
+ return [];
479
+ }
480
+ }
481
+ // ========================================
482
+ // STACK DETECTION
483
+ // ========================================
484
+ /**
485
+ * Detect the tech stack from package.json
486
+ */
487
+ detectStack() {
488
+ const stack = {
489
+ framework: 'nextjs',
490
+ database: 'supabase',
491
+ orm: 'drizzle',
492
+ auth: 'supabase',
493
+ ui: 'shadcn',
494
+ };
495
+ const packageJsonPath = (0, path_1.join)(this.cwd, 'package.json');
496
+ if (!(0, fs_1.existsSync)(packageJsonPath))
497
+ return stack;
498
+ try {
499
+ const pkg = JSON.parse((0, fs_1.readFileSync)(packageJsonPath, 'utf-8'));
500
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
501
+ // Framework detection
502
+ if (deps['next'])
503
+ stack.framework = 'nextjs';
504
+ else if (deps['@remix-run/react'])
505
+ stack.framework = 'remix';
506
+ else if (deps['react'])
507
+ stack.framework = 'react';
508
+ else if (deps['vue'])
509
+ stack.framework = 'vue';
510
+ else if (deps['svelte'])
511
+ stack.framework = 'svelte';
512
+ // ORM detection
513
+ if (deps['drizzle-orm'])
514
+ stack.orm = 'drizzle';
515
+ else if (deps['prisma'])
516
+ stack.orm = 'prisma';
517
+ else if (deps['typeorm'])
518
+ stack.orm = 'typeorm';
519
+ else if (deps['mongoose'])
520
+ stack.orm = 'mongoose';
521
+ // Database detection
522
+ if (deps['@supabase/supabase-js'])
523
+ stack.database = 'supabase';
524
+ else if (deps['@planetscale/database'])
525
+ stack.database = 'planetscale';
526
+ else if (deps['firebase'])
527
+ stack.database = 'firebase';
528
+ else if (deps['pg'])
529
+ stack.database = 'postgres';
530
+ else if (deps['mysql2'])
531
+ stack.database = 'mysql';
532
+ else if (deps['mongodb'])
533
+ stack.database = 'mongodb';
534
+ // Auth detection
535
+ if (deps['@supabase/auth-helpers-nextjs'] || deps['@supabase/supabase-js'])
536
+ stack.auth = 'supabase';
537
+ else if (deps['@clerk/nextjs'])
538
+ stack.auth = 'clerk';
539
+ else if (deps['next-auth'])
540
+ stack.auth = 'next-auth';
541
+ else if (deps['@auth/core'])
542
+ stack.auth = 'authjs';
543
+ else if (deps['firebase'])
544
+ stack.auth = 'firebase';
545
+ // UI detection
546
+ if (deps['@radix-ui/react-slot'] || (0, fs_1.existsSync)((0, path_1.join)(this.cwd, 'components', 'ui')))
547
+ stack.ui = 'shadcn';
548
+ else if (deps['@chakra-ui/react'])
549
+ stack.ui = 'chakra';
550
+ else if (deps['@mui/material'])
551
+ stack.ui = 'mui';
552
+ else if (deps['antd'])
553
+ stack.ui = 'antd';
554
+ // Payments detection
555
+ if (deps['stripe'])
556
+ stack.payments = 'stripe';
557
+ else if (deps['@paypal/react-paypal-js'])
558
+ stack.payments = 'paypal';
559
+ else if (deps['square'])
560
+ stack.payments = 'square';
561
+ }
562
+ catch {
563
+ // Return default stack on error
564
+ }
565
+ return stack;
566
+ }
567
+ // ========================================
568
+ // SUMMARY
569
+ // ========================================
570
+ /**
571
+ * Get a summary of current engineering state
572
+ */
573
+ getSummary() {
574
+ const graph = this.getGraph();
575
+ return {
576
+ project: this.getProject(),
577
+ state: this.getState(),
578
+ graphStats: { nodes: graph.nodes.length, edges: graph.edges.length },
579
+ decisions: this.getDecisions().length,
580
+ artifacts: this.listArtifacts(),
581
+ };
582
+ }
583
+ }
584
+ exports.EngineeringStateManager = EngineeringStateManager;
585
+ // =============================================================================
586
+ // CONVENIENCE FUNCTIONS
587
+ // =============================================================================
588
+ /**
589
+ * Get the state manager for current directory
590
+ */
591
+ function getStateManager(cwd) {
592
+ return new EngineeringStateManager(cwd);
593
+ }
594
+ /**
595
+ * Check if engineering project exists in current directory
596
+ */
597
+ function hasEngineeringProject(cwd) {
598
+ return getStateManager(cwd).isInitialized();
599
+ }
600
+ /**
601
+ * Quick summary of current project state
602
+ */
603
+ function getProjectSummary(cwd) {
604
+ return getStateManager(cwd).getSummary();
605
+ }