-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathcheckpoint.ts
More file actions
122 lines (109 loc) · 4.3 KB
/
checkpoint.ts
File metadata and controls
122 lines (109 loc) · 4.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import { z } from "zod";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { writeFileSync, existsSync, mkdirSync } from "fs";
import { join, dirname } from "path";
import { run, getBranch, getStatus, getLastCommit, getStagedFiles } from "../lib/git.js";
import { PROJECT_DIR } from "../lib/files.js";
import { appendLog, now } from "../lib/state.js";
export function registerCheckpoint(server: McpServer): void {
server.tool(
"checkpoint",
`Save a session checkpoint before context compaction hits. Commits current work, writes session state to workspace docs, and creates a resumption note. Call this proactively when session is getting long, or when the session-health hook warns about turn count. This is your "save game" before compaction wipes context.`,
{
summary: z.string().describe("What was accomplished so far in this session"),
next_steps: z.string().describe("What still needs to be done"),
current_blockers: z.string().optional().describe("Any issues or blockers encountered"),
commit_mode: z.enum(["staged", "tracked", "all"]).optional().describe("What to commit: 'staged' (only staged files), 'tracked' (modified tracked files), 'all' (git add -A). Default: 'tracked'"),
},
async ({ summary, next_steps, current_blockers, commit_mode }) => {
const mode = commit_mode || "tracked";
const branch = getBranch();
const dirty = getStatus();
const lastCommit = getLastCommit();
const timestamp = now();
// Write checkpoint file
const checkpointDir = join(PROJECT_DIR, ".claude");
if (!existsSync(checkpointDir)) mkdirSync(checkpointDir, { recursive: true });
const checkpointFile = join(checkpointDir, "last-checkpoint.md");
const checkpointContent = `# Session Checkpoint
**Time**: ${timestamp}
**Branch**: ${branch}
**Last Commit**: ${lastCommit}
## Accomplished
${summary}
## Next Steps
${next_steps}
${current_blockers ? `## Blockers\n${current_blockers}\n` : ""}
## Uncommitted Work (at checkpoint time)
\`\`\`
${dirty || "clean"}
\`\`\`
`;
writeFileSync(checkpointFile, checkpointContent);
appendLog("checkpoint-log.jsonl", {
timestamp,
branch,
summary,
next_steps,
blockers: current_blockers || null,
dirty_files: dirty ? dirty.split("\n").filter(Boolean).length : 0,
commit_mode: mode,
});
// Commit based on mode
let commitResult = "no uncommitted changes";
if (dirty) {
const shortSummary = summary.split("\n")[0].slice(0, 72);
const commitMsg = `checkpoint: ${shortSummary}`;
let addCmd: string;
switch (mode) {
case "staged": {
const staged = getStagedFiles();
if (!staged) {
commitResult = "nothing staged — skipped commit (use 'tracked' or 'all' mode, or stage files first)";
}
addCmd = "true"; // noop, already staged
break;
}
case "all":
addCmd = "git add -A";
break;
case "tracked":
default:
addCmd = "git add -u";
break;
}
if (commitResult === "no uncommitted changes") {
// Stage the checkpoint file too
run(["add", checkpointFile]);
// Stage files based on mode
const addArgs = addCmd === "git add -A" ? ["add", "-A"] : ["add", "-u"];
run(addArgs);
const result = run(["commit", "-m", commitMsg]);
if (result.includes("commit failed") || result.includes("nothing to commit")) {
// Rollback: unstage if commit failed
run(["reset", "HEAD"]);
commitResult = `commit failed: ${result}`;
} else {
commitResult = result;
}
}
}
return {
content: [{
type: "text" as const,
text: `## Checkpoint Saved ✅
**File**: .claude/last-checkpoint.md
**Branch**: ${branch}
**Commit mode**: ${mode}
**Commit**: ${commitResult}
### What's saved:
- Summary of work done
- Next steps for continuation
${current_blockers ? "- Current blockers\n" : ""}- Working tree state at checkpoint time
### To resume after compaction:
Tell the next session/continuation: "Read .claude/last-checkpoint.md for where I left off"`,
}],
};
}
);
}