@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
157 lines (156 loc) ⢠6.09 kB
JavaScript
export class TDDEnforcer {
sessions = new Map();
startTDDSession(feature) {
if (feature.testCases.length === 0) {
throw new Error('š« TDD Violation: Cannot start development without tests!\n' +
'š Please write at least one failing test before implementation.\n' +
'Use the "write_test" tool to create your first test.');
}
const failingTests = feature.testCases.filter(tc => tc.status !== 'passing');
if (failingTests.length === 0) {
throw new Error('š« TDD Violation: All tests are already passing!\n' +
'š“ TDD requires starting with a failing test (RED phase).\n' +
'Write a new test for the next behavior you want to implement.');
}
const session = {
id: this.generateId(),
feature,
currentPhase: 'red',
history: [{
from: 'red',
to: 'red',
timestamp: new Date(),
message: `Started TDD session with ${failingTests.length} failing test(s)`,
}],
startedAt: new Date(),
};
this.sessions.set(session.id, session);
return session;
}
canImplementCode(sessionId) {
const session = this.sessions.get(sessionId);
if (!session) {
return {
allowed: false,
message: 'š« No active TDD session. Use "start_tdd_session" first.',
};
}
if (session.currentPhase !== 'red') {
return {
allowed: false,
message: `š« You're in the ${session.currentPhase.toUpperCase()} phase. You can only write implementation code in the RED phase.`,
};
}
const failingTests = session.feature.testCases.filter(tc => tc.status === 'failing');
if (failingTests.length === 0) {
return {
allowed: false,
message: 'š« No failing tests found. Write a failing test first!',
};
}
return {
allowed: true,
message: `ā
You have ${failingTests.length} failing test(s). You may now implement code to make them pass.`,
};
}
transitionToGreen(sessionId, passingTests) {
const session = this.sessions.get(sessionId);
if (!session) {
throw new Error('No active TDD session');
}
if (session.currentPhase !== 'red') {
throw new Error(`Cannot transition to GREEN from ${session.currentPhase} phase`);
}
session.currentPhase = 'green';
session.history.push({
from: 'red',
to: 'green',
timestamp: new Date(),
message: `Made ${passingTests.length} test(s) pass`,
});
}
canRefactor(sessionId) {
const session = this.sessions.get(sessionId);
if (!session) {
return {
allowed: false,
message: 'š« No active TDD session.',
};
}
if (session.currentPhase !== 'green') {
return {
allowed: false,
message: `š« You can only refactor in the GREEN phase. Current phase: ${session.currentPhase.toUpperCase()}`,
};
}
const allTestsPassing = session.feature.testCases.every(tc => tc.status === 'passing');
if (!allTestsPassing) {
return {
allowed: false,
message: 'š« Not all tests are passing. Fix failing tests before refactoring.',
};
}
return {
allowed: true,
message: 'ā
All tests passing. You may now refactor the code.',
};
}
completeRefactoring(sessionId) {
const session = this.sessions.get(sessionId);
if (!session) {
throw new Error('No active TDD session');
}
if (session.currentPhase !== 'green') {
throw new Error(`Cannot complete refactoring from ${session.currentPhase} phase`);
}
session.currentPhase = 'refactor';
session.history.push({
from: 'green',
to: 'refactor',
timestamp: new Date(),
message: 'Completed refactoring while maintaining all tests passing',
});
}
startNewCycle(sessionId) {
const session = this.sessions.get(sessionId);
if (!session) {
throw new Error('No active TDD session');
}
session.currentPhase = 'red';
session.history.push({
from: 'refactor',
to: 'red',
timestamp: new Date(),
message: 'Started new TDD cycle',
});
}
getSessionReport(sessionId) {
const session = this.sessions.get(sessionId);
if (!session) {
return 'ā No active TDD session found';
}
const duration = this.formatDuration(session.startedAt, session.completedAt || new Date());
const cycleCount = session.history.filter(h => h.from === 'refactor' && h.to === 'red').length + 1;
return `
š TDD Session Report
āāāāāāāāāāāāāāāāāāāā
š Feature: ${session.feature.name}
ā±ļø Duration: ${duration}
š Cycles Completed: ${cycleCount}
š Current Phase: ${session.currentPhase.toUpperCase()}
ā
Tests Passing: ${session.feature.testCases.filter(tc => tc.status === 'passing').length}/${session.feature.testCases.length}
š History:
${session.history.map(h => ` ⢠${h.message} (${h.timestamp.toLocaleTimeString()})`).join('\n')}
`;
}
generateId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
formatDuration(start, end) {
const ms = end.getTime() - start.getTime();
const minutes = Math.floor(ms / 60000);
const seconds = Math.floor((ms % 60000) / 1000);
return `${minutes}m ${seconds}s`;
}
}
//# sourceMappingURL=tdd-enforcer.js.map