@stackmemoryai/stackmemory
Version:
Lossless, project-scoped memory for AI coding tools. Durable context across sessions with 56 MCP tools, FTS5 search, conductor orchestrator, loop/watch monitoring, snapshot capture, pre-flight overlap checks, Claude/Codex/OpenCode wrappers, Linear sync, a
792 lines (791 loc) • 26.5 kB
JavaScript
import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { execSync } from "child_process";
import {
existsSync,
readFileSync,
readdirSync,
statSync,
writeFileSync,
mkdirSync
} from "fs";
import { basename, join } from "path";
import { homedir, tmpdir } from "os";
import { globSync } from "glob";
let countTokens;
try {
const tokenizer = await import("@anthropic-ai/tokenizer");
countTokens = tokenizer.countTokens;
} catch {
countTokens = (text) => Math.ceil(text.length / 3.5);
}
function loadSessionDecisions(projectRoot) {
const storePath = join(projectRoot, ".stackmemory", "session-decisions.json");
if (existsSync(storePath)) {
try {
const store = JSON.parse(readFileSync(storePath, "utf-8"));
return store.decisions || [];
} catch {
return [];
}
}
return [];
}
function loadReviewFeedback(projectRoot) {
const storePath = join(projectRoot, ".stackmemory", "review-feedback.json");
if (existsSync(storePath)) {
try {
const store = JSON.parse(
readFileSync(storePath, "utf-8")
);
const cutoff = Date.now() - 24 * 60 * 60 * 1e3;
return store.feedbacks.filter(
(f) => new Date(f.timestamp).getTime() > cutoff
);
} catch {
return [];
}
}
return [];
}
function saveReviewFeedback(projectRoot, feedbacks) {
const dir = join(projectRoot, ".stackmemory");
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
const storePath = join(dir, "review-feedback.json");
let existing = [];
if (existsSync(storePath)) {
try {
const store2 = JSON.parse(
readFileSync(storePath, "utf-8")
);
existing = store2.feedbacks || [];
} catch {
}
}
const seen = /* @__PURE__ */ new Set();
const merged = [];
for (const f of [...feedbacks, ...existing]) {
const key = `${f.source}:${f.keyPoints[0] || ""}`;
if (!seen.has(key)) {
seen.add(key);
merged.push(f);
}
}
const store = {
feedbacks: merged.slice(0, 20),
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
};
writeFileSync(storePath, JSON.stringify(store, null, 2));
}
function findAgentOutputDirs(projectRoot) {
const dirs = [];
const tmpBase = process.env["TMPDIR"] || tmpdir() || "/tmp";
const projectPathEncoded = projectRoot.replace(/\//g, "-").replace(/^-/, "");
const pattern1 = join(tmpBase, "claude", `*${projectPathEncoded}*`, "tasks");
try {
const matches = globSync(pattern1);
dirs.push(...matches);
} catch {
}
if (tmpBase !== "/private/tmp") {
const pattern2 = join(
"/private/tmp",
"claude",
`*${projectPathEncoded}*`,
"tasks"
);
try {
const matches = globSync(pattern2);
dirs.push(...matches);
} catch {
}
}
const homeClaudeDir = join(homedir(), ".claude", "projects");
if (existsSync(homeClaudeDir)) {
try {
const projectDirs = readdirSync(homeClaudeDir);
for (const d of projectDirs) {
const tasksDir = join(homeClaudeDir, d, "tasks");
if (existsSync(tasksDir)) {
dirs.push(tasksDir);
}
}
} catch {
}
}
return [...new Set(dirs)];
}
class EnhancedHandoffGenerator {
projectRoot;
claudeProjectsDir;
constructor(projectRoot) {
this.projectRoot = projectRoot;
this.claudeProjectsDir = join(homedir(), ".claude", "projects");
}
/**
* Generate a high-efficacy handoff
*/
async generate() {
const handoff = {
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
project: basename(this.projectRoot),
branch: this.getCurrentBranch(),
activeWork: await this.extractActiveWork(),
decisions: await this.extractDecisions(),
architecture: await this.extractArchitecture(),
blockers: await this.extractBlockers(),
reviewFeedback: await this.extractReviewFeedback(),
nextActions: await this.extractNextActions(),
codePatterns: await this.extractCodePatterns(),
estimatedTokens: 0
};
const markdown = this.toMarkdown(handoff);
handoff.estimatedTokens = countTokens(markdown);
return handoff;
}
/**
* Extract what we're currently building from git and recent files
*/
async extractActiveWork() {
const recentCommits = this.getRecentCommits(5);
const recentFiles = this.getRecentlyModifiedFiles(10);
let description = "Unknown - check git log for context";
let status = "in_progress";
if (recentCommits.length > 0) {
const lastCommit = recentCommits[0];
if (lastCommit.includes("feat:") || lastCommit.includes("implement")) {
description = lastCommit.replace(/^[a-f0-9]+\s+/, "");
} else if (lastCommit.includes("fix:")) {
description = "Bug fix: " + lastCommit.replace(/^[a-f0-9]+\s+/, "");
} else if (lastCommit.includes("chore:") || lastCommit.includes("refactor:")) {
description = lastCommit.replace(/^[a-f0-9]+\s+/, "");
} else {
description = lastCommit.replace(/^[a-f0-9]+\s+/, "");
}
}
const gitStatus = this.getGitStatus();
if (gitStatus.includes("conflict")) {
status = "blocked";
}
return {
description,
status,
keyFiles: recentFiles.slice(0, 5),
progress: recentCommits.length > 0 ? `${recentCommits.length} commits in current session` : void 0
};
}
/**
* Extract decisions from session store, git commits, and decision logs
*/
async extractDecisions() {
const decisions = [];
const sessionDecisions = loadSessionDecisions(this.projectRoot);
for (const d of sessionDecisions) {
decisions.push({
what: d.what,
why: d.why,
alternatives: d.alternatives
});
}
const commits = this.getRecentCommits(20);
for (const commit of commits) {
if (commit.toLowerCase().includes("use ") || commit.toLowerCase().includes("switch to ") || commit.toLowerCase().includes("default to ") || commit.toLowerCase().includes("make ") && commit.toLowerCase().includes("optional")) {
const commitText = commit.replace(/^[a-f0-9]+\s+/, "");
if (!decisions.some((d) => d.what.includes(commitText.slice(0, 30)))) {
decisions.push({
what: commitText,
why: "See commit for details"
});
}
}
}
const decisionsFile = join(
this.projectRoot,
".stackmemory",
"decisions.md"
);
if (existsSync(decisionsFile)) {
const content = readFileSync(decisionsFile, "utf-8");
const parsed = this.parseDecisionsFile(content);
decisions.push(...parsed);
}
return decisions.slice(0, 10);
}
/**
* Parse a decisions.md file
*/
parseDecisionsFile(content) {
const decisions = [];
const lines = content.split("\n");
let currentDecision = null;
for (const line of lines) {
if (line.startsWith("## ") || line.startsWith("### ")) {
if (currentDecision) {
decisions.push(currentDecision);
}
currentDecision = { what: line.replace(/^#+\s+/, ""), why: "" };
} else if (currentDecision && line.toLowerCase().includes("rationale:")) {
currentDecision.why = line.replace(/rationale:\s*/i, "").trim();
} else if (currentDecision && line.toLowerCase().includes("why:")) {
currentDecision.why = line.replace(/why:\s*/i, "").trim();
} else if (currentDecision && line.toLowerCase().includes("alternatives:")) {
currentDecision.alternatives = [];
} else if (currentDecision?.alternatives && line.trim().startsWith("-")) {
currentDecision.alternatives.push(line.replace(/^\s*-\s*/, "").trim());
}
}
if (currentDecision) {
decisions.push(currentDecision);
}
return decisions;
}
/**
* Extract architecture context from key files
*/
async extractArchitecture() {
const keyComponents = [];
const patterns = [];
const recentFiles = this.getRecentlyModifiedFiles(20);
const codeFiles = recentFiles.filter(
(f) => f.endsWith(".ts") || f.endsWith(".js") || f.endsWith(".tsx")
);
for (const file of codeFiles.slice(0, 8)) {
const purpose = this.inferFilePurpose(file);
if (purpose) {
keyComponents.push({ file, purpose });
}
}
if (codeFiles.some((f) => f.includes("/daemon/"))) {
patterns.push("Daemon/background process pattern");
}
if (codeFiles.some((f) => f.includes("/cli/"))) {
patterns.push("CLI command pattern");
}
if (codeFiles.some((f) => f.includes(".test.") || f.includes("__tests__"))) {
patterns.push("Test files present");
}
if (codeFiles.some((f) => f.includes("/core/"))) {
patterns.push("Core/domain separation");
}
return { keyComponents, patterns };
}
/**
* Infer purpose from file name and path
*/
inferFilePurpose(filePath) {
const name = basename(filePath).replace(/\.(ts|js|tsx)$/, "");
const path = filePath.toLowerCase();
if (path.includes("daemon")) return "Background daemon/service";
if (path.includes("cli/command")) return "CLI command handler";
if (path.includes("config")) return "Configuration management";
if (path.includes("storage")) return "Data storage layer";
if (path.includes("handoff")) return "Session handoff logic";
if (path.includes("service")) return "Service orchestration";
if (path.includes("manager")) return "Resource/state management";
if (path.includes("handler")) return "Event/request handler";
if (path.includes("util") || path.includes("helper"))
return "Utility functions";
if (path.includes("types") || path.includes("interface"))
return "Type definitions";
if (path.includes("test")) return null;
if (name.includes("-")) {
return name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
}
return null;
}
/**
* Extract blockers from git status and recent errors
*/
async extractBlockers() {
const blockers = [];
const gitStatus = this.getGitStatus();
if (gitStatus.includes("UU ") || gitStatus.includes("both modified")) {
blockers.push({
issue: "Merge conflict detected",
attempted: ["Check git status for affected files"],
status: "open"
});
}
try {
const testResult = execSync("npm run test:run 2>&1 || true", {
encoding: "utf-8",
cwd: this.projectRoot,
timeout: 3e4
});
if (testResult.includes("FAIL") || testResult.includes("failed")) {
const failCount = (testResult.match(/(\d+) failed/i) || ["", "?"])[1];
blockers.push({
issue: `Test failures: ${failCount} tests failing`,
attempted: ["Run npm test for details"],
status: "open"
});
}
} catch {
}
try {
const lintResult = execSync("npm run lint 2>&1 || true", {
encoding: "utf-8",
cwd: this.projectRoot,
timeout: 3e4
});
if (lintResult.includes("error") && !lintResult.includes("0 errors")) {
blockers.push({
issue: "Lint errors present",
attempted: ["Run npm run lint for details"],
status: "open"
});
}
} catch {
}
return blockers;
}
/**
* Extract review feedback from agent output files and persisted storage
*/
async extractReviewFeedback() {
const feedback = [];
const newFeedbacks = [];
const outputDirs = findAgentOutputDirs(this.projectRoot);
for (const tmpDir of outputDirs) {
if (!existsSync(tmpDir)) continue;
try {
const files = readdirSync(tmpDir).filter((f) => f.endsWith(".output"));
const recentFiles = files.map((f) => ({
name: f,
path: join(tmpDir, f),
stat: statSync(join(tmpDir, f))
})).filter((f) => Date.now() - f.stat.mtimeMs < 36e5).sort((a, b) => b.stat.mtimeMs - a.stat.mtimeMs).slice(0, 3);
for (const file of recentFiles) {
const content = readFileSync(file.path, "utf-8");
const extracted = this.extractKeyPointsFromReview(content);
if (extracted.keyPoints.length > 0) {
feedback.push(extracted);
newFeedbacks.push({
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
source: extracted.source,
keyPoints: extracted.keyPoints,
actionItems: extracted.actionItems,
sourceFile: file.name
});
}
}
} catch {
}
}
if (newFeedbacks.length > 0) {
saveReviewFeedback(this.projectRoot, newFeedbacks);
}
if (feedback.length === 0) {
const stored = loadReviewFeedback(this.projectRoot);
for (const s of stored.slice(0, 3)) {
feedback.push({
source: s.source,
keyPoints: s.keyPoints,
actionItems: s.actionItems
});
}
}
return feedback.length > 0 ? feedback : void 0;
}
/**
* Extract key points from a review output
*/
extractKeyPointsFromReview(content) {
const keyPoints = [];
const actionItems = [];
let source = "Agent Review";
if (content.includes("Product Manager") || content.includes("product-manager")) {
source = "Product Manager";
} else if (content.includes("Staff Architect") || content.includes("staff-architect")) {
source = "Staff Architect";
}
const lines = content.split("\n");
let inRecommendations = false;
let inActionItems = false;
for (const line of lines) {
const trimmed = line.trim();
if (trimmed.toLowerCase().includes("recommendation") || trimmed.toLowerCase().includes("key finding")) {
inRecommendations = true;
inActionItems = false;
continue;
}
if (trimmed.toLowerCase().includes("action") || trimmed.toLowerCase().includes("next step") || trimmed.toLowerCase().includes("priority")) {
inActionItems = true;
inRecommendations = false;
continue;
}
if (trimmed.startsWith("- ") || trimmed.startsWith("* ") || /^\d+\.\s/.test(trimmed)) {
const point = trimmed.replace(/^[-*]\s+/, "").replace(/^\d+\.\s+/, "");
if (point.length > 10 && point.length < 200) {
if (inActionItems) {
actionItems.push(point);
} else if (inRecommendations) {
keyPoints.push(point);
}
}
}
}
return {
source,
keyPoints: keyPoints.slice(0, 5),
actionItems: actionItems.slice(0, 5)
};
}
/**
* Extract next actions from todo state and git
*/
async extractNextActions() {
const actions = [];
const gitStatus = this.getGitStatus();
if (gitStatus.trim()) {
actions.push("Commit pending changes");
}
const recentFiles = this.getRecentlyModifiedFiles(5);
for (const file of recentFiles) {
try {
const fullPath = join(this.projectRoot, file);
if (existsSync(fullPath)) {
const content = readFileSync(fullPath, "utf-8");
const todos = content.match(/\/\/\s*TODO:?\s*.+/gi) || [];
for (const todo of todos.slice(0, 2)) {
actions.push(todo.replace(/\/\/\s*TODO:?\s*/i, "TODO: "));
}
}
} catch {
}
}
const tasksFile = join(this.projectRoot, ".stackmemory", "tasks.json");
if (existsSync(tasksFile)) {
try {
const tasks = JSON.parse(readFileSync(tasksFile, "utf-8"));
const pending = tasks.filter(
(t) => t.status === "pending" || t.status === "in_progress"
);
for (const task of pending.slice(0, 3)) {
actions.push(task.title || task.description);
}
} catch {
}
}
return actions.slice(0, 8);
}
/**
* Extract established code patterns
*/
async extractCodePatterns() {
const patterns = [];
const eslintConfig = join(this.projectRoot, "eslint.config.js");
if (existsSync(eslintConfig)) {
const content = readFileSync(eslintConfig, "utf-8");
if (content.includes("argsIgnorePattern")) {
patterns.push("Underscore prefix for unused vars (_var)");
}
if (content.includes("ignores") && content.includes("test")) {
patterns.push("Test files excluded from lint");
}
}
const tsconfig = join(this.projectRoot, "tsconfig.json");
if (existsSync(tsconfig)) {
const content = readFileSync(tsconfig, "utf-8");
if (content.includes('"strict": true')) {
patterns.push("TypeScript strict mode enabled");
}
if (content.includes("ES2022") || content.includes("ESNext")) {
patterns.push("ESM module system");
}
}
return patterns;
}
/**
* Get recent git commits
*/
getRecentCommits(count) {
try {
const result = execSync(`git log --oneline -${count}`, {
encoding: "utf-8",
cwd: this.projectRoot
});
return result.trim().split("\n").filter(Boolean);
} catch {
return [];
}
}
/**
* Get current git branch
*/
getCurrentBranch() {
try {
return execSync("git rev-parse --abbrev-ref HEAD", {
encoding: "utf-8",
cwd: this.projectRoot
}).trim();
} catch {
return "unknown";
}
}
/**
* Get git status
*/
getGitStatus() {
try {
return execSync("git status --short", {
encoding: "utf-8",
cwd: this.projectRoot
});
} catch {
return "";
}
}
/**
* Get recently modified files
*/
getRecentlyModifiedFiles(count) {
try {
const result = execSync(
`git diff --name-only HEAD~10 HEAD 2>/dev/null || git diff --name-only`,
{
encoding: "utf-8",
cwd: this.projectRoot
}
);
return result.trim().split("\n").filter(Boolean).slice(0, count);
} catch {
return [];
}
}
/**
* Convert handoff to markdown (verbose format)
*/
toMarkdown(handoff) {
const lines = [];
lines.push(`# Session Handoff - ${handoff.timestamp.split("T")[0]}`);
lines.push("");
lines.push(`**Project**: ${handoff.project}`);
lines.push(`**Branch**: ${handoff.branch}`);
lines.push("");
lines.push("## Active Work");
lines.push(`- **Building**: ${handoff.activeWork.description}`);
lines.push(`- **Status**: ${handoff.activeWork.status}`);
if (handoff.activeWork.keyFiles.length > 0) {
lines.push(`- **Key files**: ${handoff.activeWork.keyFiles.join(", ")}`);
}
if (handoff.activeWork.progress) {
lines.push(`- **Progress**: ${handoff.activeWork.progress}`);
}
lines.push("");
if (handoff.decisions.length > 0) {
lines.push("## Key Decisions");
for (const d of handoff.decisions) {
lines.push(`1. **${d.what}**`);
if (d.why) {
lines.push(` - Rationale: ${d.why}`);
}
if (d.alternatives && d.alternatives.length > 0) {
lines.push(
` - Alternatives considered: ${d.alternatives.join(", ")}`
);
}
}
lines.push("");
}
if (handoff.architecture.keyComponents.length > 0) {
lines.push("## Architecture Context");
for (const c of handoff.architecture.keyComponents) {
lines.push(`- \`${c.file}\`: ${c.purpose}`);
}
if (handoff.architecture.patterns.length > 0) {
lines.push("");
lines.push("**Patterns**: " + handoff.architecture.patterns.join(", "));
}
lines.push("");
}
if (handoff.blockers.length > 0) {
lines.push("## Blockers");
for (const b of handoff.blockers) {
lines.push(`- **${b.issue}** [${b.status}]`);
if (b.attempted.length > 0) {
lines.push(` - Tried: ${b.attempted.join(", ")}`);
}
}
lines.push("");
}
if (handoff.reviewFeedback && handoff.reviewFeedback.length > 0) {
lines.push("## Review Feedback");
for (const r of handoff.reviewFeedback) {
lines.push(`### ${r.source}`);
if (r.keyPoints.length > 0) {
lines.push("**Key Points**:");
for (const p of r.keyPoints) {
lines.push(`- ${p}`);
}
}
if (r.actionItems.length > 0) {
lines.push("**Action Items**:");
for (const a of r.actionItems) {
lines.push(`- ${a}`);
}
}
lines.push("");
}
}
if (handoff.nextActions.length > 0) {
lines.push("## Next Actions");
for (const a of handoff.nextActions) {
lines.push(`1. ${a}`);
}
lines.push("");
}
if (handoff.codePatterns && handoff.codePatterns.length > 0) {
lines.push("## Established Patterns");
for (const p of handoff.codePatterns) {
lines.push(`- ${p}`);
}
lines.push("");
}
lines.push("---");
lines.push(`*Estimated tokens: ~${handoff.estimatedTokens}*`);
lines.push(`*Generated at ${handoff.timestamp}*`);
return lines.join("\n");
}
/**
* Convert handoff to compact format (~50% smaller)
* Optimized for minimal context window usage
*/
toCompact(handoff) {
const lines = [];
lines.push(`# Handoff: ${handoff.project}@${handoff.branch}`);
const status = handoff.activeWork.status === "in_progress" ? "WIP" : handoff.activeWork.status;
lines.push(`## Work: ${handoff.activeWork.description} [${status}]`);
if (handoff.activeWork.keyFiles.length > 0) {
const files = handoff.activeWork.keyFiles.slice(0, 5).map((f) => basename(f)).join(", ");
const progress = handoff.activeWork.progress ? ` (${handoff.activeWork.progress.replace(" in current session", "")})` : "";
lines.push(`Files: ${files}${progress}`);
}
if (handoff.decisions.length > 0) {
lines.push("");
lines.push("## Decisions");
for (const d of handoff.decisions.slice(0, 7)) {
const what = d.what.length > 40 ? d.what.slice(0, 37) + "..." : d.what;
const why = d.why ? ` \u2192 ${d.why.slice(0, 50)}` : "";
lines.push(`- ${what}${why}`);
}
}
if (handoff.blockers.length > 0) {
lines.push("");
lines.push("## Blockers");
for (const b of handoff.blockers) {
const status2 = b.status === "open" ? "!" : "\u2713";
const tried = b.attempted.length > 0 ? ` \u2192 ${b.attempted[0]}` : "";
lines.push(`${status2} ${b.issue}${tried}`);
}
}
if (handoff.reviewFeedback && handoff.reviewFeedback.length > 0) {
lines.push("");
lines.push("## Feedback");
for (const r of handoff.reviewFeedback.slice(0, 2)) {
lines.push(`[${r.source}]`);
for (const p of r.keyPoints.slice(0, 3)) {
lines.push(`- ${p.slice(0, 60)}`);
}
for (const a of r.actionItems.slice(0, 2)) {
lines.push(`\u2192 ${a.slice(0, 60)}`);
}
}
}
if (handoff.nextActions.length > 0) {
lines.push("");
lines.push("## Next");
for (const a of handoff.nextActions.slice(0, 3)) {
lines.push(`- ${a.slice(0, 60)}`);
}
}
lines.push("");
lines.push(`---`);
lines.push(
`~${handoff.estimatedTokens} tokens | ${handoff.timestamp.split("T")[0]}`
);
return lines.join("\n");
}
/**
* Convert handoff to ultra-compact pipe-delimited format (~90% smaller)
* Optimized for minimal token usage while preserving critical context
* Target: ~100-150 tokens
*/
toUltraCompact(handoff) {
const lines = [];
const status = handoff.activeWork.status === "in_progress" ? "WIP" : handoff.activeWork.status;
const commitCount = handoff.activeWork.progress?.match(/(\d+)/)?.[1] || "0";
lines.push(
`[H]${handoff.project}@${handoff.branch}|${status}|${commitCount}c`
);
if (handoff.activeWork.keyFiles.length > 0) {
const files = handoff.activeWork.keyFiles.slice(0, 5).map((f) => basename(f).replace(/\.(ts|js|tsx|jsx)$/, "")).join(",");
lines.push(`[F]${files}`);
}
if (handoff.decisions.length > 0) {
const decisions = handoff.decisions.slice(0, 5).map((d) => {
const what = d.what.slice(0, 25).replace(/\|/g, "/");
const why = d.why ? `\u2192${d.why.slice(0, 20)}` : "";
return `${what}${why}`;
}).join("|");
lines.push(`[D]${decisions}`);
}
if (handoff.blockers.length > 0) {
const blockers = handoff.blockers.slice(0, 3).map((b) => {
const marker = b.status === "open" ? "!" : "\u2713";
const issue = b.issue.slice(0, 20).replace(/\|/g, "/");
const tried = b.attempted.length > 0 ? `\u2192${b.attempted[0].slice(0, 15)}` : "";
return `${marker}${issue}${tried}`;
}).join("|");
lines.push(`[B]${blockers}`);
}
if (handoff.nextActions.length > 0) {
const actions = handoff.nextActions.slice(0, 3).map((a) => a.slice(0, 25).replace(/\|/g, "/")).join("|");
lines.push(`[N]${actions}`);
}
const ultraCompactContent = lines.join("\n");
const tokens = countTokens(ultraCompactContent);
lines.push(`~${tokens}t|${handoff.timestamp.split("T")[0]}`);
return lines.join("\n");
}
/**
* Auto-select format based on context budget and content complexity
* Returns: 'ultra' | 'compact' | 'verbose'
*/
selectFormat(handoff, contextBudget) {
if (contextBudget !== void 0) {
if (contextBudget < 500) return "ultra";
if (contextBudget < 2e3) return "compact";
return "verbose";
}
const complexity = handoff.decisions.length + handoff.blockers.length + (handoff.reviewFeedback?.length || 0) * 2 + handoff.nextActions.length;
if (complexity <= 3 && handoff.activeWork.keyFiles.length <= 3) {
return "ultra";
}
if (complexity > 8 || handoff.reviewFeedback && handoff.reviewFeedback.length > 1) {
return "verbose";
}
return "compact";
}
/**
* Generate handoff in auto-selected format
*/
toAutoFormat(handoff, contextBudget) {
const format = this.selectFormat(handoff, contextBudget);
switch (format) {
case "ultra":
return this.toUltraCompact(handoff);
case "verbose":
return this.toMarkdown(handoff);
default:
return this.toCompact(handoff);
}
}
}
export {
EnhancedHandoffGenerator
};