@appiq/flutter-workflow 1.4.4 → 2.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/CHANGELOG.md +160 -0
- package/README.md +69 -9
- package/agents/claude/cubit-agent.md +163 -2
- package/agents/claude/data-agent.md +162 -2
- package/agents/claude/domain-agent.md +163 -2
- package/agents/claude/feature-manager.md +359 -21
- package/agents/claude/security-agent.md +162 -2
- package/agents/claude/test-agent.md +164 -2
- package/agents/claude/ui-agent.md +158 -8
- package/config/agent-coordination.json +335 -0
- package/config/independent-mode-template.md +202 -0
- package/lib/independent-agent-tracker.js +565 -0
- package/lib/setup-independent-mode.js +562 -0
- package/lib/state-manager.js +526 -0
- package/package.json +4 -2
- package/templates/enhanced-task-breakdown-template.md +415 -0
- package/templates/enhanced-task-history-template.md +605 -0
- package/templates/feature-template.md +116 -30
- package/templates/additional_cubit_req.md +0 -357
- package/templates/additional_data_req.md +0 -480
- package/templates/additional_domain_req.md +0 -431
- package/templates/additional_ui_req.md +0 -205
- package/templates/feature-history-template.md +0 -280
- package/templates/platform-adaptive-widget-template.dart +0 -407
- package/templates/pretty-ui-examples.md +0 -597
- package/templates/task-breakdown-template.md +0 -265
- package/templates/task-history-template.md +0 -276
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppIQ Flutter Workflow - State Management System
|
|
3
|
+
* Provides crash recovery, state preservation, and coordination utilities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs').promises;
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
class FeatureStateManager {
|
|
10
|
+
constructor(basePath = 'docs/features') {
|
|
11
|
+
this.basePath = basePath;
|
|
12
|
+
this.stateCache = new Map();
|
|
13
|
+
this.backupInterval = 300000; // 5 minutes
|
|
14
|
+
this.maxBackups = 10;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Initialize state management for a feature
|
|
19
|
+
*/
|
|
20
|
+
async initializeFeatureState(featureName, initialData = {}) {
|
|
21
|
+
const stateData = {
|
|
22
|
+
featureName,
|
|
23
|
+
status: 'initialized',
|
|
24
|
+
currentPhase: 'planning',
|
|
25
|
+
currentAgent: 'feature-manager',
|
|
26
|
+
createdDate: new Date().toISOString(),
|
|
27
|
+
lastUpdated: new Date().toISOString(),
|
|
28
|
+
version: '1.0.0',
|
|
29
|
+
agents: {
|
|
30
|
+
'po-agent': { status: 'pending', progress: 0, tasks: [], lastActivity: null },
|
|
31
|
+
'ui-agent': { status: 'pending', progress: 0, tasks: [], lastActivity: null },
|
|
32
|
+
'cubit-agent': { status: 'pending', progress: 0, tasks: [], lastActivity: null },
|
|
33
|
+
'domain-agent': { status: 'pending', progress: 0, tasks: [], lastActivity: null },
|
|
34
|
+
'data-agent': { status: 'pending', progress: 0, tasks: [], lastActivity: null },
|
|
35
|
+
'security-agent': { status: 'pending', progress: 0, tasks: [], lastActivity: null },
|
|
36
|
+
'test-agent': { status: 'pending', progress: 0, tasks: [], lastActivity: null },
|
|
37
|
+
'integration-validator': { status: 'pending', progress: 0, tasks: [], lastActivity: null }
|
|
38
|
+
},
|
|
39
|
+
qualityGates: {
|
|
40
|
+
requirements: false,
|
|
41
|
+
ui: false,
|
|
42
|
+
state: false,
|
|
43
|
+
domain: false,
|
|
44
|
+
data: false,
|
|
45
|
+
security: false,
|
|
46
|
+
testing: false,
|
|
47
|
+
integration: false
|
|
48
|
+
},
|
|
49
|
+
parallelExecution: {
|
|
50
|
+
active: false,
|
|
51
|
+
groups: [],
|
|
52
|
+
coordination: {}
|
|
53
|
+
},
|
|
54
|
+
blockers: [],
|
|
55
|
+
deploymentReadiness: false,
|
|
56
|
+
crashRecovery: {
|
|
57
|
+
enabled: true,
|
|
58
|
+
lastBackup: new Date().toISOString(),
|
|
59
|
+
recoveryPoints: []
|
|
60
|
+
},
|
|
61
|
+
performance: {
|
|
62
|
+
startTime: new Date().toISOString(),
|
|
63
|
+
agentDurations: {},
|
|
64
|
+
totalDuration: null,
|
|
65
|
+
efficiency: {}
|
|
66
|
+
},
|
|
67
|
+
...initialData
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
await this.saveFeatureState(featureName, stateData);
|
|
71
|
+
this.stateCache.set(featureName, stateData);
|
|
72
|
+
|
|
73
|
+
// Start automatic backup
|
|
74
|
+
this.scheduleBackup(featureName);
|
|
75
|
+
|
|
76
|
+
return stateData;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Load feature state from file
|
|
81
|
+
*/
|
|
82
|
+
async loadFeatureState(featureName) {
|
|
83
|
+
try {
|
|
84
|
+
// Check cache first
|
|
85
|
+
if (this.stateCache.has(featureName)) {
|
|
86
|
+
return this.stateCache.get(featureName);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const stateFile = path.join(this.basePath, `${featureName}_state.json`);
|
|
90
|
+
const stateData = JSON.parse(await fs.readFile(stateFile, 'utf8'));
|
|
91
|
+
|
|
92
|
+
// Validate state integrity
|
|
93
|
+
this.validateStateIntegrity(stateData);
|
|
94
|
+
|
|
95
|
+
this.stateCache.set(featureName, stateData);
|
|
96
|
+
return stateData;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.warn(`Could not load state for ${featureName}:`, error.message);
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Save feature state to file with backup
|
|
105
|
+
*/
|
|
106
|
+
async saveFeatureState(featureName, stateData) {
|
|
107
|
+
try {
|
|
108
|
+
stateData.lastUpdated = new Date().toISOString();
|
|
109
|
+
stateData.version = this.incrementVersion(stateData.version || '1.0.0');
|
|
110
|
+
|
|
111
|
+
const stateFile = path.join(this.basePath, `${featureName}_state.json`);
|
|
112
|
+
|
|
113
|
+
// Create backup of current state
|
|
114
|
+
await this.createBackup(featureName, stateData);
|
|
115
|
+
|
|
116
|
+
// Save new state
|
|
117
|
+
await fs.writeFile(stateFile, JSON.stringify(stateData, null, 2));
|
|
118
|
+
|
|
119
|
+
// Update cache
|
|
120
|
+
this.stateCache.set(featureName, stateData);
|
|
121
|
+
|
|
122
|
+
console.log(`✅ State saved for ${featureName} - Version ${stateData.version}`);
|
|
123
|
+
return true;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error(`❌ Failed to save state for ${featureName}:`, error.message);
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Update agent status and progress
|
|
132
|
+
*/
|
|
133
|
+
async updateAgentProgress(featureName, agentName, updates) {
|
|
134
|
+
const state = await this.loadFeatureState(featureName);
|
|
135
|
+
if (!state) {
|
|
136
|
+
throw new Error(`Feature state not found: ${featureName}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!state.agents[agentName]) {
|
|
140
|
+
throw new Error(`Agent not found: ${agentName}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Update agent data
|
|
144
|
+
Object.assign(state.agents[agentName], {
|
|
145
|
+
...updates,
|
|
146
|
+
lastActivity: new Date().toISOString()
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Update overall progress
|
|
150
|
+
const totalAgents = Object.keys(state.agents).length;
|
|
151
|
+
const completedAgents = Object.values(state.agents).filter(agent => agent.status === 'completed').length;
|
|
152
|
+
state.overallProgress = Math.round((completedAgents / totalAgents) * 100);
|
|
153
|
+
|
|
154
|
+
// Update current agent if status changed
|
|
155
|
+
if (updates.status === 'active') {
|
|
156
|
+
state.currentAgent = agentName;
|
|
157
|
+
} else if (updates.status === 'completed') {
|
|
158
|
+
state.currentAgent = this.getNextAgent(state, agentName);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
await this.saveFeatureState(featureName, state);
|
|
162
|
+
return state;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Create recovery checkpoint
|
|
167
|
+
*/
|
|
168
|
+
async createRecoveryCheckpoint(featureName, description = 'Manual checkpoint') {
|
|
169
|
+
const state = await this.loadFeatureState(featureName);
|
|
170
|
+
if (!state) {
|
|
171
|
+
throw new Error(`Feature state not found: ${featureName}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const checkpoint = {
|
|
175
|
+
id: `checkpoint_${Date.now()}`,
|
|
176
|
+
timestamp: new Date().toISOString(),
|
|
177
|
+
description,
|
|
178
|
+
state: JSON.parse(JSON.stringify(state)), // Deep copy
|
|
179
|
+
gitCommit: await this.getCurrentGitCommit(),
|
|
180
|
+
fileSystem: await this.captureFileSystemState(featureName)
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
state.crashRecovery.recoveryPoints.push(checkpoint);
|
|
184
|
+
|
|
185
|
+
// Keep only the latest 10 checkpoints
|
|
186
|
+
if (state.crashRecovery.recoveryPoints.length > this.maxBackups) {
|
|
187
|
+
state.crashRecovery.recoveryPoints = state.crashRecovery.recoveryPoints.slice(-this.maxBackups);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
await this.saveFeatureState(featureName, state);
|
|
191
|
+
console.log(`📋 Recovery checkpoint created: ${checkpoint.id}`);
|
|
192
|
+
return checkpoint;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Restore from recovery checkpoint
|
|
197
|
+
*/
|
|
198
|
+
async restoreFromCheckpoint(featureName, checkpointId = null) {
|
|
199
|
+
const state = await this.loadFeatureState(featureName);
|
|
200
|
+
if (!state) {
|
|
201
|
+
throw new Error(`Feature state not found: ${featureName}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const checkpoint = checkpointId
|
|
205
|
+
? state.crashRecovery.recoveryPoints.find(cp => cp.id === checkpointId)
|
|
206
|
+
: state.crashRecovery.recoveryPoints[state.crashRecovery.recoveryPoints.length - 1];
|
|
207
|
+
|
|
208
|
+
if (!checkpoint) {
|
|
209
|
+
throw new Error(`Recovery checkpoint not found: ${checkpointId || 'latest'}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Restore state
|
|
213
|
+
const restoredState = checkpoint.state;
|
|
214
|
+
restoredState.lastUpdated = new Date().toISOString();
|
|
215
|
+
restoredState.status = 'recovered';
|
|
216
|
+
|
|
217
|
+
await this.saveFeatureState(featureName, restoredState);
|
|
218
|
+
|
|
219
|
+
console.log(`🔄 Restored from checkpoint: ${checkpoint.id} (${checkpoint.description})`);
|
|
220
|
+
return restoredState;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Handle parallel agent coordination
|
|
225
|
+
*/
|
|
226
|
+
async startParallelExecution(featureName, groupName, agents) {
|
|
227
|
+
const state = await this.loadFeatureState(featureName);
|
|
228
|
+
if (!state) {
|
|
229
|
+
throw new Error(`Feature state not found: ${featureName}`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
state.parallelExecution.active = true;
|
|
233
|
+
state.parallelExecution.groups.push({
|
|
234
|
+
name: groupName,
|
|
235
|
+
agents,
|
|
236
|
+
startTime: new Date().toISOString(),
|
|
237
|
+
status: 'active',
|
|
238
|
+
coordination: {
|
|
239
|
+
sharedContext: {},
|
|
240
|
+
conflictResolution: 'priority-based',
|
|
241
|
+
progressTracking: {}
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Update agent statuses
|
|
246
|
+
agents.forEach(agentName => {
|
|
247
|
+
if (state.agents[agentName]) {
|
|
248
|
+
state.agents[agentName].status = 'active';
|
|
249
|
+
state.agents[agentName].parallelGroup = groupName;
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
await this.saveFeatureState(featureName, state);
|
|
254
|
+
console.log(`⚡ Started parallel execution: ${groupName} with agents [${agents.join(', ')}]`);
|
|
255
|
+
return state;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Complete parallel execution group
|
|
260
|
+
*/
|
|
261
|
+
async completeParallelExecution(featureName, groupName) {
|
|
262
|
+
const state = await this.loadFeatureState(featureName);
|
|
263
|
+
if (!state) {
|
|
264
|
+
throw new Error(`Feature state not found: ${featureName}`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const group = state.parallelExecution.groups.find(g => g.name === groupName);
|
|
268
|
+
if (!group) {
|
|
269
|
+
throw new Error(`Parallel group not found: ${groupName}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
group.status = 'completed';
|
|
273
|
+
group.endTime = new Date().toISOString();
|
|
274
|
+
group.duration = new Date(group.endTime) - new Date(group.startTime);
|
|
275
|
+
|
|
276
|
+
// Check if all parallel groups are completed
|
|
277
|
+
const activeGroups = state.parallelExecution.groups.filter(g => g.status === 'active');
|
|
278
|
+
if (activeGroups.length === 0) {
|
|
279
|
+
state.parallelExecution.active = false;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
await this.saveFeatureState(featureName, state);
|
|
283
|
+
console.log(`✅ Completed parallel execution: ${groupName}`);
|
|
284
|
+
return state;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Validate quality gate
|
|
289
|
+
*/
|
|
290
|
+
async validateQualityGate(featureName, gateName, criteria = {}) {
|
|
291
|
+
const state = await this.loadFeatureState(featureName);
|
|
292
|
+
if (!state) {
|
|
293
|
+
throw new Error(`Feature state not found: ${featureName}`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const validation = {
|
|
297
|
+
gate: gateName,
|
|
298
|
+
timestamp: new Date().toISOString(),
|
|
299
|
+
criteria,
|
|
300
|
+
result: 'pending',
|
|
301
|
+
issues: []
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// Perform validation logic here
|
|
305
|
+
// This would typically call external validation systems
|
|
306
|
+
const passed = await this.performQualityValidation(gateName, criteria);
|
|
307
|
+
|
|
308
|
+
validation.result = passed ? 'passed' : 'failed';
|
|
309
|
+
state.qualityGates[gateName] = passed;
|
|
310
|
+
|
|
311
|
+
if (!passed) {
|
|
312
|
+
state.blockers.push({
|
|
313
|
+
type: 'quality-gate-failure',
|
|
314
|
+
gate: gateName,
|
|
315
|
+
timestamp: validation.timestamp,
|
|
316
|
+
description: `Quality gate ${gateName} failed validation`,
|
|
317
|
+
resolution: 'pending'
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
await this.saveFeatureState(featureName, state);
|
|
322
|
+
console.log(`🚦 Quality gate ${gateName}: ${validation.result.toUpperCase()}`);
|
|
323
|
+
return validation;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Health check for feature state
|
|
328
|
+
*/
|
|
329
|
+
async performHealthCheck(featureName = null) {
|
|
330
|
+
const healthReport = {
|
|
331
|
+
timestamp: new Date().toISOString(),
|
|
332
|
+
overall: 'healthy',
|
|
333
|
+
features: {},
|
|
334
|
+
system: {
|
|
335
|
+
stateManagerVersion: '1.0.0',
|
|
336
|
+
cacheSize: this.stateCache.size,
|
|
337
|
+
backupSystem: 'operational'
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
if (featureName) {
|
|
343
|
+
// Check specific feature
|
|
344
|
+
const state = await this.loadFeatureState(featureName);
|
|
345
|
+
healthReport.features[featureName] = this.analyzeFeatureHealth(state);
|
|
346
|
+
} else {
|
|
347
|
+
// Check all features
|
|
348
|
+
const featureFiles = await fs.readdir(this.basePath);
|
|
349
|
+
const stateFiles = featureFiles.filter(file => file.endsWith('_state.json'));
|
|
350
|
+
|
|
351
|
+
for (const stateFile of stateFiles) {
|
|
352
|
+
const fName = stateFile.replace('_state.json', '');
|
|
353
|
+
const state = await this.loadFeatureState(fName);
|
|
354
|
+
healthReport.features[fName] = this.analyzeFeatureHealth(state);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Determine overall health
|
|
359
|
+
const featureHealths = Object.values(healthReport.features);
|
|
360
|
+
const unhealthyFeatures = featureHealths.filter(health => health.status !== 'healthy');
|
|
361
|
+
|
|
362
|
+
if (unhealthyFeatures.length > 0) {
|
|
363
|
+
healthReport.overall = unhealthyFeatures.length > featureHealths.length / 2 ? 'critical' : 'warning';
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
} catch (error) {
|
|
367
|
+
healthReport.overall = 'error';
|
|
368
|
+
healthReport.error = error.message;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return healthReport;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Helper methods
|
|
375
|
+
|
|
376
|
+
validateStateIntegrity(state) {
|
|
377
|
+
const required = ['featureName', 'status', 'agents', 'qualityGates'];
|
|
378
|
+
for (const field of required) {
|
|
379
|
+
if (!state[field]) {
|
|
380
|
+
throw new Error(`Invalid state: missing ${field}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
incrementVersion(version) {
|
|
386
|
+
const [major, minor, patch] = version.split('.').map(Number);
|
|
387
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async createBackup(featureName, stateData) {
|
|
391
|
+
try {
|
|
392
|
+
const backupFile = path.join(this.basePath, `${featureName}_state_backup_${Date.now()}.json`);
|
|
393
|
+
await fs.writeFile(backupFile, JSON.stringify(stateData, null, 2));
|
|
394
|
+
|
|
395
|
+
// Clean old backups
|
|
396
|
+
await this.cleanOldBackups(featureName);
|
|
397
|
+
} catch (error) {
|
|
398
|
+
console.warn(`Backup creation failed for ${featureName}:`, error.message);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async cleanOldBackups(featureName) {
|
|
403
|
+
try {
|
|
404
|
+
const files = await fs.readdir(this.basePath);
|
|
405
|
+
const backupFiles = files
|
|
406
|
+
.filter(file => file.startsWith(`${featureName}_state_backup_`))
|
|
407
|
+
.map(file => ({
|
|
408
|
+
name: file,
|
|
409
|
+
path: path.join(this.basePath, file),
|
|
410
|
+
time: parseInt(file.split('_').pop().replace('.json', ''))
|
|
411
|
+
}))
|
|
412
|
+
.sort((a, b) => b.time - a.time);
|
|
413
|
+
|
|
414
|
+
// Keep only the latest backups
|
|
415
|
+
const filesToDelete = backupFiles.slice(this.maxBackups);
|
|
416
|
+
for (const file of filesToDelete) {
|
|
417
|
+
await fs.unlink(file.path);
|
|
418
|
+
}
|
|
419
|
+
} catch (error) {
|
|
420
|
+
console.warn(`Backup cleanup failed for ${featureName}:`, error.message);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
getNextAgent(state, currentAgent) {
|
|
425
|
+
const agentOrder = [
|
|
426
|
+
'po-agent', 'ui-agent', 'domain-agent', 'cubit-agent',
|
|
427
|
+
'data-agent', 'security-agent', 'test-agent', 'integration-validator'
|
|
428
|
+
];
|
|
429
|
+
|
|
430
|
+
const currentIndex = agentOrder.indexOf(currentAgent);
|
|
431
|
+
if (currentIndex === -1 || currentIndex === agentOrder.length - 1) {
|
|
432
|
+
return 'feature-manager'; // Last agent or unknown
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return agentOrder[currentIndex + 1];
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async getCurrentGitCommit() {
|
|
439
|
+
try {
|
|
440
|
+
const { exec } = require('child_process');
|
|
441
|
+
return new Promise((resolve) => {
|
|
442
|
+
exec('git rev-parse HEAD', (error, stdout) => {
|
|
443
|
+
resolve(error ? 'unknown' : stdout.trim());
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
} catch {
|
|
447
|
+
return 'unknown';
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async captureFileSystemState(featureName) {
|
|
452
|
+
try {
|
|
453
|
+
const docsPath = path.join('docs', 'features');
|
|
454
|
+
const tasksPath = path.join('docs', 'tasks');
|
|
455
|
+
|
|
456
|
+
return {
|
|
457
|
+
featureFile: `${docsPath}/${featureName}.md`,
|
|
458
|
+
stateFile: `${docsPath}/${featureName}_state.json`,
|
|
459
|
+
tasksFile: `${tasksPath}/${featureName}_tasks.md`,
|
|
460
|
+
historyFile: `${tasksPath}/${featureName}_history.md`,
|
|
461
|
+
timestamp: new Date().toISOString()
|
|
462
|
+
};
|
|
463
|
+
} catch {
|
|
464
|
+
return {};
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async performQualityValidation(gateName, criteria) {
|
|
469
|
+
// This would typically integrate with actual validation systems
|
|
470
|
+
// For now, return true as a placeholder
|
|
471
|
+
console.log(`🔍 Validating quality gate: ${gateName}`);
|
|
472
|
+
return true;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
analyzeFeatureHealth(state) {
|
|
476
|
+
if (!state) {
|
|
477
|
+
return { status: 'error', message: 'State not found' };
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const issues = [];
|
|
481
|
+
|
|
482
|
+
// Check for blockers
|
|
483
|
+
if (state.blockers && state.blockers.length > 0) {
|
|
484
|
+
issues.push(`${state.blockers.length} active blockers`);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Check agent progress
|
|
488
|
+
const stuckAgents = Object.entries(state.agents)
|
|
489
|
+
.filter(([name, agent]) => {
|
|
490
|
+
const lastActivity = agent.lastActivity ? new Date(agent.lastActivity) : null;
|
|
491
|
+
const hoursSinceActivity = lastActivity ? (Date.now() - lastActivity.getTime()) / (1000 * 60 * 60) : Infinity;
|
|
492
|
+
return agent.status === 'active' && hoursSinceActivity > 24;
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
if (stuckAgents.length > 0) {
|
|
496
|
+
issues.push(`${stuckAgents.length} agents inactive for >24h`);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Check quality gates
|
|
500
|
+
const failedGates = Object.entries(state.qualityGates)
|
|
501
|
+
.filter(([gate, passed]) => !passed).length;
|
|
502
|
+
|
|
503
|
+
if (failedGates > 0) {
|
|
504
|
+
issues.push(`${failedGates} quality gates not passed`);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return {
|
|
508
|
+
status: issues.length === 0 ? 'healthy' : issues.length < 3 ? 'warning' : 'critical',
|
|
509
|
+
issues,
|
|
510
|
+
lastUpdate: state.lastUpdated,
|
|
511
|
+
progress: state.overallProgress || 0
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
scheduleBackup(featureName) {
|
|
516
|
+
setInterval(async () => {
|
|
517
|
+
try {
|
|
518
|
+
await this.createRecoveryCheckpoint(featureName, 'Automatic backup');
|
|
519
|
+
} catch (error) {
|
|
520
|
+
console.warn(`Automatic backup failed for ${featureName}:`, error.message);
|
|
521
|
+
}
|
|
522
|
+
}, this.backupInterval);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
module.exports = { FeatureStateManager };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appiq/flutter-workflow",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "🚀
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "🚀 Professional Flutter development with AI-powered agent coordination & intelligent parallel execution - Complete workflow system with crash recovery and state management by AppIQ Solutions",
|
|
5
5
|
"main": "bin/cli.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"appiq-workflow": "bin/cli.js"
|
|
@@ -56,6 +56,8 @@
|
|
|
56
56
|
"bin/",
|
|
57
57
|
"agents/",
|
|
58
58
|
"templates/",
|
|
59
|
+
"config/",
|
|
60
|
+
"lib/",
|
|
59
61
|
"README.md",
|
|
60
62
|
"LICENSE",
|
|
61
63
|
"CHANGELOG.md",
|