claude-baton 2.0.1
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/LICENSE +21 -0
- package/README.md +115 -0
- package/bin/claude-baton.js +2 -0
- package/commands/memo-checkpoint.md +43 -0
- package/commands/memo-eod.md +42 -0
- package/commands/memo-resume.md +76 -0
- package/dist/cli.d.ts +22 -0
- package/dist/cli.js +392 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +251 -0
- package/dist/llm.d.ts +16 -0
- package/dist/llm.js +119 -0
- package/dist/store.d.ts +28 -0
- package/dist/store.js +249 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +10 -0
- package/package.json +60 -0
- package/prompts/auto_checkpoint.txt +30 -0
- package/prompts/daily_summary.txt +29 -0
package/dist/llm.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import os from "os";
|
|
3
|
+
/**
|
|
4
|
+
* Strip markdown code fences from LLM output.
|
|
5
|
+
* LLMs frequently wrap JSON in ```json ... ``` blocks.
|
|
6
|
+
*/
|
|
7
|
+
export function stripCodeFences(text) {
|
|
8
|
+
const trimmed = text.trim();
|
|
9
|
+
// Match ```<optional language>\n...\n```
|
|
10
|
+
const match = trimmed.match(/^```(?:\w*)\s*\n?([\s\S]*?)\n?\s*```$/);
|
|
11
|
+
if (match) {
|
|
12
|
+
return match[1].trim();
|
|
13
|
+
}
|
|
14
|
+
return trimmed;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Internal: spawns claude -p and returns the raw stdout string.
|
|
18
|
+
* Used by both callClaude and callClaudeJson to avoid double-parse issues.
|
|
19
|
+
*/
|
|
20
|
+
function callClaudeRaw(prompt, model = "haiku", timeout = 30000) {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
let settled = false;
|
|
23
|
+
// Strip Claude Code env vars so nested claude -p works from hooks
|
|
24
|
+
const env = { ...process.env };
|
|
25
|
+
delete env.CLAUDECODE;
|
|
26
|
+
delete env.CLAUDE_CODE_ENTRYPOINT;
|
|
27
|
+
const proc = spawn("claude", ["-p", "--model", model, "--output-format", "json"], {
|
|
28
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
29
|
+
env,
|
|
30
|
+
cwd: os.tmpdir(),
|
|
31
|
+
});
|
|
32
|
+
let stdout = "";
|
|
33
|
+
let stderr = "";
|
|
34
|
+
proc.stdout.on("data", (data) => {
|
|
35
|
+
stdout += data.toString();
|
|
36
|
+
});
|
|
37
|
+
proc.stderr.on("data", (data) => {
|
|
38
|
+
stderr += data.toString();
|
|
39
|
+
});
|
|
40
|
+
const timer = setTimeout(() => {
|
|
41
|
+
if (settled)
|
|
42
|
+
return;
|
|
43
|
+
settled = true;
|
|
44
|
+
proc.kill();
|
|
45
|
+
reject(new Error(`claude -p timed out after ${timeout}ms`));
|
|
46
|
+
}, timeout);
|
|
47
|
+
proc.on("close", (code) => {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
if (settled)
|
|
50
|
+
return;
|
|
51
|
+
settled = true;
|
|
52
|
+
if (process.env.MEMORIA_DEBUG) {
|
|
53
|
+
console.error(`[DEBUG] claude -p exit=${code} stdout=${stdout.slice(0, 500)} stderr=${stderr.slice(0, 200)}`);
|
|
54
|
+
}
|
|
55
|
+
if (code !== 0) {
|
|
56
|
+
reject(new Error(`claude -p exited with code ${code}: ${stderr}`));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
resolve(stdout);
|
|
60
|
+
});
|
|
61
|
+
proc.on("error", (err) => {
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
if (settled)
|
|
64
|
+
return;
|
|
65
|
+
settled = true;
|
|
66
|
+
reject(new Error(`Failed to spawn claude: ${err.message}`));
|
|
67
|
+
});
|
|
68
|
+
if (process.env.MEMORIA_DEBUG) {
|
|
69
|
+
console.error(`[DEBUG] Prompt length: ${prompt.length}, first 300 chars: ${prompt.slice(0, 300)}`);
|
|
70
|
+
}
|
|
71
|
+
proc.stdin.write(prompt);
|
|
72
|
+
proc.stdin.end();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Call claude -p and return the result as a string.
|
|
77
|
+
* If the raw output is JSON with a `result` field, extracts it.
|
|
78
|
+
* Always returns a string — non-string result values are JSON.stringified.
|
|
79
|
+
*/
|
|
80
|
+
export async function callClaude(prompt, model = "haiku", timeout = 30000) {
|
|
81
|
+
const raw = await callClaudeRaw(prompt, model, timeout);
|
|
82
|
+
const stripped = stripCodeFences(raw);
|
|
83
|
+
try {
|
|
84
|
+
const parsed = JSON.parse(stripped);
|
|
85
|
+
const result = parsed.result ?? stripped;
|
|
86
|
+
if (typeof result === "string") {
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
return JSON.stringify(result);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return stripped;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Call claude -p and parse the response as JSON of type T.
|
|
97
|
+
* Uses the raw stdout (not the string-extracted callClaude) to avoid double-parse.
|
|
98
|
+
*/
|
|
99
|
+
export async function callClaudeJson(prompt, model = "haiku", timeout = 30000) {
|
|
100
|
+
const raw = await callClaudeRaw(prompt, model, timeout);
|
|
101
|
+
const stripped = stripCodeFences(raw);
|
|
102
|
+
try {
|
|
103
|
+
const parsed = JSON.parse(stripped);
|
|
104
|
+
// If the output has a `result` field, use that as the JSON value
|
|
105
|
+
const value = parsed.result !== undefined ? parsed.result : parsed;
|
|
106
|
+
// If value is already an object/array, return it directly
|
|
107
|
+
if (typeof value === "object" && value !== null) {
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
// If value is a string, try to parse it as JSON
|
|
111
|
+
if (typeof value === "string") {
|
|
112
|
+
return JSON.parse(stripCodeFences(value));
|
|
113
|
+
}
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
throw new Error(`Failed to parse JSON from claude response: ${stripped.slice(0, 200)}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type Database } from "sql.js";
|
|
2
|
+
import type { Checkpoint, DailySummary } from "./types.js";
|
|
3
|
+
export declare function initDatabase(dbPath?: string): Promise<Database>;
|
|
4
|
+
export declare function saveDatabase(db: Database, dbPath: string): void;
|
|
5
|
+
export declare function getDefaultDbPath(): string;
|
|
6
|
+
export declare function initSchema(db: Database): void;
|
|
7
|
+
export declare function insertCheckpoint(db: Database, projectPath: string, sessionId: string, currentState: string, whatWasBuilt: string, nextSteps: string, opts?: {
|
|
8
|
+
branch?: string;
|
|
9
|
+
decisionsMade?: string;
|
|
10
|
+
blockers?: string;
|
|
11
|
+
uncommittedFiles?: string[];
|
|
12
|
+
gitSnapshot?: string;
|
|
13
|
+
planReference?: string;
|
|
14
|
+
}, dbPath?: string): string;
|
|
15
|
+
export declare function getCheckpoint(db: Database, id: string): Checkpoint | null;
|
|
16
|
+
export declare function getLatestCheckpoint(db: Database, projectPath: string): Checkpoint | null;
|
|
17
|
+
export declare function getCheckpointsByDate(db: Database, projectPath: string, date: string): Checkpoint[];
|
|
18
|
+
export declare function getAllCheckpoints(db: Database, projectPath?: string): Checkpoint[];
|
|
19
|
+
export declare function insertDailySummary(db: Database, projectPath: string, date: string, summary: Record<string, unknown>, dbPath?: string): string;
|
|
20
|
+
export declare function getDailySummary(db: Database, projectPath: string, date: string): DailySummary | null;
|
|
21
|
+
export declare function getAllDailySummaries(db: Database, projectPath?: string): DailySummary[];
|
|
22
|
+
export declare function countAll(db: Database, projectPath?: string): Record<string, number>;
|
|
23
|
+
export declare function listProjects(db: Database): Array<{
|
|
24
|
+
project_path: string;
|
|
25
|
+
checkpoint_count: number;
|
|
26
|
+
}>;
|
|
27
|
+
export declare function deleteProjectData(db: Database, projectPath: string, dbPath?: string): void;
|
|
28
|
+
export declare function deleteAllData(db: Database, dbPath?: string): void;
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import initSqlJs from "sql.js";
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import os from "os";
|
|
5
|
+
import crypto from "crypto";
|
|
6
|
+
// --- Database lifecycle ---
|
|
7
|
+
export async function initDatabase(dbPath) {
|
|
8
|
+
const SQL = await initSqlJs();
|
|
9
|
+
let db;
|
|
10
|
+
if (dbPath && existsSync(dbPath)) {
|
|
11
|
+
const buffer = readFileSync(dbPath);
|
|
12
|
+
db = new SQL.Database(buffer);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
if (dbPath) {
|
|
16
|
+
mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
db = new SQL.Database();
|
|
19
|
+
}
|
|
20
|
+
initSchema(db);
|
|
21
|
+
if (dbPath)
|
|
22
|
+
saveDatabase(db, dbPath);
|
|
23
|
+
return db;
|
|
24
|
+
}
|
|
25
|
+
export function saveDatabase(db, dbPath) {
|
|
26
|
+
const data = db.export();
|
|
27
|
+
const buffer = Buffer.from(data);
|
|
28
|
+
writeFileSync(dbPath, buffer);
|
|
29
|
+
}
|
|
30
|
+
export function getDefaultDbPath() {
|
|
31
|
+
return path.join(os.homedir(), ".claude-baton", "store.db");
|
|
32
|
+
}
|
|
33
|
+
export function initSchema(db) {
|
|
34
|
+
db.exec(`
|
|
35
|
+
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
36
|
+
id TEXT PRIMARY KEY,
|
|
37
|
+
project_path TEXT NOT NULL,
|
|
38
|
+
session_id TEXT NOT NULL,
|
|
39
|
+
branch TEXT,
|
|
40
|
+
current_state TEXT NOT NULL,
|
|
41
|
+
what_was_built TEXT NOT NULL,
|
|
42
|
+
next_steps TEXT NOT NULL,
|
|
43
|
+
decisions_made TEXT,
|
|
44
|
+
blockers TEXT,
|
|
45
|
+
uncommitted_files TEXT DEFAULT '[]',
|
|
46
|
+
git_snapshot TEXT,
|
|
47
|
+
plan_reference TEXT,
|
|
48
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
49
|
+
);
|
|
50
|
+
CREATE INDEX IF NOT EXISTS idx_checkpoints_project ON checkpoints(project_path);
|
|
51
|
+
|
|
52
|
+
CREATE TABLE IF NOT EXISTS daily_summaries (
|
|
53
|
+
id TEXT PRIMARY KEY,
|
|
54
|
+
project_path TEXT NOT NULL,
|
|
55
|
+
date TEXT NOT NULL,
|
|
56
|
+
summary TEXT NOT NULL,
|
|
57
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
58
|
+
);
|
|
59
|
+
CREATE INDEX IF NOT EXISTS idx_daily_summaries_project ON daily_summaries(project_path);
|
|
60
|
+
CREATE INDEX IF NOT EXISTS idx_daily_summaries_date ON daily_summaries(date);
|
|
61
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_daily_summaries_project_date ON daily_summaries(project_path, date);
|
|
62
|
+
`);
|
|
63
|
+
// Migration: add git_snapshot column for existing databases
|
|
64
|
+
try {
|
|
65
|
+
db.exec("ALTER TABLE checkpoints ADD COLUMN git_snapshot TEXT");
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Column already exists — expected for new databases or already-migrated ones
|
|
69
|
+
}
|
|
70
|
+
// Migration: add plan_reference column for existing databases
|
|
71
|
+
try {
|
|
72
|
+
db.exec("ALTER TABLE checkpoints ADD COLUMN plan_reference TEXT");
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Column already exists
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// --- Checkpoints CRUD ---
|
|
79
|
+
export function insertCheckpoint(db, projectPath, sessionId, currentState, whatWasBuilt, nextSteps, opts, dbPath) {
|
|
80
|
+
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
83
|
+
id,
|
|
84
|
+
projectPath,
|
|
85
|
+
sessionId,
|
|
86
|
+
opts?.branch ?? null,
|
|
87
|
+
currentState,
|
|
88
|
+
whatWasBuilt,
|
|
89
|
+
nextSteps,
|
|
90
|
+
opts?.decisionsMade ?? null,
|
|
91
|
+
opts?.blockers ?? null,
|
|
92
|
+
JSON.stringify(opts?.uncommittedFiles ?? []),
|
|
93
|
+
opts?.gitSnapshot ?? null,
|
|
94
|
+
opts?.planReference ?? null,
|
|
95
|
+
new Date().toISOString(),
|
|
96
|
+
]);
|
|
97
|
+
if (dbPath)
|
|
98
|
+
saveDatabase(db, dbPath);
|
|
99
|
+
return id;
|
|
100
|
+
}
|
|
101
|
+
export function getCheckpoint(db, id) {
|
|
102
|
+
const stmt = db.prepare("SELECT * FROM checkpoints WHERE id = ?");
|
|
103
|
+
stmt.bind([id]);
|
|
104
|
+
const row = stmt.step() ? stmt.getAsObject() : null;
|
|
105
|
+
stmt.free();
|
|
106
|
+
if (!row)
|
|
107
|
+
return null;
|
|
108
|
+
return parseCheckpointRow(row);
|
|
109
|
+
}
|
|
110
|
+
export function getLatestCheckpoint(db, projectPath) {
|
|
111
|
+
const stmt = db.prepare("SELECT * FROM checkpoints WHERE project_path = ? ORDER BY created_at DESC, rowid DESC LIMIT 1");
|
|
112
|
+
stmt.bind([projectPath]);
|
|
113
|
+
const row = stmt.step() ? stmt.getAsObject() : null;
|
|
114
|
+
stmt.free();
|
|
115
|
+
if (!row)
|
|
116
|
+
return null;
|
|
117
|
+
return parseCheckpointRow(row);
|
|
118
|
+
}
|
|
119
|
+
function parseCheckpointRow(row) {
|
|
120
|
+
return {
|
|
121
|
+
...row,
|
|
122
|
+
uncommitted_files: JSON.parse(row.uncommitted_files),
|
|
123
|
+
git_snapshot: row.git_snapshot ?? null,
|
|
124
|
+
plan_reference: row.plan_reference ?? null,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
export function getCheckpointsByDate(db, projectPath, date) {
|
|
128
|
+
const stmt = db.prepare("SELECT * FROM checkpoints WHERE project_path = ? AND created_at LIKE ? || '%' ORDER BY created_at ASC");
|
|
129
|
+
stmt.bind([projectPath, date]);
|
|
130
|
+
const results = [];
|
|
131
|
+
while (stmt.step()) {
|
|
132
|
+
results.push(parseCheckpointRow(stmt.getAsObject()));
|
|
133
|
+
}
|
|
134
|
+
stmt.free();
|
|
135
|
+
return results;
|
|
136
|
+
}
|
|
137
|
+
export function getAllCheckpoints(db, projectPath) {
|
|
138
|
+
let sql = "SELECT * FROM checkpoints";
|
|
139
|
+
const params = [];
|
|
140
|
+
if (projectPath) {
|
|
141
|
+
sql += " WHERE project_path = ?";
|
|
142
|
+
params.push(projectPath);
|
|
143
|
+
}
|
|
144
|
+
sql += " ORDER BY created_at DESC";
|
|
145
|
+
const stmt = db.prepare(sql);
|
|
146
|
+
stmt.bind(params);
|
|
147
|
+
const results = [];
|
|
148
|
+
while (stmt.step()) {
|
|
149
|
+
results.push(parseCheckpointRow(stmt.getAsObject()));
|
|
150
|
+
}
|
|
151
|
+
stmt.free();
|
|
152
|
+
return results;
|
|
153
|
+
}
|
|
154
|
+
// --- Daily Summaries CRUD ---
|
|
155
|
+
export function insertDailySummary(db, projectPath, date, summary, dbPath) {
|
|
156
|
+
const existing = getDailySummary(db, projectPath, date);
|
|
157
|
+
if (existing) {
|
|
158
|
+
db.run("UPDATE daily_summaries SET summary = ? WHERE id = ?", [
|
|
159
|
+
JSON.stringify(summary),
|
|
160
|
+
existing.id,
|
|
161
|
+
]);
|
|
162
|
+
if (dbPath)
|
|
163
|
+
saveDatabase(db, dbPath);
|
|
164
|
+
return existing.id;
|
|
165
|
+
}
|
|
166
|
+
const id = crypto.randomUUID();
|
|
167
|
+
db.run(`INSERT INTO daily_summaries (id, project_path, date, summary, created_at)
|
|
168
|
+
VALUES (?, ?, ?, ?, ?)`, [id, projectPath, date, JSON.stringify(summary), new Date().toISOString()]);
|
|
169
|
+
if (dbPath)
|
|
170
|
+
saveDatabase(db, dbPath);
|
|
171
|
+
return id;
|
|
172
|
+
}
|
|
173
|
+
export function getDailySummary(db, projectPath, date) {
|
|
174
|
+
const stmt = db.prepare("SELECT * FROM daily_summaries WHERE project_path = ? AND date = ?");
|
|
175
|
+
stmt.bind([projectPath, date]);
|
|
176
|
+
const row = stmt.step() ? stmt.getAsObject() : null;
|
|
177
|
+
stmt.free();
|
|
178
|
+
if (!row)
|
|
179
|
+
return null;
|
|
180
|
+
return parseDailySummaryRow(row);
|
|
181
|
+
}
|
|
182
|
+
function parseDailySummaryRow(row) {
|
|
183
|
+
return { ...row, summary: JSON.parse(row.summary) };
|
|
184
|
+
}
|
|
185
|
+
export function getAllDailySummaries(db, projectPath) {
|
|
186
|
+
let sql = "SELECT * FROM daily_summaries";
|
|
187
|
+
const params = [];
|
|
188
|
+
if (projectPath) {
|
|
189
|
+
sql += " WHERE project_path = ?";
|
|
190
|
+
params.push(projectPath);
|
|
191
|
+
}
|
|
192
|
+
sql += " ORDER BY date DESC";
|
|
193
|
+
const stmt = db.prepare(sql);
|
|
194
|
+
stmt.bind(params);
|
|
195
|
+
const results = [];
|
|
196
|
+
while (stmt.step()) {
|
|
197
|
+
results.push(parseDailySummaryRow(stmt.getAsObject()));
|
|
198
|
+
}
|
|
199
|
+
stmt.free();
|
|
200
|
+
return results;
|
|
201
|
+
}
|
|
202
|
+
// --- Aggregate queries ---
|
|
203
|
+
function countTable(db, sql, params) {
|
|
204
|
+
const stmt = db.prepare(sql);
|
|
205
|
+
stmt.bind(params);
|
|
206
|
+
stmt.step();
|
|
207
|
+
const count = stmt.getAsObject().count ?? 0;
|
|
208
|
+
stmt.free();
|
|
209
|
+
return count;
|
|
210
|
+
}
|
|
211
|
+
export function countAll(db, projectPath) {
|
|
212
|
+
if (projectPath) {
|
|
213
|
+
const p = [projectPath];
|
|
214
|
+
return {
|
|
215
|
+
checkpoints: countTable(db, "SELECT COUNT(*) as count FROM checkpoints WHERE project_path = ?", p),
|
|
216
|
+
daily_summaries: countTable(db, "SELECT COUNT(*) as count FROM daily_summaries WHERE project_path = ?", p),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
checkpoints: countTable(db, "SELECT COUNT(*) as count FROM checkpoints", []),
|
|
221
|
+
daily_summaries: countTable(db, "SELECT COUNT(*) as count FROM daily_summaries", []),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
export function listProjects(db) {
|
|
225
|
+
const stmt = db.prepare("SELECT project_path, COUNT(*) as count FROM checkpoints GROUP BY project_path ORDER BY count DESC");
|
|
226
|
+
const results = [];
|
|
227
|
+
while (stmt.step()) {
|
|
228
|
+
const row = stmt.getAsObject();
|
|
229
|
+
results.push({
|
|
230
|
+
project_path: row.project_path,
|
|
231
|
+
checkpoint_count: row.count,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
stmt.free();
|
|
235
|
+
return results;
|
|
236
|
+
}
|
|
237
|
+
// --- Delete operations ---
|
|
238
|
+
export function deleteProjectData(db, projectPath, dbPath) {
|
|
239
|
+
db.run("DELETE FROM checkpoints WHERE project_path = ?", [projectPath]);
|
|
240
|
+
db.run("DELETE FROM daily_summaries WHERE project_path = ?", [projectPath]);
|
|
241
|
+
if (dbPath)
|
|
242
|
+
saveDatabase(db, dbPath);
|
|
243
|
+
}
|
|
244
|
+
export function deleteAllData(db, dbPath) {
|
|
245
|
+
db.run("DELETE FROM checkpoints");
|
|
246
|
+
db.run("DELETE FROM daily_summaries");
|
|
247
|
+
if (dbPath)
|
|
248
|
+
saveDatabase(db, dbPath);
|
|
249
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** A snapshot of session state saved before /compact or /clear. */
|
|
2
|
+
export interface Checkpoint {
|
|
3
|
+
id: string;
|
|
4
|
+
project_path: string;
|
|
5
|
+
session_id: string;
|
|
6
|
+
branch: string | null;
|
|
7
|
+
current_state: string;
|
|
8
|
+
what_was_built: string;
|
|
9
|
+
next_steps: string;
|
|
10
|
+
decisions_made: string | null;
|
|
11
|
+
blockers: string | null;
|
|
12
|
+
uncommitted_files: string[];
|
|
13
|
+
git_snapshot: string | null;
|
|
14
|
+
plan_reference: string | null;
|
|
15
|
+
created_at: string;
|
|
16
|
+
}
|
|
17
|
+
/** An aggregated end-of-day summary of project activity. */
|
|
18
|
+
export interface DailySummary {
|
|
19
|
+
id: string;
|
|
20
|
+
project_path: string;
|
|
21
|
+
date: string;
|
|
22
|
+
summary: Record<string, unknown>;
|
|
23
|
+
created_at: string;
|
|
24
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils.d.ts
ADDED
package/dist/utils.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-baton",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "Session lifecycle management for Claude Code",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"claude-baton": "bin/claude-baton.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"bin",
|
|
14
|
+
"prompts",
|
|
15
|
+
"commands",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mcp",
|
|
21
|
+
"claude",
|
|
22
|
+
"claude-code",
|
|
23
|
+
"session",
|
|
24
|
+
"checkpoint",
|
|
25
|
+
"resume",
|
|
26
|
+
"lifecycle"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"prepare": "tsc",
|
|
33
|
+
"build": "tsc",
|
|
34
|
+
"test": "vitest run",
|
|
35
|
+
"dev": "node dist/index.js",
|
|
36
|
+
"lint": "tsc --noEmit",
|
|
37
|
+
"format:check": "npx prettier --check 'src/**/*.ts' 'tests/**/*.ts'",
|
|
38
|
+
"format": "npx prettier --write 'src/**/*.ts' 'tests/**/*.ts'"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
42
|
+
"commander": "^13.1.0",
|
|
43
|
+
"sql.js": "^1.12.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^22.15.3",
|
|
47
|
+
"typescript": "^5.8.3",
|
|
48
|
+
"vitest": "^3.1.2"
|
|
49
|
+
},
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"author": "bakabaka91",
|
|
52
|
+
"repository": {
|
|
53
|
+
"type": "git",
|
|
54
|
+
"url": "git+https://github.com/bakabaka91/claude-baton.git"
|
|
55
|
+
},
|
|
56
|
+
"homepage": "https://github.com/bakabaka91/claude-baton#readme",
|
|
57
|
+
"bugs": {
|
|
58
|
+
"url": "https://github.com/bakabaka91/claude-baton/issues"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
You are extracting a session checkpoint from a Claude Code conversation transcript.
|
|
2
|
+
|
|
3
|
+
Analyze the transcript and produce a JSON object with these fields:
|
|
4
|
+
|
|
5
|
+
- what_was_built: What was accomplished in this session (1-3 sentences)
|
|
6
|
+
- current_state: Current state of the project — what's passing, what's broken, what's uncommitted (1-2 sentences)
|
|
7
|
+
- next_steps: What should be done next (1-2 sentences, actionable)
|
|
8
|
+
- decisions_made: Key decisions made during this session, if any (1-2 sentences, or "None")
|
|
9
|
+
- blockers: Anything blocking progress, if any (1 sentence, or "None")
|
|
10
|
+
- 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
|
+
|
|
12
|
+
Rules:
|
|
13
|
+
- Be concise — each field should be 1-3 sentences max
|
|
14
|
+
- Focus on what changed, not what was discussed
|
|
15
|
+
- next_steps should be specific enough to resume work
|
|
16
|
+
- For plan_reference, look for file reads/references to plan docs, spec files, or roadmap documents in the transcript
|
|
17
|
+
- Output ONLY valid JSON, no markdown, no explanation
|
|
18
|
+
|
|
19
|
+
Example output:
|
|
20
|
+
{
|
|
21
|
+
"what_was_built": "Added user authentication with JWT tokens and password hashing.",
|
|
22
|
+
"current_state": "Build passing, 45 tests passing. Auth routes implemented but not yet integrated with frontend.",
|
|
23
|
+
"next_steps": "Wire up login form to POST /auth/login endpoint. Add token refresh middleware.",
|
|
24
|
+
"decisions_made": "Using bcrypt over argon2 for password hashing due to simpler deployment. Storing refresh tokens in httpOnly cookies.",
|
|
25
|
+
"blockers": "None",
|
|
26
|
+
"plan_reference": "docs/auth-plan.md Phase 2 Step 1"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
Transcript:
|
|
30
|
+
{TRANSCRIPT}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
You are a daily summary agent. Given a day's activity data, produce a structured end-of-day summary.
|
|
2
|
+
|
|
3
|
+
Date: {{DATE}}
|
|
4
|
+
|
|
5
|
+
Activity data:
|
|
6
|
+
{{ACTIVITY}}
|
|
7
|
+
|
|
8
|
+
Instructions:
|
|
9
|
+
- Summarize what was accomplished during the day based on the activity data
|
|
10
|
+
- Identify key decisions that were made and their rationale
|
|
11
|
+
- Note any blockers or unresolved issues that need attention
|
|
12
|
+
- Suggest concrete next steps based on the day's progress
|
|
13
|
+
- Write a single quotable highlight line that captures the day's essence
|
|
14
|
+
- Be concise — each field should capture signal, not noise
|
|
15
|
+
- Do not fabricate information not present in the activity data
|
|
16
|
+
- If the activity data is sparse, reflect that honestly rather than padding
|
|
17
|
+
|
|
18
|
+
Respond with ONLY a valid JSON object matching this shape:
|
|
19
|
+
|
|
20
|
+
{"what_was_built": "...", "decisions_made": ["..."], "blockers": ["..."], "next_steps": ["..."], "highlights": "..."}
|
|
21
|
+
|
|
22
|
+
Field rules:
|
|
23
|
+
- what_was_built: 1-3 sentence summary of concrete accomplishments
|
|
24
|
+
- decisions_made: array of short strings, each describing one decision and why
|
|
25
|
+
- blockers: array of unresolved issues; use [] if none
|
|
26
|
+
- next_steps: array of actionable items for the next session
|
|
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().
|