@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.
- package/dist/lib/engineering-state.d.ts +269 -0
- package/dist/lib/engineering-state.js +605 -0
- package/dist/mcp/engineering-tools.d.ts +396 -0
- package/dist/mcp/engineering-tools.js +808 -0
- package/dist/mcp/server.js +15 -0
- package/package.json +1 -1
- package/src/lib/engineering-state.ts +823 -0
- package/src/mcp/engineering-tools.ts +977 -0
- package/src/mcp/server.ts +16 -0
|
@@ -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
|
+
}
|