claude-baton 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/memo-resume.md +13 -0
- package/dist/cli.js +139 -27
- package/dist/index.js +10 -9
- package/dist/llm.js +2 -2
- package/dist/store.d.ts +1 -0
- package/dist/store.js +28 -8
- package/dist/types.d.ts +1 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +17 -4
- package/package.json +1 -1
- package/prompts/auto_checkpoint.txt +23 -9
- package/prompts/daily_summary.txt +1 -3
package/commands/memo-resume.md
CHANGED
|
@@ -8,6 +8,17 @@ Restore context from last checkpoint at session start.
|
|
|
8
8
|
|
|
9
9
|
3. Call the `get_checkpoint` MCP tool (latest by default, or by ID if the user specified one via $ARGUMENTS).
|
|
10
10
|
|
|
11
|
+
3.5. **Staleness check:** If a checkpoint was found, compute the time delta between the checkpoint's `created_at` and now:
|
|
12
|
+
- More than 7 days old: display `"WARNING: This checkpoint is [N] days old. Project state may have changed significantly."`
|
|
13
|
+
- More than 24 hours but 7 days or less: display `"Note: This checkpoint is [N] hours/days old."`
|
|
14
|
+
- 24 hours or less: no staleness note
|
|
15
|
+
|
|
16
|
+
3.75. **Cold-start fallback:** If NO checkpoint exists for this project:
|
|
17
|
+
- Run `git log --oneline -20`, `git diff --stat HEAD~5..HEAD`, `git branch --show-current`, `git status --short`
|
|
18
|
+
- Present a "Cold Start Briefing" with recent commits, file changes, and uncommitted work
|
|
19
|
+
- End with: "No checkpoint to resume from, but here is the project state from git. What would you like to work on?"
|
|
20
|
+
- Skip remaining steps (4-8)
|
|
21
|
+
|
|
11
22
|
4. Capture current git state by running these bash commands:
|
|
12
23
|
- `git branch --show-current`
|
|
13
24
|
- `git status --short`
|
|
@@ -28,6 +39,8 @@ Restore context from last checkpoint at session start.
|
|
|
28
39
|
## Session Resume -- [Project Name] -- [DATE]
|
|
29
40
|
|
|
30
41
|
Resuming from checkpoint: [timestamp]
|
|
42
|
+
Source: [auto/manual] checkpoint
|
|
43
|
+
[Staleness warning if applicable]
|
|
31
44
|
|
|
32
45
|
### Branch
|
|
33
46
|
[checkpoint branch] -- currently on [current branch]
|
package/dist/cli.js
CHANGED
|
@@ -5,8 +5,8 @@ import { fileURLToPath } from "url";
|
|
|
5
5
|
import path from "path";
|
|
6
6
|
import os from "os";
|
|
7
7
|
import { createInterface } from "readline";
|
|
8
|
-
import { initDatabase, getDefaultDbPath, saveDatabase, countAll, listProjects, insertCheckpoint, getAllCheckpoints, getAllDailySummaries, deleteProjectData, deleteAllData, } from "./store.js";
|
|
9
|
-
import { ensureDir } from "./utils.js";
|
|
8
|
+
import { initDatabase, getDefaultDbPath, saveDatabase, countAll, listProjects, insertCheckpoint, getLatestCheckpoint, getAllCheckpoints, getAllDailySummaries, deleteProjectData, deleteAllData, } from "./store.js";
|
|
9
|
+
import { ensureDir, normalizeProjectPath } from "./utils.js";
|
|
10
10
|
import { callClaudeJson } from "./llm.js";
|
|
11
11
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
12
|
function readPromptTemplate(name) {
|
|
@@ -33,8 +33,9 @@ export async function handleAutoCheckpoint() {
|
|
|
33
33
|
}
|
|
34
34
|
let transcriptPath;
|
|
35
35
|
try {
|
|
36
|
-
const
|
|
37
|
-
|
|
36
|
+
const hookInput = JSON.parse(stdinData);
|
|
37
|
+
// Claude Code PreCompact hook sends flat JSON with transcript_path at top level
|
|
38
|
+
transcriptPath = hookInput?.transcript_path;
|
|
38
39
|
}
|
|
39
40
|
catch {
|
|
40
41
|
console.error("[claude-baton] Could not parse hook metadata, skipping auto-checkpoint");
|
|
@@ -54,15 +55,44 @@ export async function handleAutoCheckpoint() {
|
|
|
54
55
|
const branch = gitCmd("git branch --show-current");
|
|
55
56
|
const status = gitCmd("git status --short");
|
|
56
57
|
const log = gitCmd("git log --oneline -10");
|
|
57
|
-
//
|
|
58
|
+
// Initialize DB and fetch previous checkpoint for chaining
|
|
59
|
+
const dbPath = getDefaultDbPath();
|
|
60
|
+
const db = await initDatabase(dbPath);
|
|
61
|
+
const projectPath = normalizeProjectPath(process.cwd());
|
|
62
|
+
const prevCheckpoint = getLatestCheckpoint(db, projectPath);
|
|
63
|
+
// Compute git diff since last checkpoint
|
|
64
|
+
let gitDiffSinceCheckpoint = "";
|
|
65
|
+
if (prevCheckpoint?.git_snapshot) {
|
|
66
|
+
const topCommitHash = prevCheckpoint.git_snapshot
|
|
67
|
+
.split("\n")[0]
|
|
68
|
+
?.split(" ")[0];
|
|
69
|
+
if (topCommitHash) {
|
|
70
|
+
gitDiffSinceCheckpoint = gitCmd(`git diff --stat ${topCommitHash}..HEAD`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Build previous checkpoint context
|
|
74
|
+
let prevContext = "No previous checkpoint exists for this project.";
|
|
75
|
+
if (prevCheckpoint) {
|
|
76
|
+
prevContext = [
|
|
77
|
+
`What was built: ${prevCheckpoint.what_was_built}`,
|
|
78
|
+
`Current state: ${prevCheckpoint.current_state}`,
|
|
79
|
+
`Next steps: ${prevCheckpoint.next_steps}`,
|
|
80
|
+
prevCheckpoint.decisions_made
|
|
81
|
+
? `Decisions: ${prevCheckpoint.decisions_made}`
|
|
82
|
+
: null,
|
|
83
|
+
]
|
|
84
|
+
.filter(Boolean)
|
|
85
|
+
.join("\n");
|
|
86
|
+
}
|
|
87
|
+
// Build prompt with three sections
|
|
58
88
|
const template = readPromptTemplate("auto_checkpoint.txt");
|
|
59
|
-
const prompt = template
|
|
89
|
+
const prompt = template
|
|
90
|
+
.replace("{{PREVIOUS_CHECKPOINT}}", prevContext)
|
|
91
|
+
.replace("{{GIT_DIFF}}", gitDiffSinceCheckpoint || "No file changes since last checkpoint.")
|
|
92
|
+
.replace("{{TRANSCRIPT}}", transcript);
|
|
60
93
|
// Call LLM
|
|
61
94
|
const result = await callClaudeJson(prompt, "haiku", 30000);
|
|
62
95
|
// Save checkpoint
|
|
63
|
-
const dbPath = getDefaultDbPath();
|
|
64
|
-
const db = await initDatabase(dbPath);
|
|
65
|
-
const projectPath = process.cwd();
|
|
66
96
|
const sessionId = new Date().toISOString();
|
|
67
97
|
const uncommittedFiles = status
|
|
68
98
|
? status.split("\n").map((l) => l.trim())
|
|
@@ -74,6 +104,7 @@ export async function handleAutoCheckpoint() {
|
|
|
74
104
|
uncommittedFiles,
|
|
75
105
|
gitSnapshot: log || undefined,
|
|
76
106
|
planReference: result.plan_reference || undefined,
|
|
107
|
+
source: "auto",
|
|
77
108
|
}, dbPath);
|
|
78
109
|
console.error("[claude-baton] Auto-checkpoint saved before compaction");
|
|
79
110
|
}
|
|
@@ -94,29 +125,76 @@ export async function handleSetup() {
|
|
|
94
125
|
settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
95
126
|
}
|
|
96
127
|
catch {
|
|
97
|
-
settings
|
|
128
|
+
console.error("Error: could not parse ~/.claude/settings.json. Fix the file manually and re-run setup.");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Register MCP server via `claude mcp add` (the correct way for Claude Code
|
|
133
|
+
// to discover servers). Uses --scope user for cross-project availability.
|
|
134
|
+
const serverScript = path.resolve(__dirname, "..", "bin", "claude-baton.js");
|
|
135
|
+
try {
|
|
136
|
+
// Remove first (idempotent) then add — avoids "already exists" errors
|
|
137
|
+
execSync("claude mcp remove claude-baton -s user 2>/dev/null || true", {
|
|
138
|
+
encoding: "utf-8",
|
|
139
|
+
timeout: 10000,
|
|
140
|
+
});
|
|
141
|
+
execSync(`claude mcp add -s user claude-baton -- node ${serverScript} serve`, { encoding: "utf-8", timeout: 10000 });
|
|
142
|
+
console.error(" Registered MCP server (user scope)");
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
console.error(" Warning: could not register MCP server via 'claude mcp add'.");
|
|
146
|
+
console.error(" Ensure Claude Code CLI is installed. You can manually run:");
|
|
147
|
+
console.error(` claude mcp add -s user claude-baton -- node ${serverScript} serve`);
|
|
148
|
+
}
|
|
149
|
+
// Clean up legacy mcpServers from settings.json if present
|
|
150
|
+
if (settings.mcpServers &&
|
|
151
|
+
typeof settings.mcpServers === "object" &&
|
|
152
|
+
settings.mcpServers["claude-baton"]) {
|
|
153
|
+
delete settings.mcpServers["claude-baton"];
|
|
154
|
+
if (Object.keys(settings.mcpServers).length === 0) {
|
|
155
|
+
delete settings.mcpServers;
|
|
98
156
|
}
|
|
99
157
|
}
|
|
100
|
-
// Register MCP server
|
|
101
|
-
const mcpServers = (settings.mcpServers ?? {});
|
|
102
|
-
mcpServers["claude-baton"] = {
|
|
103
|
-
command: "npx",
|
|
104
|
-
args: ["-y", "claude-baton", "serve"],
|
|
105
|
-
};
|
|
106
|
-
settings.mcpServers = mcpServers;
|
|
107
158
|
// Register PreCompact hook (idempotent — skip if already present)
|
|
108
159
|
const hooks = (settings.hooks ?? {});
|
|
109
160
|
const preCompactHooks = (hooks.PreCompact ?? []);
|
|
110
|
-
const
|
|
111
|
-
|
|
161
|
+
const hasBatonHook = preCompactHooks.some((h) => Array.isArray(h.hooks) &&
|
|
162
|
+
h.hooks.some((hook) => hook.command?.includes("claude-baton")));
|
|
163
|
+
if (!hasBatonHook) {
|
|
164
|
+
const autoCheckpointBin = path.resolve(__dirname, "..", "bin", "claude-baton.js");
|
|
112
165
|
preCompactHooks.push({
|
|
113
|
-
|
|
114
|
-
|
|
166
|
+
matcher: "",
|
|
167
|
+
hooks: [
|
|
168
|
+
{
|
|
169
|
+
type: "command",
|
|
170
|
+
command: `node ${autoCheckpointBin} auto-checkpoint`,
|
|
171
|
+
},
|
|
172
|
+
],
|
|
115
173
|
});
|
|
116
174
|
hooks.PreCompact = preCompactHooks;
|
|
117
175
|
settings.hooks = hooks;
|
|
118
176
|
console.error(" Registered PreCompact hook");
|
|
119
177
|
}
|
|
178
|
+
// Register allowed tools for frictionless slash commands (idempotent)
|
|
179
|
+
const BATON_TOOLS = [
|
|
180
|
+
"Bash(git status*)",
|
|
181
|
+
"Bash(git log*)",
|
|
182
|
+
"Bash(git diff*)",
|
|
183
|
+
"Bash(git branch*)",
|
|
184
|
+
"Bash(node *claude-baton*)",
|
|
185
|
+
];
|
|
186
|
+
const allowedTools = (settings.allowedTools ?? []);
|
|
187
|
+
let toolsAdded = 0;
|
|
188
|
+
for (const tool of BATON_TOOLS) {
|
|
189
|
+
if (!allowedTools.includes(tool)) {
|
|
190
|
+
allowedTools.push(tool);
|
|
191
|
+
toolsAdded++;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (toolsAdded > 0) {
|
|
195
|
+
settings.allowedTools = allowedTools;
|
|
196
|
+
console.error(` Registered ${toolsAdded} allowed tools`);
|
|
197
|
+
}
|
|
120
198
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
121
199
|
const dbPath = getDefaultDbPath();
|
|
122
200
|
await initDatabase(dbPath);
|
|
@@ -152,12 +230,22 @@ export function installCommands() {
|
|
|
152
230
|
}
|
|
153
231
|
// --- Uninstall command ---
|
|
154
232
|
export async function handleUninstall(opts) {
|
|
155
|
-
// 1. Remove MCP server
|
|
233
|
+
// 1. Remove MCP server via claude CLI + clean up settings.json
|
|
234
|
+
try {
|
|
235
|
+
execSync("claude mcp remove claude-baton -s user 2>/dev/null || true", {
|
|
236
|
+
encoding: "utf-8",
|
|
237
|
+
timeout: 10000,
|
|
238
|
+
});
|
|
239
|
+
console.error(" Removed MCP server");
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
console.error(" Warning: could not remove MCP server via CLI");
|
|
243
|
+
}
|
|
156
244
|
const settingsPath = path.join(os.homedir(), ".claude", "settings.json");
|
|
157
245
|
if (existsSync(settingsPath)) {
|
|
158
246
|
try {
|
|
159
247
|
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
160
|
-
// Remove MCP server
|
|
248
|
+
// Remove legacy MCP server from settings.json if present
|
|
161
249
|
if (settings.mcpServers &&
|
|
162
250
|
typeof settings.mcpServers === "object" &&
|
|
163
251
|
settings.mcpServers["claude-baton"]) {
|
|
@@ -165,7 +253,6 @@ export async function handleUninstall(opts) {
|
|
|
165
253
|
if (Object.keys(settings.mcpServers).length === 0) {
|
|
166
254
|
delete settings.mcpServers;
|
|
167
255
|
}
|
|
168
|
-
console.error(" Removed MCP server from settings.json");
|
|
169
256
|
}
|
|
170
257
|
// Remove PreCompact hook
|
|
171
258
|
if (settings.hooks &&
|
|
@@ -173,7 +260,8 @@ export async function handleUninstall(opts) {
|
|
|
173
260
|
settings.hooks.PreCompact) {
|
|
174
261
|
const hooksObj = settings.hooks;
|
|
175
262
|
const preCompact = hooksObj.PreCompact;
|
|
176
|
-
const filtered = preCompact.filter((h) => !h.
|
|
263
|
+
const filtered = preCompact.filter((h) => !Array.isArray(h.hooks) ||
|
|
264
|
+
!h.hooks.some((hook) => hook.command?.includes("claude-baton")));
|
|
177
265
|
if (filtered.length === 0) {
|
|
178
266
|
delete hooksObj.PreCompact;
|
|
179
267
|
}
|
|
@@ -185,6 +273,21 @@ export async function handleUninstall(opts) {
|
|
|
185
273
|
}
|
|
186
274
|
console.error(" Removed PreCompact hook");
|
|
187
275
|
}
|
|
276
|
+
// Remove allowed tools
|
|
277
|
+
if (Array.isArray(settings.allowedTools)) {
|
|
278
|
+
const BATON_PATTERNS = [
|
|
279
|
+
"Bash(git status*)",
|
|
280
|
+
"Bash(git log*)",
|
|
281
|
+
"Bash(git diff*)",
|
|
282
|
+
"Bash(git branch*)",
|
|
283
|
+
"Bash(node *claude-baton*)",
|
|
284
|
+
];
|
|
285
|
+
settings.allowedTools = settings.allowedTools.filter((t) => !BATON_PATTERNS.includes(t));
|
|
286
|
+
if (settings.allowedTools.length === 0) {
|
|
287
|
+
delete settings.allowedTools;
|
|
288
|
+
}
|
|
289
|
+
console.error(" Removed allowed tools");
|
|
290
|
+
}
|
|
188
291
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
189
292
|
}
|
|
190
293
|
catch {
|
|
@@ -229,7 +332,7 @@ export async function handleStatus(opts) {
|
|
|
229
332
|
return;
|
|
230
333
|
}
|
|
231
334
|
const db = await initDatabase(dbPath);
|
|
232
|
-
const projectPath = opts.project ?? process.cwd();
|
|
335
|
+
const projectPath = opts.project ?? normalizeProjectPath(process.cwd());
|
|
233
336
|
const counts = countAll(db, projectPath);
|
|
234
337
|
const dbSize = statSync(dbPath).size;
|
|
235
338
|
console.log(`Project: ${projectPath}`);
|
|
@@ -289,8 +392,17 @@ export async function handleImport(file) {
|
|
|
289
392
|
return;
|
|
290
393
|
}
|
|
291
394
|
let imported = 0;
|
|
395
|
+
let skipped = 0;
|
|
292
396
|
if (Array.isArray(data.checkpoints)) {
|
|
293
397
|
for (const cp of data.checkpoints) {
|
|
398
|
+
if (!cp.project_path ||
|
|
399
|
+
!cp.session_id ||
|
|
400
|
+
!cp.current_state ||
|
|
401
|
+
!cp.what_was_built ||
|
|
402
|
+
!cp.next_steps) {
|
|
403
|
+
skipped++;
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
294
406
|
insertCheckpoint(db, cp.project_path, cp.session_id, cp.current_state, cp.what_was_built, cp.next_steps, {
|
|
295
407
|
branch: cp.branch,
|
|
296
408
|
decisionsMade: cp.decisions_made,
|
|
@@ -302,7 +414,7 @@ export async function handleImport(file) {
|
|
|
302
414
|
}
|
|
303
415
|
}
|
|
304
416
|
saveDatabase(db, dbPath);
|
|
305
|
-
console.error(`Imported ${imported} items.`);
|
|
417
|
+
console.error(`Imported ${imported} items.${skipped ? ` Skipped ${skipped} malformed.` : ""}`);
|
|
306
418
|
}
|
|
307
419
|
// --- Reset command ---
|
|
308
420
|
export async function handleReset(opts) {
|
package/dist/index.js
CHANGED
|
@@ -3,10 +3,12 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
3
3
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
4
|
import { initDatabase, getDefaultDbPath, insertCheckpoint, getLatestCheckpoint, getCheckpoint, getCheckpointsByDate, insertDailySummary, } from "./store.js";
|
|
5
5
|
import { callClaudeJson } from "./llm.js";
|
|
6
|
+
import { normalizeProjectPath } from "./utils.js";
|
|
6
7
|
import { readFileSync, statSync } from "fs";
|
|
7
8
|
import path from "path";
|
|
8
9
|
import { fileURLToPath } from "url";
|
|
9
10
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const pkg = JSON.parse(readFileSync(path.join(__dirname, "..", "package.json"), "utf-8"));
|
|
10
12
|
let db;
|
|
11
13
|
let dbPath;
|
|
12
14
|
let lastDbMtime = 0;
|
|
@@ -25,13 +27,6 @@ async function reloadDbIfChanged() {
|
|
|
25
27
|
}
|
|
26
28
|
/** Stable fallback session ID — generated once at module load, not per call. */
|
|
27
29
|
const fallbackSessionId = `session-${Date.now()}`;
|
|
28
|
-
/** Build an MCP error response with a descriptive message. */
|
|
29
|
-
function toolError(message) {
|
|
30
|
-
return {
|
|
31
|
-
content: [{ type: "text", text: `Error: ${message}` }],
|
|
32
|
-
isError: true,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
30
|
/**
|
|
36
31
|
* Validate that a required string argument is present and is a string.
|
|
37
32
|
* Returns the validated string, or throws with a descriptive message.
|
|
@@ -50,7 +45,7 @@ class ValidationError extends Error {
|
|
|
50
45
|
this.name = "ValidationError";
|
|
51
46
|
}
|
|
52
47
|
}
|
|
53
|
-
const server = new Server({ name: "claude-baton", version:
|
|
48
|
+
const server = new Server({ name: "claude-baton", version: pkg.version }, { capabilities: { tools: {} } });
|
|
54
49
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
55
50
|
tools: [
|
|
56
51
|
{
|
|
@@ -81,6 +76,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
81
76
|
type: "string",
|
|
82
77
|
description: "Reference to active plan document and section, e.g. 'docs/plan.md Phase 2 Step 3'",
|
|
83
78
|
},
|
|
79
|
+
source: {
|
|
80
|
+
type: "string",
|
|
81
|
+
enum: ["manual", "auto"],
|
|
82
|
+
description: "Checkpoint source. Defaults to manual.",
|
|
83
|
+
},
|
|
84
84
|
project: {
|
|
85
85
|
type: "string",
|
|
86
86
|
description: "Project path (defaults to cwd)",
|
|
@@ -144,7 +144,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
144
144
|
}));
|
|
145
145
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
146
146
|
const { name, arguments: args } = request.params;
|
|
147
|
-
const projectPath = args?.project ?? process.cwd();
|
|
147
|
+
const projectPath = args?.project ?? normalizeProjectPath(process.cwd());
|
|
148
148
|
await reloadDbIfChanged();
|
|
149
149
|
try {
|
|
150
150
|
switch (name) {
|
|
@@ -161,6 +161,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
161
161
|
uncommittedFiles: a?.uncommitted_files,
|
|
162
162
|
gitSnapshot: a?.git_snapshot,
|
|
163
163
|
planReference: a?.plan_reference,
|
|
164
|
+
source: a?.source ?? "manual",
|
|
164
165
|
}, dbPath);
|
|
165
166
|
return { content: [{ type: "text", text: `Checkpoint saved: ${id}` }] };
|
|
166
167
|
}
|
package/dist/llm.js
CHANGED
|
@@ -49,7 +49,7 @@ function callClaudeRaw(prompt, model = "haiku", timeout = 30000) {
|
|
|
49
49
|
if (settled)
|
|
50
50
|
return;
|
|
51
51
|
settled = true;
|
|
52
|
-
if (process.env.
|
|
52
|
+
if (process.env.CLAUDE_BATON_DEBUG) {
|
|
53
53
|
console.error(`[DEBUG] claude -p exit=${code} stdout=${stdout.slice(0, 500)} stderr=${stderr.slice(0, 200)}`);
|
|
54
54
|
}
|
|
55
55
|
if (code !== 0) {
|
|
@@ -65,7 +65,7 @@ function callClaudeRaw(prompt, model = "haiku", timeout = 30000) {
|
|
|
65
65
|
settled = true;
|
|
66
66
|
reject(new Error(`Failed to spawn claude: ${err.message}`));
|
|
67
67
|
});
|
|
68
|
-
if (process.env.
|
|
68
|
+
if (process.env.CLAUDE_BATON_DEBUG) {
|
|
69
69
|
console.error(`[DEBUG] Prompt length: ${prompt.length}, first 300 chars: ${prompt.slice(0, 300)}`);
|
|
70
70
|
}
|
|
71
71
|
proc.stdin.write(prompt);
|
package/dist/store.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare function insertCheckpoint(db: Database, projectPath: string, sess
|
|
|
11
11
|
uncommittedFiles?: string[];
|
|
12
12
|
gitSnapshot?: string;
|
|
13
13
|
planReference?: string;
|
|
14
|
+
source?: "manual" | "auto";
|
|
14
15
|
}, dbPath?: string): string;
|
|
15
16
|
export declare function getCheckpoint(db: Database, id: string): Checkpoint | null;
|
|
16
17
|
export declare function getLatestCheckpoint(db: Database, projectPath: string): Checkpoint | null;
|
package/dist/store.js
CHANGED
|
@@ -45,6 +45,7 @@ export function initSchema(db) {
|
|
|
45
45
|
uncommitted_files TEXT DEFAULT '[]',
|
|
46
46
|
git_snapshot TEXT,
|
|
47
47
|
plan_reference TEXT,
|
|
48
|
+
source TEXT DEFAULT 'manual',
|
|
48
49
|
created_at TEXT DEFAULT (datetime('now'))
|
|
49
50
|
);
|
|
50
51
|
CREATE INDEX IF NOT EXISTS idx_checkpoints_project ON checkpoints(project_path);
|
|
@@ -64,22 +65,35 @@ export function initSchema(db) {
|
|
|
64
65
|
try {
|
|
65
66
|
db.exec("ALTER TABLE checkpoints ADD COLUMN git_snapshot TEXT");
|
|
66
67
|
}
|
|
67
|
-
catch {
|
|
68
|
-
|
|
68
|
+
catch (e) {
|
|
69
|
+
const msg = e instanceof Error ? e.message : "";
|
|
70
|
+
if (!msg.includes("duplicate column"))
|
|
71
|
+
throw e;
|
|
69
72
|
}
|
|
70
73
|
// Migration: add plan_reference column for existing databases
|
|
71
74
|
try {
|
|
72
75
|
db.exec("ALTER TABLE checkpoints ADD COLUMN plan_reference TEXT");
|
|
73
76
|
}
|
|
74
|
-
catch {
|
|
75
|
-
|
|
77
|
+
catch (e) {
|
|
78
|
+
const msg = e instanceof Error ? e.message : "";
|
|
79
|
+
if (!msg.includes("duplicate column"))
|
|
80
|
+
throw e;
|
|
81
|
+
}
|
|
82
|
+
// Migration: add source column for existing databases
|
|
83
|
+
try {
|
|
84
|
+
db.exec("ALTER TABLE checkpoints ADD COLUMN source TEXT DEFAULT 'manual'");
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
const msg = e instanceof Error ? e.message : "";
|
|
88
|
+
if (!msg.includes("duplicate column"))
|
|
89
|
+
throw e;
|
|
76
90
|
}
|
|
77
91
|
}
|
|
78
92
|
// --- Checkpoints CRUD ---
|
|
79
93
|
export function insertCheckpoint(db, projectPath, sessionId, currentState, whatWasBuilt, nextSteps, opts, dbPath) {
|
|
80
94
|
const id = crypto.randomUUID();
|
|
81
|
-
db.run(`INSERT INTO checkpoints (id, project_path, session_id, branch, current_state, what_was_built, next_steps, decisions_made, blockers, uncommitted_files, git_snapshot, plan_reference, created_at)
|
|
82
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
95
|
+
db.run(`INSERT INTO checkpoints (id, project_path, session_id, branch, current_state, what_was_built, next_steps, decisions_made, blockers, uncommitted_files, git_snapshot, plan_reference, source, created_at)
|
|
96
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
83
97
|
id,
|
|
84
98
|
projectPath,
|
|
85
99
|
sessionId,
|
|
@@ -92,6 +106,7 @@ export function insertCheckpoint(db, projectPath, sessionId, currentState, whatW
|
|
|
92
106
|
JSON.stringify(opts?.uncommittedFiles ?? []),
|
|
93
107
|
opts?.gitSnapshot ?? null,
|
|
94
108
|
opts?.planReference ?? null,
|
|
109
|
+
opts?.source ?? "manual",
|
|
95
110
|
new Date().toISOString(),
|
|
96
111
|
]);
|
|
97
112
|
if (dbPath)
|
|
@@ -122,11 +137,16 @@ function parseCheckpointRow(row) {
|
|
|
122
137
|
uncommitted_files: JSON.parse(row.uncommitted_files),
|
|
123
138
|
git_snapshot: row.git_snapshot ?? null,
|
|
124
139
|
plan_reference: row.plan_reference ?? null,
|
|
140
|
+
source: row.source ?? "manual",
|
|
125
141
|
};
|
|
126
142
|
}
|
|
127
143
|
export function getCheckpointsByDate(db, projectPath, date) {
|
|
128
|
-
const
|
|
129
|
-
|
|
144
|
+
const startLocal = new Date(`${date}T00:00:00`);
|
|
145
|
+
const endLocal = new Date(`${date}T23:59:59.999`);
|
|
146
|
+
const startUtc = startLocal.toISOString();
|
|
147
|
+
const endUtc = endLocal.toISOString();
|
|
148
|
+
const stmt = db.prepare("SELECT * FROM checkpoints WHERE project_path = ? AND created_at >= ? AND created_at <= ? ORDER BY created_at ASC");
|
|
149
|
+
stmt.bind([projectPath, startUtc, endUtc]);
|
|
130
150
|
const results = [];
|
|
131
151
|
while (stmt.step()) {
|
|
132
152
|
results.push(parseCheckpointRow(stmt.getAsObject()));
|
package/dist/types.d.ts
CHANGED
package/dist/utils.d.ts
CHANGED
package/dist/utils.js
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
|
-
import { mkdirSync,
|
|
1
|
+
import { mkdirSync, realpathSync } from "fs";
|
|
2
|
+
import path from "path";
|
|
2
3
|
// --- Path helpers ---
|
|
3
4
|
export function getProjectPath() {
|
|
4
5
|
return process.cwd();
|
|
5
6
|
}
|
|
6
|
-
export function
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
export function normalizeProjectPath(p) {
|
|
8
|
+
try {
|
|
9
|
+
const resolved = realpathSync(p);
|
|
10
|
+
return resolved.length > 1 && resolved.endsWith(path.sep)
|
|
11
|
+
? resolved.slice(0, -1)
|
|
12
|
+
: resolved;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
const normalized = path.resolve(p);
|
|
16
|
+
return normalized.length > 1 && normalized.endsWith(path.sep)
|
|
17
|
+
? normalized.slice(0, -1)
|
|
18
|
+
: normalized;
|
|
9
19
|
}
|
|
10
20
|
}
|
|
21
|
+
export function ensureDir(dirPath) {
|
|
22
|
+
mkdirSync(dirPath, { recursive: true });
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,35 @@
|
|
|
1
1
|
You are extracting a session checkpoint from a Claude Code conversation transcript.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
You have three sources of context:
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
PREVIOUS CHECKPOINT:
|
|
6
|
+
{{PREVIOUS_CHECKPOINT}}
|
|
7
|
+
|
|
8
|
+
GIT CHANGES SINCE LAST CHECKPOINT:
|
|
9
|
+
{{GIT_DIFF}}
|
|
10
|
+
|
|
11
|
+
CURRENT TRANSCRIPT:
|
|
12
|
+
{{TRANSCRIPT}}
|
|
13
|
+
|
|
14
|
+
Analyze these inputs and produce a JSON object with these fields:
|
|
15
|
+
|
|
16
|
+
- what_was_built: What was accomplished since the previous checkpoint (1-3 sentences). Focus on what CHANGED — don't repeat already-captured work.
|
|
6
17
|
- current_state: Current state of the project — what's passing, what's broken, what's uncommitted (1-2 sentences)
|
|
7
18
|
- next_steps: What should be done next (1-2 sentences, actionable)
|
|
8
|
-
- decisions_made: Key decisions made
|
|
19
|
+
- decisions_made: Key decisions made since the previous checkpoint, if any (1-2 sentences, or "None")
|
|
9
20
|
- blockers: Anything blocking progress, if any (1 sentence, or "None")
|
|
10
21
|
- plan_reference: If the session references an active plan document (e.g. PLAN.md, docs/plan.md, a spec file), include the file path and the specific section being worked on (e.g. "docs/v2-plan.md Phase 2 Step 3"). Null if no plan document is referenced.
|
|
11
22
|
|
|
12
23
|
Rules:
|
|
13
24
|
- Be concise — each field should be 1-3 sentences max
|
|
14
|
-
- Focus on what
|
|
15
|
-
-
|
|
25
|
+
- Focus on what was COMPLETED in THIS session's transcript, not what was discussed or explored
|
|
26
|
+
- Don't repeat work already captured in the previous checkpoint
|
|
27
|
+
- If the transcript is mostly noise (exploration, context-filling, debugging with no resolution), be honest: "Explored X and Y but no code changes yet"
|
|
28
|
+
- Note file changes from git diff that aren't explained in the transcript
|
|
29
|
+
- next_steps should be the logical next action based on THIS session's work, not inherited from the previous checkpoint
|
|
30
|
+
- blockers should only include blockers that were ACTIVELY ENCOUNTERED in this session. Do NOT carry forward blockers from the previous checkpoint unless the session transcript shows they were hit. If nothing blocked progress, say "None"
|
|
31
|
+
- current_state should lead with what IS working (e.g. "All tests passing, build clean") before mentioning what's not done
|
|
32
|
+
- what_was_built should name specific features, not just describe generic "schema changes" — use the same terminology the transcript uses
|
|
16
33
|
- For plan_reference, look for file reads/references to plan docs, spec files, or roadmap documents in the transcript
|
|
17
34
|
- Output ONLY valid JSON, no markdown, no explanation
|
|
18
35
|
|
|
@@ -24,7 +41,4 @@ Example output:
|
|
|
24
41
|
"decisions_made": "Using bcrypt over argon2 for password hashing due to simpler deployment. Storing refresh tokens in httpOnly cookies.",
|
|
25
42
|
"blockers": "None",
|
|
26
43
|
"plan_reference": "docs/auth-plan.md Phase 2 Step 1"
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
Transcript:
|
|
30
|
-
{TRANSCRIPT}
|
|
44
|
+
}
|
|
@@ -15,7 +15,7 @@ Instructions:
|
|
|
15
15
|
- Do not fabricate information not present in the activity data
|
|
16
16
|
- If the activity data is sparse, reflect that honestly rather than padding
|
|
17
17
|
|
|
18
|
-
Respond with ONLY a valid JSON object matching this shape
|
|
18
|
+
Respond with ONLY a valid JSON object matching this shape — no code fences, no text before or after. Your entire response must be parseable by JSON.parse().
|
|
19
19
|
|
|
20
20
|
{"what_was_built": "...", "decisions_made": ["..."], "blockers": ["..."], "next_steps": ["..."], "highlights": "..."}
|
|
21
21
|
|
|
@@ -25,5 +25,3 @@ Field rules:
|
|
|
25
25
|
- blockers: array of unresolved issues; use [] if none
|
|
26
26
|
- next_steps: array of actionable items for the next session
|
|
27
27
|
- highlights: one-line summary suitable for a changelog or standup
|
|
28
|
-
|
|
29
|
-
Respond with ONLY valid JSON. Do not wrap in code fences. Do not include any text before or after the JSON object. Your entire response must be parseable by JSON.parse().
|