Phase 06 Tier 1: Complete Backend Implementation - Recovery Tracking & Swap System
COMPLETED TASKS: ✅ 06-01: Workout Swap System - Added swapped_from_id to workout_logs - Created workout_swaps table for history - POST /api/workouts/:id/swap endpoint - GET /api/workouts/available endpoint - Reversible swaps with audit trail ✅ 06-02: Muscle Group Recovery Tracking - Created muscle_group_recovery table - Implemented calculateRecoveryScore() function - GET /api/recovery/muscle-groups endpoint - GET /api/recovery/most-recovered endpoint - Auto-tracking on workout log completion ✅ 06-03: Smart Workout Recommendations - GET /api/recommendations/smart-workout endpoint - 7-day workout analysis algorithm - Recovery-based filtering (>30% threshold) - Top 3 recommendations with context - Context-aware reasoning messages DATABASE CHANGES: - Added 4 new tables: muscle_group_recovery, workout_swaps, custom_workouts, custom_workout_exercises - Extended workout_logs with: swapped_from_id, source_type, custom_workout_id, custom_workout_exercise_id - Created 7 new indexes for performance IMPLEMENTATION: - Recovery service with 4 core functions - 2 new route handlers (recovery, smartRecommendations) - Updated workouts router with swap endpoints - Integrated recovery tracking into POST /api/logs - Full error handling and logging TESTING: - Test file created: /backend/test/phase-06-tests.js - Ready for E2E and staging validation STATUS: Ready for frontend integration and production review Branch: feature/06-phase-06
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Claude Flow Session Manager
|
||||
* Handles session lifecycle: start, restore, end
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const SESSION_DIR = path.join(process.cwd(), '.claude-flow', 'sessions');
|
||||
const SESSION_FILE = path.join(SESSION_DIR, 'current.json');
|
||||
|
||||
const commands = {
|
||||
start: () => {
|
||||
const sessionId = `session-${Date.now()}`;
|
||||
const session = {
|
||||
id: sessionId,
|
||||
startedAt: new Date().toISOString(),
|
||||
cwd: process.cwd(),
|
||||
context: {},
|
||||
metrics: {
|
||||
edits: 0,
|
||||
commands: 0,
|
||||
tasks: 0,
|
||||
errors: 0,
|
||||
},
|
||||
};
|
||||
|
||||
fs.mkdirSync(SESSION_DIR, { recursive: true });
|
||||
fs.writeFileSync(SESSION_FILE, JSON.stringify(session, null, 2));
|
||||
|
||||
console.log(`Session started: ${sessionId}`);
|
||||
return session;
|
||||
},
|
||||
|
||||
restore: () => {
|
||||
if (!fs.existsSync(SESSION_FILE)) {
|
||||
console.log('No session to restore');
|
||||
return null;
|
||||
}
|
||||
|
||||
const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
|
||||
session.restoredAt = new Date().toISOString();
|
||||
fs.writeFileSync(SESSION_FILE, JSON.stringify(session, null, 2));
|
||||
|
||||
console.log(`Session restored: ${session.id}`);
|
||||
return session;
|
||||
},
|
||||
|
||||
end: () => {
|
||||
if (!fs.existsSync(SESSION_FILE)) {
|
||||
console.log('No active session');
|
||||
return null;
|
||||
}
|
||||
|
||||
const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
|
||||
session.endedAt = new Date().toISOString();
|
||||
session.duration = Date.now() - new Date(session.startedAt).getTime();
|
||||
|
||||
// Archive session
|
||||
const archivePath = path.join(SESSION_DIR, `${session.id}.json`);
|
||||
fs.writeFileSync(archivePath, JSON.stringify(session, null, 2));
|
||||
fs.unlinkSync(SESSION_FILE);
|
||||
|
||||
console.log(`Session ended: ${session.id}`);
|
||||
console.log(`Duration: ${Math.round(session.duration / 1000 / 60)} minutes`);
|
||||
console.log(`Metrics: ${JSON.stringify(session.metrics)}`);
|
||||
|
||||
return session;
|
||||
},
|
||||
|
||||
status: () => {
|
||||
if (!fs.existsSync(SESSION_FILE)) {
|
||||
console.log('No active session');
|
||||
return null;
|
||||
}
|
||||
|
||||
const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
|
||||
const duration = Date.now() - new Date(session.startedAt).getTime();
|
||||
|
||||
console.log(`Session: ${session.id}`);
|
||||
console.log(`Started: ${session.startedAt}`);
|
||||
console.log(`Duration: ${Math.round(duration / 1000 / 60)} minutes`);
|
||||
console.log(`Metrics: ${JSON.stringify(session.metrics)}`);
|
||||
|
||||
return session;
|
||||
},
|
||||
|
||||
update: (key, value) => {
|
||||
if (!fs.existsSync(SESSION_FILE)) {
|
||||
console.log('No active session');
|
||||
return null;
|
||||
}
|
||||
|
||||
const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
|
||||
session.context[key] = value;
|
||||
session.updatedAt = new Date().toISOString();
|
||||
fs.writeFileSync(SESSION_FILE, JSON.stringify(session, null, 2));
|
||||
|
||||
return session;
|
||||
},
|
||||
|
||||
get: (key) => {
|
||||
if (!fs.existsSync(SESSION_FILE)) return null;
|
||||
try {
|
||||
const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
|
||||
return key ? (session.context || {})[key] : session.context;
|
||||
} catch { return null; }
|
||||
},
|
||||
|
||||
metric: (name) => {
|
||||
if (!fs.existsSync(SESSION_FILE)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
|
||||
if (session.metrics[name] !== undefined) {
|
||||
session.metrics[name]++;
|
||||
fs.writeFileSync(SESSION_FILE, JSON.stringify(session, null, 2));
|
||||
}
|
||||
|
||||
return session;
|
||||
},
|
||||
};
|
||||
|
||||
// CLI
|
||||
const [,, command, ...args] = process.argv;
|
||||
|
||||
if (command && commands[command]) {
|
||||
commands[command](...args);
|
||||
} else {
|
||||
console.log('Usage: session.js <start|restore|end|status|update|metric> [args]');
|
||||
}
|
||||
|
||||
module.exports = commands;
|
||||
Reference in New Issue
Block a user