@samjurnio_/memento 0.2.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/LICENSE +202 -0
- package/README.md +144 -0
- package/dist/adapters/antigravity.d.ts +19 -0
- package/dist/adapters/antigravity.js +463 -0
- package/dist/adapters/antigravity.js.map +1 -0
- package/dist/adapters/claude-code.d.ts +14 -0
- package/dist/adapters/claude-code.js +213 -0
- package/dist/adapters/claude-code.js.map +1 -0
- package/dist/adapters/codex.d.ts +12 -0
- package/dist/adapters/codex.js +247 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/gemini.d.ts +10 -0
- package/dist/adapters/gemini.js +171 -0
- package/dist/adapters/gemini.js.map +1 -0
- package/dist/adapters/opencode.d.ts +3 -0
- package/dist/adapters/opencode.js +137 -0
- package/dist/adapters/opencode.js.map +1 -0
- package/dist/adapters/pi.d.ts +6 -0
- package/dist/adapters/pi.js +172 -0
- package/dist/adapters/pi.js.map +1 -0
- package/dist/artifacts.d.ts +25 -0
- package/dist/artifacts.js +50 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/bench-live.d.ts +28 -0
- package/dist/bench-live.js +187 -0
- package/dist/bench-live.js.map +1 -0
- package/dist/bench.d.ts +33 -0
- package/dist/bench.js +213 -0
- package/dist/bench.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +338 -0
- package/dist/cli.js.map +1 -0
- package/dist/compile.d.ts +27 -0
- package/dist/compile.js +193 -0
- package/dist/compile.js.map +1 -0
- package/dist/git.d.ts +45 -0
- package/dist/git.js +172 -0
- package/dist/git.js.map +1 -0
- package/dist/handoff.d.ts +26 -0
- package/dist/handoff.js +80 -0
- package/dist/handoff.js.map +1 -0
- package/dist/hooks.d.ts +25 -0
- package/dist/hooks.js +114 -0
- package/dist/hooks.js.map +1 -0
- package/dist/ids.d.ts +10 -0
- package/dist/ids.js +32 -0
- package/dist/ids.js.map +1 -0
- package/dist/inject/antigravity.d.ts +7 -0
- package/dist/inject/antigravity.js +391 -0
- package/dist/inject/antigravity.js.map +1 -0
- package/dist/inject/claude-code.d.ts +15 -0
- package/dist/inject/claude-code.js +53 -0
- package/dist/inject/claude-code.js.map +1 -0
- package/dist/inject/codex.d.ts +7 -0
- package/dist/inject/codex.js +307 -0
- package/dist/inject/codex.js.map +1 -0
- package/dist/inject/dispatch.d.ts +11 -0
- package/dist/inject/dispatch.js +42 -0
- package/dist/inject/dispatch.js.map +1 -0
- package/dist/inject.d.ts +22 -0
- package/dist/inject.js +47 -0
- package/dist/inject.js.map +1 -0
- package/dist/ledger.d.ts +45 -0
- package/dist/ledger.js +50 -0
- package/dist/ledger.js.map +1 -0
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.js +197 -0
- package/dist/mcp.js.map +1 -0
- package/dist/packet.d.ts +185 -0
- package/dist/packet.js +154 -0
- package/dist/packet.js.map +1 -0
- package/dist/redact.d.ts +19 -0
- package/dist/redact.js +86 -0
- package/dist/redact.js.map +1 -0
- package/dist/render.d.ts +11 -0
- package/dist/render.js +145 -0
- package/dist/render.js.map +1 -0
- package/dist/summarize.d.ts +35 -0
- package/dist/summarize.js +166 -0
- package/dist/summarize.js.map +1 -0
- package/package.json +61 -0
- package/schemas/packet.v1.schema.json +510 -0
package/dist/git.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
3
|
+
import { rmSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
/**
|
|
8
|
+
* Git is the file-state transport (spec §3.2). All commands run through
|
|
9
|
+
* execFile with argv arrays — no shell, no injection surface. Snapshots are
|
|
10
|
+
* written to refs/memento/* via a TEMPORARY index so the user's real index,
|
|
11
|
+
* HEAD, and working tree are never touched.
|
|
12
|
+
*/
|
|
13
|
+
const execFileAsync = promisify(execFile);
|
|
14
|
+
async function git(dir, args, env) {
|
|
15
|
+
const { stdout } = await execFileAsync("git", ["-C", dir, ...args], {
|
|
16
|
+
encoding: "utf8",
|
|
17
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
18
|
+
env: env ?? process.env,
|
|
19
|
+
});
|
|
20
|
+
return stdout;
|
|
21
|
+
}
|
|
22
|
+
async function tryGit(dir, args) {
|
|
23
|
+
try {
|
|
24
|
+
return await git(dir, args);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Capture the workspace's git state for a packet. Returns undefined when the
|
|
32
|
+
* directory is not a git repository (packets allow workspace.git to be absent).
|
|
33
|
+
*/
|
|
34
|
+
export async function captureGitState(dir, options = {}) {
|
|
35
|
+
const inside = await tryGit(dir, ["rev-parse", "--is-inside-work-tree"]);
|
|
36
|
+
if (inside?.trim() !== "true") {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const remote = (await tryGit(dir, ["remote", "get-url", "origin"]))?.trim() ?? null;
|
|
40
|
+
const branch = (await tryGit(dir, ["rev-parse", "--abbrev-ref", "HEAD"]))?.trim() ?? null;
|
|
41
|
+
const headSha = (await tryGit(dir, ["rev-parse", "--short", "HEAD"]))?.trim() ?? null;
|
|
42
|
+
// .memento/ is excluded from dirty-state capture: writing the handoff
|
|
43
|
+
// artifacts must not make every subsequent verify report drift.
|
|
44
|
+
const rawPorcelain = (await tryGit(dir, ["status", "--porcelain=v1", "--", ":(exclude).memento"])) ?? "";
|
|
45
|
+
const porcelain = rawPorcelain
|
|
46
|
+
.split("\n")
|
|
47
|
+
.filter((line) => line.trim().length > 0 && !line.slice(3).trim().startsWith(".memento"))
|
|
48
|
+
.join("\n");
|
|
49
|
+
const diff = headSha
|
|
50
|
+
? ((await tryGit(dir, ["diff", "HEAD", "--no-color", "--", ":(exclude).memento"])) ?? "")
|
|
51
|
+
: "";
|
|
52
|
+
const dirtyDigest = digestDirtyState(porcelain, diff);
|
|
53
|
+
const dirty = porcelain
|
|
54
|
+
.split("\n")
|
|
55
|
+
.filter((line) => line.trim().length > 0)
|
|
56
|
+
.map((line) => ({
|
|
57
|
+
path: line.slice(3).trim(),
|
|
58
|
+
state: describePorcelainCode(line.slice(0, 2)),
|
|
59
|
+
}));
|
|
60
|
+
let snapshotRef = null;
|
|
61
|
+
if (options.snapshotPacketId) {
|
|
62
|
+
snapshotRef = await snapshotWorkspace(dir, options.snapshotPacketId, headSha);
|
|
63
|
+
}
|
|
64
|
+
return { remote, branch, headSha, snapshotRef, dirtyDigest, dirty };
|
|
65
|
+
}
|
|
66
|
+
export function digestDirtyState(porcelain, diff) {
|
|
67
|
+
return `sha256:${createHash("sha256").update(`${porcelain}\n${diff}`).digest("hex").slice(0, 24)}`;
|
|
68
|
+
}
|
|
69
|
+
function describePorcelainCode(code) {
|
|
70
|
+
const map = {
|
|
71
|
+
"M": "modified",
|
|
72
|
+
"A": "added",
|
|
73
|
+
"D": "deleted",
|
|
74
|
+
"R": "renamed",
|
|
75
|
+
"C": "copied",
|
|
76
|
+
"U": "unmerged",
|
|
77
|
+
"?": "untracked",
|
|
78
|
+
};
|
|
79
|
+
const x = map[code[0] ?? " "] ?? "";
|
|
80
|
+
const y = map[code[1] ?? " "] ?? "";
|
|
81
|
+
return [x, y].filter(Boolean).join("+") || "changed";
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Snapshot the FULL working tree (incl. uncommitted and untracked files) into
|
|
85
|
+
* a shadow commit at refs/memento/<packetId>. Uses GIT_INDEX_FILE so the user's
|
|
86
|
+
* staging area is untouched. Returns the ref name, or null on failure —
|
|
87
|
+
* snapshot failure must never block packet compilation.
|
|
88
|
+
*/
|
|
89
|
+
export async function snapshotWorkspace(dir, packetId, headSha) {
|
|
90
|
+
const tmpIndex = join(tmpdir(), `memento-index-${randomBytes(6).toString("hex")}`);
|
|
91
|
+
const env = { ...process.env, GIT_INDEX_FILE: tmpIndex };
|
|
92
|
+
try {
|
|
93
|
+
if (headSha) {
|
|
94
|
+
await git(dir, ["read-tree", "HEAD"], env);
|
|
95
|
+
}
|
|
96
|
+
await git(dir, ["add", "-A", "."], env);
|
|
97
|
+
const treeSha = (await git(dir, ["write-tree"], env)).trim();
|
|
98
|
+
const commitArgs = ["commit-tree", treeSha, "-m", `memento snapshot ${packetId}`];
|
|
99
|
+
if (headSha) {
|
|
100
|
+
commitArgs.push("-p", "HEAD");
|
|
101
|
+
}
|
|
102
|
+
const commitSha = (await git(dir, commitArgs, env)).trim();
|
|
103
|
+
const ref = `refs/memento/${packetId}`;
|
|
104
|
+
await git(dir, ["update-ref", ref, commitSha]);
|
|
105
|
+
return ref;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
rmSync(tmpIndex, { force: true });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The continuity check (spec §7.4): compare the live workspace against the
|
|
116
|
+
* git state recorded in a packet. Drift is surfaced, never auto-resolved.
|
|
117
|
+
*/
|
|
118
|
+
export async function verifyWorkspace(dir, expected) {
|
|
119
|
+
const current = await captureGitState(dir);
|
|
120
|
+
const drift = [];
|
|
121
|
+
if (!current) {
|
|
122
|
+
return { clean: false, drift: ["directory is not a git repository"] };
|
|
123
|
+
}
|
|
124
|
+
if (expected.headSha && current.headSha !== expected.headSha) {
|
|
125
|
+
drift.push(`HEAD moved: packet expects ${expected.headSha}, workspace is at ${current.headSha ?? "(none)"}`);
|
|
126
|
+
}
|
|
127
|
+
if (expected.branch && current.branch !== expected.branch) {
|
|
128
|
+
drift.push(`branch changed: packet expects "${expected.branch}", workspace is on "${current.branch ?? "(detached)"}"`);
|
|
129
|
+
}
|
|
130
|
+
if (current.dirtyDigest !== expected.dirtyDigest) {
|
|
131
|
+
const expectedPaths = new Set(expected.dirty.map((file) => file.path));
|
|
132
|
+
const currentPaths = new Set(current.dirty.map((file) => file.path));
|
|
133
|
+
const appeared = [...currentPaths].filter((path) => !expectedPaths.has(path));
|
|
134
|
+
const vanished = [...expectedPaths].filter((path) => !currentPaths.has(path));
|
|
135
|
+
drift.push("working-tree contents differ from capture (dirty digest mismatch)");
|
|
136
|
+
appeared.forEach((path) => drift.push(` changed since capture: ${path}`));
|
|
137
|
+
vanished.forEach((path) => drift.push(` no longer dirty: ${path}`));
|
|
138
|
+
}
|
|
139
|
+
return { clean: drift.length === 0, drift };
|
|
140
|
+
}
|
|
141
|
+
/** True if `ref` resolves in this repo (snapshots live only where captured). */
|
|
142
|
+
export async function snapshotRefExists(dir, ref) {
|
|
143
|
+
return (await tryGit(dir, ["rev-parse", "--verify", "--quiet", ref])) !== null;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Materialize a packet's working-tree snapshot (refs/memento/<id>) back into
|
|
147
|
+
* `dir` — the receiving agent gets the prior agent's actual uncommitted AND
|
|
148
|
+
* untracked files, not just a description of them. This is what makes a memento
|
|
149
|
+
* carry executable state rather than notes.
|
|
150
|
+
*
|
|
151
|
+
* Uses a TEMPORARY index + checkout-index so the real index and HEAD are never
|
|
152
|
+
* touched. Existing files are overwritten (-f); files absent from the snapshot
|
|
153
|
+
* are left in place (non-deleting by design — restore is additive, never
|
|
154
|
+
* destructive to work the snapshot didn't know about).
|
|
155
|
+
*/
|
|
156
|
+
export async function restoreWorkspace(dir, snapshotRef) {
|
|
157
|
+
const tmpIndex = join(tmpdir(), `memento-restore-${randomBytes(6).toString("hex")}`);
|
|
158
|
+
const env = { ...process.env, GIT_INDEX_FILE: tmpIndex };
|
|
159
|
+
try {
|
|
160
|
+
await git(dir, ["read-tree", snapshotRef], env);
|
|
161
|
+
const files = (await git(dir, ["ls-files"], env))
|
|
162
|
+
.split("\n")
|
|
163
|
+
.map((line) => line.trim())
|
|
164
|
+
.filter(Boolean);
|
|
165
|
+
await git(dir, ["checkout-index", "-a", "-f"], env);
|
|
166
|
+
return { files };
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
rmSync(tmpIndex, { force: true });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=git.js.map
|
package/dist/git.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type WrittenArtifacts } from "./artifacts.js";
|
|
2
|
+
import type { Packet } from "./packet.js";
|
|
3
|
+
/**
|
|
4
|
+
* The one handoff path. CLI `compile`, the Claude hook, and the MCP
|
|
5
|
+
* `memento_handoff` tool all run through here, so every entry point gets
|
|
6
|
+
* identical behavior: adapter dispatch, session discovery, chain
|
|
7
|
+
* continuation, compilation, and artifact writing.
|
|
8
|
+
*/
|
|
9
|
+
export declare const AGENTS: readonly ["claude-code", "codex", "gemini-cli", "antigravity", "pi", "opencode"];
|
|
10
|
+
export type AgentName = (typeof AGENTS)[number];
|
|
11
|
+
export declare const REASONS: readonly ["context_exhaustion", "operator_switch", "session_end", "scheduled", "team_handoff"];
|
|
12
|
+
export interface PerformHandoffOptions {
|
|
13
|
+
workspaceDir: string;
|
|
14
|
+
agent?: string;
|
|
15
|
+
transcript?: string;
|
|
16
|
+
reason?: Packet["packet"]["reason"];
|
|
17
|
+
taskId?: string;
|
|
18
|
+
noSnapshot?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface HandoffResult {
|
|
21
|
+
packet: Packet;
|
|
22
|
+
artifacts: WrittenArtifacts;
|
|
23
|
+
transcriptPath: string;
|
|
24
|
+
eventCount: number;
|
|
25
|
+
}
|
|
26
|
+
export declare function performHandoff(options: PerformHandoffOptions): Promise<HandoffResult>;
|
package/dist/handoff.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { latestClaudeTranscript, loadClaudeLedger } from "./adapters/claude-code.js";
|
|
3
|
+
import { latestCodexRollout, loadCodexLedger } from "./adapters/codex.js";
|
|
4
|
+
import { latestAntigravityConversation, loadAntigravityLedger } from "./adapters/antigravity.js";
|
|
5
|
+
import { latestGeminiCheckpoint, loadGeminiLedger } from "./adapters/gemini.js";
|
|
6
|
+
import { latestPiSession, loadPiLedger } from "./adapters/pi.js";
|
|
7
|
+
import { latestOpencodeSession, loadOpencodeLedger } from "./adapters/opencode.js";
|
|
8
|
+
import { chainFromPrior, writePacketArtifacts } from "./artifacts.js";
|
|
9
|
+
import { compilePacket } from "./compile.js";
|
|
10
|
+
/**
|
|
11
|
+
* The one handoff path. CLI `compile`, the Claude hook, and the MCP
|
|
12
|
+
* `memento_handoff` tool all run through here, so every entry point gets
|
|
13
|
+
* identical behavior: adapter dispatch, session discovery, chain
|
|
14
|
+
* continuation, compilation, and artifact writing.
|
|
15
|
+
*/
|
|
16
|
+
export const AGENTS = ["claude-code", "codex", "gemini-cli", "antigravity", "pi", "opencode"];
|
|
17
|
+
export const REASONS = [
|
|
18
|
+
"context_exhaustion",
|
|
19
|
+
"operator_switch",
|
|
20
|
+
"session_end",
|
|
21
|
+
"scheduled",
|
|
22
|
+
"team_handoff",
|
|
23
|
+
];
|
|
24
|
+
export async function performHandoff(options) {
|
|
25
|
+
const agent = options.agent ?? "claude-code";
|
|
26
|
+
if (!AGENTS.includes(agent)) {
|
|
27
|
+
throw new Error(`Adapter "${agent}" is not implemented yet (${AGENTS.join(" | ")}).`);
|
|
28
|
+
}
|
|
29
|
+
const transcript = options.transcript ?? discoverSession(agent, options.workspaceDir);
|
|
30
|
+
if (!transcript || !existsSync(transcript)) {
|
|
31
|
+
throw new Error(`No ${agent} transcript found for ${options.workspaceDir} — pass one explicitly (--transcript / transcript).`);
|
|
32
|
+
}
|
|
33
|
+
const ledger = agent === "codex"
|
|
34
|
+
? loadCodexLedger(transcript)
|
|
35
|
+
: agent === "gemini-cli"
|
|
36
|
+
? loadGeminiLedger(transcript)
|
|
37
|
+
: agent === "antigravity"
|
|
38
|
+
? loadAntigravityLedger(transcript)
|
|
39
|
+
: agent === "pi"
|
|
40
|
+
? loadPiLedger(transcript)
|
|
41
|
+
: agent === "opencode"
|
|
42
|
+
? loadOpencodeLedger(transcript)
|
|
43
|
+
: loadClaudeLedger(transcript);
|
|
44
|
+
if (ledger.events.length === 0) {
|
|
45
|
+
throw new Error(`Transcript ${transcript} contained no usable events.`);
|
|
46
|
+
}
|
|
47
|
+
// Continue the workspace's packet chain unless the caller pins a task.
|
|
48
|
+
const chain = options.taskId ? undefined : chainFromPrior(options.workspaceDir);
|
|
49
|
+
const packet = await compilePacket({
|
|
50
|
+
ledger,
|
|
51
|
+
workspaceDir: options.workspaceDir,
|
|
52
|
+
reason: options.reason ?? "operator_switch",
|
|
53
|
+
...(options.taskId ? { taskId: options.taskId } : {}),
|
|
54
|
+
...(chain ? { taskId: chain.taskId, parent: chain.parent, seq: chain.seq } : {}),
|
|
55
|
+
...(options.noSnapshot ? { noSnapshot: true } : {}),
|
|
56
|
+
});
|
|
57
|
+
const artifacts = writePacketArtifacts(options.workspaceDir, packet);
|
|
58
|
+
return { packet, artifacts, transcriptPath: transcript, eventCount: ledger.events.length };
|
|
59
|
+
}
|
|
60
|
+
function discoverSession(agent, workspaceDir) {
|
|
61
|
+
switch (agent) {
|
|
62
|
+
case "codex":
|
|
63
|
+
return latestCodexRollout(workspaceDir);
|
|
64
|
+
// Gemini checkpoints carry no cwd, so discovery is global-newest,
|
|
65
|
+
// not workspace-scoped — see adapters/gemini.ts.
|
|
66
|
+
case "gemini-cli":
|
|
67
|
+
return latestGeminiCheckpoint();
|
|
68
|
+
case "antigravity":
|
|
69
|
+
return latestAntigravityConversation(workspaceDir);
|
|
70
|
+
case "pi":
|
|
71
|
+
return latestPiSession(workspaceDir);
|
|
72
|
+
// opencode sessions live in a global store with no cwd, so discovery is
|
|
73
|
+
// newest-session, not workspace-scoped — see adapters/opencode.ts.
|
|
74
|
+
case "opencode":
|
|
75
|
+
return latestOpencodeSession(workspaceDir);
|
|
76
|
+
default:
|
|
77
|
+
return latestClaudeTranscript(workspaceDir);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=handoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handoff.js","sourceRoot":"","sources":["../src/handoff.ts"],"names":[],"mappings":"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"}
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Packet } from "./packet.js";
|
|
2
|
+
export interface InstallResult {
|
|
3
|
+
settingsPath: string;
|
|
4
|
+
installed: string[];
|
|
5
|
+
skipped: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function installClaudeHooks(workspaceDir: string, command?: string): InstallResult;
|
|
8
|
+
export interface CodexInstallResult {
|
|
9
|
+
configPath: string;
|
|
10
|
+
installed: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Register Memento as an MCP server in ~/.codex/config.toml so Codex can call
|
|
14
|
+
* memento_resume/memento_handoff natively. TOML is appended textually (no parser
|
|
15
|
+
* dependency): idempotent via a literal [mcp_servers.memento] check — the rest
|
|
16
|
+
* of the user's config is never rewritten.
|
|
17
|
+
*/
|
|
18
|
+
export declare function installCodexMcp(home?: string): CodexInstallResult;
|
|
19
|
+
export interface HookOutcome {
|
|
20
|
+
ok: boolean;
|
|
21
|
+
message: string;
|
|
22
|
+
packet?: Packet;
|
|
23
|
+
}
|
|
24
|
+
export declare function handleHookPayload(payloadJson: string): Promise<HookOutcome>;
|
|
25
|
+
export declare function readStdin(): Promise<string>;
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { performHandoff } from "./handoff.js";
|
|
5
|
+
/**
|
|
6
|
+
* Claude Code lifecycle capture (spec §7.1/§7.2). Two pieces:
|
|
7
|
+
*
|
|
8
|
+
* - installClaudeHooks: writes PreCompact + SessionEnd hooks into the
|
|
9
|
+
* workspace's .claude/settings.json, each invoking `memento hook`.
|
|
10
|
+
* PreCompact is the flagship trigger — it fires at the exact moment of
|
|
11
|
+
* context pressure, which is when a continuation packet matters most.
|
|
12
|
+
*
|
|
13
|
+
* - handleHookPayload: the receiving end. Claude Code pipes a JSON payload
|
|
14
|
+
* (hook_event_name, transcript_path, cwd, …) to the hook command's stdin;
|
|
15
|
+
* this compiles a packet from that exact transcript. It NEVER throws —
|
|
16
|
+
* a broken hook must not break the user's session.
|
|
17
|
+
*/
|
|
18
|
+
const HOOK_EVENTS = ["PreCompact", "SessionEnd"];
|
|
19
|
+
export function installClaudeHooks(workspaceDir, command = "memento hook") {
|
|
20
|
+
const dir = join(workspaceDir, ".claude");
|
|
21
|
+
const settingsPath = join(dir, "settings.json");
|
|
22
|
+
mkdirSync(dir, { recursive: true });
|
|
23
|
+
let settings = {};
|
|
24
|
+
if (existsSync(settingsPath)) {
|
|
25
|
+
try {
|
|
26
|
+
settings = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
throw new Error(`${settingsPath} is not valid JSON — fix or remove it first.`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const hooks = (settings.hooks ?? {});
|
|
33
|
+
const installed = [];
|
|
34
|
+
const skipped = [];
|
|
35
|
+
for (const event of HOOK_EVENTS) {
|
|
36
|
+
const entries = hooks[event] ?? [];
|
|
37
|
+
const alreadyInstalled = entries.some((entry) => entry.hooks?.some((hook) => hook.command?.includes("memento")));
|
|
38
|
+
if (alreadyInstalled) {
|
|
39
|
+
skipped.push(event);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
entries.push({ hooks: [{ type: "command", command }] });
|
|
43
|
+
hooks[event] = entries;
|
|
44
|
+
installed.push(event);
|
|
45
|
+
}
|
|
46
|
+
settings.hooks = hooks;
|
|
47
|
+
writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
|
|
48
|
+
return { settingsPath, installed, skipped };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Register Memento as an MCP server in ~/.codex/config.toml so Codex can call
|
|
52
|
+
* memento_resume/memento_handoff natively. TOML is appended textually (no parser
|
|
53
|
+
* dependency): idempotent via a literal [mcp_servers.memento] check — the rest
|
|
54
|
+
* of the user's config is never rewritten.
|
|
55
|
+
*/
|
|
56
|
+
export function installCodexMcp(home = homedir()) {
|
|
57
|
+
const dir = join(home, ".codex");
|
|
58
|
+
const configPath = join(dir, "config.toml");
|
|
59
|
+
mkdirSync(dir, { recursive: true });
|
|
60
|
+
const existing = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
|
|
61
|
+
if (existing.includes("[mcp_servers.memento]")) {
|
|
62
|
+
return { configPath, installed: false };
|
|
63
|
+
}
|
|
64
|
+
const block = [
|
|
65
|
+
"",
|
|
66
|
+
"# Added by `memento install-hooks codex` — cross-agent session continuity.",
|
|
67
|
+
"[mcp_servers.memento]",
|
|
68
|
+
"command = \"memento\"",
|
|
69
|
+
"args = [\"mcp\"]",
|
|
70
|
+
"",
|
|
71
|
+
].join("\n");
|
|
72
|
+
appendFileSync(configPath, existing.length > 0 && !existing.endsWith("\n") ? `\n${block}` : block);
|
|
73
|
+
return { configPath, installed: true };
|
|
74
|
+
}
|
|
75
|
+
const REASON_BY_EVENT = {
|
|
76
|
+
PreCompact: "context_exhaustion",
|
|
77
|
+
SessionEnd: "session_end",
|
|
78
|
+
Stop: "session_end",
|
|
79
|
+
};
|
|
80
|
+
export async function handleHookPayload(payloadJson) {
|
|
81
|
+
try {
|
|
82
|
+
const payload = JSON.parse(payloadJson);
|
|
83
|
+
const workspaceDir = payload.cwd ?? process.cwd();
|
|
84
|
+
const reason = REASON_BY_EVENT[payload.hook_event_name ?? ""] ?? "operator_switch";
|
|
85
|
+
const result = await performHandoff({
|
|
86
|
+
workspaceDir,
|
|
87
|
+
agent: "claude-code",
|
|
88
|
+
reason,
|
|
89
|
+
...(payload.transcript_path && existsSync(payload.transcript_path)
|
|
90
|
+
? { transcript: payload.transcript_path }
|
|
91
|
+
: {}),
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
ok: true,
|
|
95
|
+
message: `memento hook: sealed ${result.packet.packet.id} (${reason}) → ${result.artifacts.markdownPath}`,
|
|
96
|
+
packet: result.packet,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
// Hooks run inside someone's live session: degrade, never disrupt.
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
message: `memento hook: ${error instanceof Error ? error.message : String(error)}`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export async function readStdin() {
|
|
108
|
+
const chunks = [];
|
|
109
|
+
for await (const chunk of process.stdin) {
|
|
110
|
+
chunks.push(chunk);
|
|
111
|
+
}
|
|
112
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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"}
|
package/dist/ids.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function ulid(now?: number): string;
|
|
2
|
+
export declare const newTaskId: () => string;
|
|
3
|
+
export declare const newPacketId: () => string;
|
|
4
|
+
export declare const newSessionId: () => string;
|
|
5
|
+
/**
|
|
6
|
+
* Workspace identity (spec §5.1): hash of the git remote URL when one exists,
|
|
7
|
+
* else the absolute repo path. Remote-derived ids are stable across machines,
|
|
8
|
+
* which is what makes team handoff addressable.
|
|
9
|
+
*/
|
|
10
|
+
export declare function workspaceId(seed: string): string;
|
package/dist/ids.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* ULID-style identifiers: 10 chars of Crockford-base32 time + 16 chars of
|
|
4
|
+
* randomness. Lexically sortable by creation time, no dependency needed.
|
|
5
|
+
*/
|
|
6
|
+
const CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
7
|
+
export function ulid(now = Date.now()) {
|
|
8
|
+
let time = now;
|
|
9
|
+
let timePart = "";
|
|
10
|
+
for (let i = 0; i < 10; i += 1) {
|
|
11
|
+
timePart = CROCKFORD[time % 32] + timePart;
|
|
12
|
+
time = Math.floor(time / 32);
|
|
13
|
+
}
|
|
14
|
+
const bytes = randomBytes(16);
|
|
15
|
+
let randPart = "";
|
|
16
|
+
for (let i = 0; i < 16; i += 1) {
|
|
17
|
+
randPart += CROCKFORD[(bytes[i] ?? 0) % 32];
|
|
18
|
+
}
|
|
19
|
+
return timePart + randPart;
|
|
20
|
+
}
|
|
21
|
+
export const newTaskId = () => `task_${ulid()}`;
|
|
22
|
+
export const newPacketId = () => `cp_${ulid()}`;
|
|
23
|
+
export const newSessionId = () => `bses_${ulid()}`;
|
|
24
|
+
/**
|
|
25
|
+
* Workspace identity (spec §5.1): hash of the git remote URL when one exists,
|
|
26
|
+
* else the absolute repo path. Remote-derived ids are stable across machines,
|
|
27
|
+
* which is what makes team handoff addressable.
|
|
28
|
+
*/
|
|
29
|
+
export function workspaceId(seed) {
|
|
30
|
+
return `ws_${createHash("sha256").update(seed).digest("hex").slice(0, 12)}`;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=ids.js.map
|
package/dist/ids.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ids.js","sourceRoot":"","sources":["../src/ids.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAE,WAAW,EAAC,MAAM,aAAa,CAAC;AAEpD;;;GAGG;AACH,MAAM,SAAS,GAAG,kCAAkC,CAAC;AAErD,MAAM,UAAU,IAAI,CAAC,MAAc,IAAI,CAAC,GAAG,EAAE;IACzC,IAAI,IAAI,GAAG,GAAG,CAAC;IACf,IAAI,QAAQ,GAAG,EAAE,CAAC;IAElB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,QAAQ,GAAG,SAAS,CAAC,IAAI,GAAG,EAAE,CAAC,GAAG,QAAQ,CAAC;QAC3C,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;IACjC,CAAC;IAED,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC9B,IAAI,QAAQ,GAAG,EAAE,CAAC;IAElB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,QAAQ,IAAI,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,OAAO,QAAQ,GAAG,QAAQ,CAAC;AAC/B,CAAC;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,GAAW,EAAE,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;AACxD,MAAM,CAAC,MAAM,WAAW,GAAG,GAAW,EAAE,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;AACxD,MAAM,CAAC,MAAM,YAAY,GAAG,GAAW,EAAE,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;AAE3D;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACpC,OAAO,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;AAChF,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Packet } from "../packet.js";
|
|
2
|
+
export interface InjectResult {
|
|
3
|
+
sessionId: string;
|
|
4
|
+
transcriptPath: string;
|
|
5
|
+
sqliteUpdated?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function injectAntigravitySession(packet: Packet, workspaceDir: string, home?: string): InjectResult;
|