flashbacker
Version:
Claude Code state management with session continuity and AI personas
194 lines (190 loc) • 7.82 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TodoChainManager = void 0;
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
/**
* TodoChainManager handles persistent TODO_CHAIN.md operations
* Based on Cline's FocusChainManager pattern adapted for Flashbacker memory system
*/
class TodoChainManager {
constructor(projectDir) {
this.projectDir = projectDir;
this.todoChainPath = path_1.default.join(projectDir, '.claude', 'flashback', 'memory', 'TODO_CHAIN.md');
}
/**
* Get current TODO_CHAIN.md content
* Returns null if file doesn't exist
*/
async getCurrentTodoChain() {
try {
if (await fs_extra_1.default.pathExists(this.todoChainPath)) {
const content = await fs_extra_1.default.readFile(this.todoChainPath, 'utf8');
return content.trim();
}
return null;
}
catch (error) {
console.error('Error reading TODO_CHAIN.md:', error);
return null;
}
}
/**
* Update TODO_CHAIN.md with new content
* Creates file if it doesn't exist
*/
async updateTodoChain(content) {
try {
// Ensure memory directory exists
const memoryDir = path_1.default.dirname(this.todoChainPath);
await fs_extra_1.default.ensureDir(memoryDir);
// Write TODO_CHAIN.md with proper header
const todoChainContent = `# TODO Chain - Persistent Development Tasks
> **Context-resistant todo list that survives compaction**
> This file maintains development continuity across context resets
## Current Tasks
${content.trim()}
---
*Last updated: ${new Date().toLocaleString()}*
*Use \`flashback todo-chain --update\` to modify this list*
`;
await fs_extra_1.default.writeFile(this.todoChainPath, todoChainContent, 'utf8');
}
catch (error) {
console.error('Error writing TODO_CHAIN.md:', error);
throw error;
}
}
/**
* Parse progress from todo chain content
* Counts completed vs total tasks using markdown checklist format
*/
parseProgress(todoContent) {
try {
const lines = todoContent.split('\n');
let total = 0;
let completed = 0;
for (const line of lines) {
const trimmed = line.trim();
// Match standard markdown checklist format: - [ ] or - [x] or - [X]
if (trimmed.match(/^-\s*\[[\sxX]\]/)) {
total++;
// Check if completed (x or X in brackets)
if (trimmed.match(/^-\s*\[[xX]\]/)) {
completed++;
}
}
}
const percentage = total > 0 ? Math.round((completed / total) * 100) : 0;
return {
completed,
total,
percentage,
};
}
catch (error) {
console.error('Error parsing todo progress:', error);
return {
completed: 0,
total: 0,
percentage: 0,
};
}
}
/**
* Generate context summary from memory files and git commits
* Combines REMEMBER.md, WORKING_PLAN.md, and recent commits
*/
async generateContextSummary() {
try {
const memoryDir = path_1.default.join(this.projectDir, '.claude', 'flashback', 'memory');
let summary = '## Context Summary\n\n';
// Add REMEMBER.md content (first 500 chars)
const rememberPath = path_1.default.join(memoryDir, 'REMEMBER.md');
if (await fs_extra_1.default.pathExists(rememberPath)) {
const rememberContent = await fs_extra_1.default.readFile(rememberPath, 'utf8');
summary += `### Project Knowledge (REMEMBER.md):\n${rememberContent.substring(0, 500)}...\n\n`;
}
// Add WORKING_PLAN.md current status (first 300 chars)
const workingPlanPath = path_1.default.join(memoryDir, 'WORKING_PLAN.md');
if (await fs_extra_1.default.pathExists(workingPlanPath)) {
const workingPlanContent = await fs_extra_1.default.readFile(workingPlanPath, 'utf8');
summary += `### Current Priorities (WORKING_PLAN.md):\n${workingPlanContent.substring(0, 300)}...\n\n`;
}
// Add recent git commits
try {
const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
const commits = execSync('git log --oneline -5', {
cwd: this.projectDir,
encoding: 'utf8',
});
summary += `### Recent Commits:\n\`\`\`\n${commits.trim()}\n\`\`\`\n\n`;
}
catch (error) {
summary += '### Recent Commits:\nNo git history available\n\n';
}
// Add current TODO_CHAIN.md if it exists
const currentTodo = await this.getCurrentTodoChain();
if (currentTodo) {
const progress = this.parseProgress(currentTodo);
summary += `### Current TODO Chain (${progress.completed}/${progress.total} - ${progress.percentage}%):\n${currentTodo}\n\n`;
}
else {
summary += '### Current TODO Chain:\nNo TODO_CHAIN.md exists yet\n\n';
}
return summary;
}
catch (error) {
console.error('Error generating context summary:', error);
return 'Error generating context summary';
}
}
/**
* Check if reminder should be injected
* Based on message count and todo update frequency
*/
shouldInjectReminder(messageCount, lastUpdateMessage = 0) {
// Follow Cline's pattern: remind every 6 messages
const reminderInterval = 6;
const messagesSinceUpdate = messageCount - lastUpdateMessage;
return messagesSinceUpdate >= reminderInterval && messageCount > 0;
}
}
exports.TodoChainManager = TodoChainManager;
//# sourceMappingURL=todo-chain-manager.js.map
;