clawvault 2.4.4 → 2.4.5

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.
Files changed (36) hide show
  1. package/bin/clawvault.js +4 -0
  2. package/bin/command-registration.test.js +13 -1
  3. package/bin/help-contract.test.js +2 -0
  4. package/bin/register-config-commands.js +153 -0
  5. package/bin/register-config-route-commands.test.js +114 -0
  6. package/bin/register-resilience-commands.js +37 -2
  7. package/bin/register-resilience-commands.test.js +81 -0
  8. package/bin/register-route-commands.js +114 -0
  9. package/bin/test-helpers/cli-command-fixtures.js +10 -0
  10. package/dist/{chunk-XDCFXFGH.js → chunk-22WE3J4F.js} +1 -1
  11. package/dist/chunk-3PJIGGWV.js +49 -0
  12. package/dist/{chunk-GBIDDDSL.js → chunk-4JJL47IJ.js} +1 -1
  13. package/dist/{chunk-FDJIZKCW.js → chunk-6B3JWM7J.js} +12 -48
  14. package/dist/{chunk-SNEMCQP7.js → chunk-B3SMJZIZ.js} +1 -1
  15. package/dist/{chunk-MZZJLQNQ.js → chunk-F55HGNU4.js} +6 -0
  16. package/dist/{chunk-BMOQI62Q.js → chunk-HNMFXFYP.js} +5 -3
  17. package/dist/chunk-OIWVQYQF.js +284 -0
  18. package/dist/{chunk-FEFPBHH4.js → chunk-RXEIQ3KQ.js} +452 -22
  19. package/dist/{chunk-DHJPXGC7.js → chunk-U2ONVV7N.js} +1 -1
  20. package/dist/{chunk-IFTEGE4D.js → chunk-YIRWDQKA.js} +4 -2
  21. package/dist/commands/checkpoint.js +1 -1
  22. package/dist/commands/context.js +4 -3
  23. package/dist/commands/doctor.js +3 -2
  24. package/dist/commands/observe.js +3 -2
  25. package/dist/commands/rebuild.js +3 -2
  26. package/dist/commands/recover.d.ts +13 -1
  27. package/dist/commands/recover.js +10 -2
  28. package/dist/commands/replay.js +3 -2
  29. package/dist/commands/setup.js +3 -2
  30. package/dist/commands/sleep.js +5 -4
  31. package/dist/commands/status.js +3 -2
  32. package/dist/commands/wake.js +5 -4
  33. package/dist/index.d.ts +27 -1
  34. package/dist/index.js +36 -14
  35. package/package.json +1 -1
  36. package/dist/chunk-IWYZAXKJ.js +0 -146
@@ -1,46 +1,3 @@
1
- // src/types.ts
2
- var DEFAULT_CATEGORIES = [
3
- "preferences",
4
- "decisions",
5
- "patterns",
6
- "people",
7
- "projects",
8
- "goals",
9
- "transcripts",
10
- "inbox",
11
- "templates",
12
- "lessons",
13
- "agents",
14
- "commitments",
15
- "handoffs",
16
- "research",
17
- "tasks",
18
- "backlog"
19
- ];
20
- var MEMORY_TYPES = [
21
- "fact",
22
- "feeling",
23
- "decision",
24
- "lesson",
25
- "commitment",
26
- "preference",
27
- "relationship",
28
- "project"
29
- ];
30
- var TYPE_TO_CATEGORY = {
31
- fact: "facts",
32
- feeling: "feelings",
33
- decision: "decisions",
34
- lesson: "lessons",
35
- commitment: "commitments",
36
- preference: "preferences",
37
- relationship: "people",
38
- project: "projects"
39
- };
40
- var DEFAULT_CONFIG = {
41
- categories: DEFAULT_CATEGORIES
42
- };
43
-
44
1
  // src/lib/search.ts
45
2
  import { execFileSync, spawnSync } from "child_process";
46
3
  import * as fs from "fs";
@@ -71,8 +28,19 @@ function extractJsonPayload(raw) {
71
28
  if (end <= start) return null;
72
29
  return raw.slice(start, end + 1);
73
30
  }
31
+ function stripQmdNoise(raw) {
32
+ return raw.split("\n").filter((line) => {
33
+ const t = line.trim();
34
+ if (!t) return true;
35
+ if (t.startsWith("[node-llama-cpp]")) return false;
36
+ if (t.startsWith("Expanding query")) return false;
37
+ if (t.startsWith("Searching ") && t.endsWith("queries...")) return false;
38
+ if (/^[├└─│]/.test(t)) return false;
39
+ return true;
40
+ }).join("\n");
41
+ }
74
42
  function parseQmdOutput(raw) {
75
- const trimmed = raw.trim();
43
+ const trimmed = stripQmdNoise(raw).trim();
76
44
  if (!trimmed) return [];
77
45
  const direct = tryParseJson(trimmed);
78
46
  const extracted = direct ? null : extractJsonPayload(trimmed);
@@ -368,10 +336,6 @@ function extractTags(content) {
368
336
  }
369
337
 
370
338
  export {
371
- DEFAULT_CATEGORIES,
372
- MEMORY_TYPES,
373
- TYPE_TO_CATEGORY,
374
- DEFAULT_CONFIG,
375
339
  QMD_INSTALL_URL,
376
340
  QMD_INSTALL_COMMAND,
377
341
  QmdUnavailableError,
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-P5EPF6MB.js";
7
7
  import {
8
8
  Observer
9
- } from "./chunk-FEFPBHH4.js";
9
+ } from "./chunk-RXEIQ3KQ.js";
10
10
  import {
11
11
  resolveVaultPath
12
12
  } from "./chunk-MXSSG3QU.js";
@@ -6,6 +6,7 @@ var CLAWVAULT_DIR = ".clawvault";
6
6
  var CHECKPOINT_FILE = "last-checkpoint.json";
7
7
  var SESSION_STATE_FILE = "session-state.json";
8
8
  var DIRTY_DEATH_FLAG = "dirty-death.flag";
9
+ var CHECKPOINT_HISTORY_DIR = "checkpoints";
9
10
  var pendingCheckpoint = null;
10
11
  var pendingData = null;
11
12
  function ensureClawvaultDir(vaultPath) {
@@ -18,6 +19,11 @@ function ensureClawvaultDir(vaultPath) {
18
19
  function writeCheckpointToDisk(dir, data) {
19
20
  const checkpointPath = path.join(dir, CHECKPOINT_FILE);
20
21
  fs.writeFileSync(checkpointPath, JSON.stringify(data, null, 2));
22
+ const historyDir = path.join(dir, CHECKPOINT_HISTORY_DIR);
23
+ fs.mkdirSync(historyDir, { recursive: true });
24
+ const historyFileName = `${data.timestamp.replace(/[:.]/g, "-")}.json`;
25
+ const historyPath = path.join(historyDir, historyFileName);
26
+ fs.writeFileSync(historyPath, JSON.stringify(data, null, 2));
21
27
  const flagPath = path.join(dir, DIRTY_DEATH_FLAG);
22
28
  fs.writeFileSync(flagPath, data.timestamp);
23
29
  }
@@ -1,14 +1,16 @@
1
1
  import {
2
- DEFAULT_CATEGORIES,
3
2
  QmdUnavailableError,
4
3
  SearchEngine,
5
- TYPE_TO_CATEGORY,
6
4
  extractTags,
7
5
  extractWikiLinks,
8
6
  hasQmd,
9
7
  qmdEmbed,
10
8
  qmdUpdate
11
- } from "./chunk-FDJIZKCW.js";
9
+ } from "./chunk-6B3JWM7J.js";
10
+ import {
11
+ DEFAULT_CATEGORIES,
12
+ TYPE_TO_CATEGORY
13
+ } from "./chunk-3PJIGGWV.js";
12
14
  import {
13
15
  buildOrUpdateMemoryGraphIndex
14
16
  } from "./chunk-ZZA73MFY.js";
@@ -0,0 +1,284 @@
1
+ import {
2
+ formatAge
3
+ } from "./chunk-7ZRP733D.js";
4
+ import {
5
+ checkDirtyDeath,
6
+ clearDirtyFlag
7
+ } from "./chunk-F55HGNU4.js";
8
+
9
+ // src/commands/recover.ts
10
+ import * as fs from "fs";
11
+ import * as path from "path";
12
+ var CLAWVAULT_DIR = ".clawvault";
13
+ var CHECKPOINT_FILE = "last-checkpoint.json";
14
+ var CHECKPOINT_HISTORY_DIR = "checkpoints";
15
+ function parseCheckpointFile(filePath) {
16
+ try {
17
+ const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
18
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
19
+ return null;
20
+ }
21
+ const record = parsed;
22
+ const timestamp = typeof record.timestamp === "string" ? record.timestamp.trim() : "";
23
+ if (!timestamp) {
24
+ return null;
25
+ }
26
+ const checkpoint = {
27
+ timestamp,
28
+ workingOn: typeof record.workingOn === "string" ? record.workingOn : null,
29
+ focus: typeof record.focus === "string" ? record.focus : null,
30
+ blocked: typeof record.blocked === "string" ? record.blocked : null
31
+ };
32
+ if (typeof record.sessionId === "string") {
33
+ checkpoint.sessionId = record.sessionId;
34
+ }
35
+ if (typeof record.sessionKey === "string") {
36
+ checkpoint.sessionKey = record.sessionKey;
37
+ }
38
+ if (typeof record.model === "string") {
39
+ checkpoint.model = record.model;
40
+ }
41
+ if (typeof record.tokenEstimate === "number" && Number.isFinite(record.tokenEstimate)) {
42
+ checkpoint.tokenEstimate = record.tokenEstimate;
43
+ }
44
+ if (typeof record.sessionStartedAt === "string") {
45
+ checkpoint.sessionStartedAt = record.sessionStartedAt;
46
+ }
47
+ if (typeof record.urgent === "boolean") {
48
+ checkpoint.urgent = record.urgent;
49
+ }
50
+ return checkpoint;
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+ function compareByTimestampDesc(left, right) {
56
+ const leftTime = Date.parse(left.timestamp);
57
+ const rightTime = Date.parse(right.timestamp);
58
+ if (!Number.isNaN(leftTime) && !Number.isNaN(rightTime)) {
59
+ return rightTime - leftTime;
60
+ }
61
+ return right.timestamp.localeCompare(left.timestamp);
62
+ }
63
+ async function checkRecoveryStatus(vaultPath) {
64
+ const { died, checkpoint, deathTime } = await checkDirtyDeath(vaultPath);
65
+ return { died, checkpoint, deathTime };
66
+ }
67
+ function listCheckpoints(vaultPath) {
68
+ const resolvedVaultPath = path.resolve(vaultPath);
69
+ const clawvaultDir = path.join(resolvedVaultPath, CLAWVAULT_DIR);
70
+ const historyDir = path.join(clawvaultDir, CHECKPOINT_HISTORY_DIR);
71
+ const checkpoints = [];
72
+ if (fs.existsSync(historyDir)) {
73
+ const files = fs.readdirSync(historyDir).filter((entry) => entry.endsWith(".json")).sort().reverse();
74
+ for (const fileName of files) {
75
+ const absolutePath = path.join(historyDir, fileName);
76
+ const parsed = parseCheckpointFile(absolutePath);
77
+ if (!parsed) {
78
+ continue;
79
+ }
80
+ checkpoints.push({
81
+ ...parsed,
82
+ filePath: absolutePath
83
+ });
84
+ }
85
+ }
86
+ if (checkpoints.length === 0) {
87
+ const latestCheckpointPath = path.join(clawvaultDir, CHECKPOINT_FILE);
88
+ if (fs.existsSync(latestCheckpointPath)) {
89
+ const fallback = parseCheckpointFile(latestCheckpointPath);
90
+ if (fallback) {
91
+ checkpoints.push({
92
+ ...fallback,
93
+ filePath: latestCheckpointPath
94
+ });
95
+ }
96
+ }
97
+ }
98
+ return checkpoints.sort(compareByTimestampDesc);
99
+ }
100
+ async function recover(vaultPath, options = {}) {
101
+ const { clearFlag = false } = options;
102
+ const { died, checkpoint, deathTime } = await checkRecoveryStatus(vaultPath);
103
+ if (!died) {
104
+ return {
105
+ died: false,
106
+ deathTime: null,
107
+ checkpoint: null,
108
+ handoffPath: null,
109
+ handoffContent: null,
110
+ recoveryMessage: "No context death detected. Clean startup."
111
+ };
112
+ }
113
+ const handoffsDir = path.join(vaultPath, "handoffs");
114
+ let handoffPath = null;
115
+ let handoffContent = null;
116
+ if (fs.existsSync(handoffsDir)) {
117
+ const files = fs.readdirSync(handoffsDir).filter((f) => f.startsWith("handoff-") && f.endsWith(".md")).sort().reverse();
118
+ if (files.length > 0) {
119
+ handoffPath = path.join(handoffsDir, files[0]);
120
+ handoffContent = fs.readFileSync(handoffPath, "utf-8");
121
+ }
122
+ }
123
+ let message = "\u26A0\uFE0F **CONTEXT DEATH DETECTED**\n\n";
124
+ message += `Your previous session died at ${deathTime}.
125
+
126
+ `;
127
+ if (checkpoint) {
128
+ message += "**Last known state:**\n";
129
+ if (checkpoint.workingOn) {
130
+ message += `- Working on: ${checkpoint.workingOn}
131
+ `;
132
+ }
133
+ if (checkpoint.focus) {
134
+ message += `- Focus: ${checkpoint.focus}
135
+ `;
136
+ }
137
+ if (checkpoint.blocked) {
138
+ message += `- Blocked: ${checkpoint.blocked}
139
+ `;
140
+ }
141
+ message += "\n";
142
+ }
143
+ if (handoffPath) {
144
+ message += `**Last handoff:** ${path.basename(handoffPath)}
145
+ `;
146
+ message += "Review and resume from where you left off.\n";
147
+ } else {
148
+ message += "**No handoff found.** You may have lost context.\n";
149
+ }
150
+ if (clearFlag) {
151
+ await clearDirtyFlag(vaultPath);
152
+ }
153
+ return {
154
+ died: true,
155
+ deathTime,
156
+ checkpoint,
157
+ handoffPath,
158
+ handoffContent,
159
+ recoveryMessage: message
160
+ };
161
+ }
162
+ function formatRecoveryCheckStatus(info) {
163
+ if (!info.died) {
164
+ return "\u2713 Dirty death flag is clear.";
165
+ }
166
+ let output = "\u26A0\uFE0F Dirty death flag is set.\n";
167
+ output += `Death time: ${info.deathTime}
168
+ `;
169
+ if (info.checkpoint?.timestamp) {
170
+ const age = formatAge(Date.now() - new Date(info.checkpoint.timestamp).getTime());
171
+ output += `Last checkpoint: ${info.checkpoint.timestamp} (${age} ago)
172
+ `;
173
+ } else {
174
+ output += "Last checkpoint: unavailable\n";
175
+ }
176
+ output += "Use `clawvault recover --clear` after reviewing recovery details.";
177
+ return output;
178
+ }
179
+ function formatCheckpointList(checkpoints) {
180
+ if (checkpoints.length === 0) {
181
+ return "No checkpoints found.";
182
+ }
183
+ const headers = ["TIMESTAMP", "WORKING_ON", "FOCUS", "FILE"];
184
+ const rows = checkpoints.map((checkpoint) => ({
185
+ timestamp: checkpoint.timestamp,
186
+ workingOn: checkpoint.workingOn ?? "-",
187
+ focus: checkpoint.focus ?? "-",
188
+ file: path.basename(checkpoint.filePath)
189
+ }));
190
+ const timestampWidth = Math.max(headers[0].length, ...rows.map((row) => row.timestamp.length));
191
+ const workingOnWidth = Math.max(headers[1].length, ...rows.map((row) => row.workingOn.length));
192
+ const focusWidth = Math.max(headers[2].length, ...rows.map((row) => row.focus.length));
193
+ const fileWidth = Math.max(headers[3].length, ...rows.map((row) => row.file.length));
194
+ const lines = [];
195
+ lines.push(
196
+ `${headers[0].padEnd(timestampWidth)} ${headers[1].padEnd(workingOnWidth)} ${headers[2].padEnd(focusWidth)} ${headers[3].padEnd(fileWidth)}`
197
+ );
198
+ lines.push(
199
+ `${"-".repeat(timestampWidth)} ${"-".repeat(workingOnWidth)} ${"-".repeat(focusWidth)} ${"-".repeat(fileWidth)}`
200
+ );
201
+ for (const row of rows) {
202
+ lines.push(
203
+ `${row.timestamp.padEnd(timestampWidth)} ${row.workingOn.padEnd(workingOnWidth)} ${row.focus.padEnd(focusWidth)} ${row.file}`
204
+ );
205
+ }
206
+ return lines.join("\n");
207
+ }
208
+ function formatRecoveryInfo(info, options = {}) {
209
+ const { verbose = false } = options;
210
+ if (!info.died) {
211
+ return "\u2713 Clean startup - no context death detected.";
212
+ }
213
+ let output = "\n\u26A0\uFE0F CONTEXT DEATH DETECTED\n";
214
+ output += "\u2550".repeat(40) + "\n\n";
215
+ output += `Death time: ${info.deathTime}
216
+ `;
217
+ if (info.checkpoint?.timestamp) {
218
+ const age = formatAge(Date.now() - new Date(info.checkpoint.timestamp).getTime());
219
+ output += `Checkpoint: ${info.checkpoint.timestamp} (${age} ago)
220
+ `;
221
+ }
222
+ output += "\n";
223
+ if (info.checkpoint) {
224
+ output += "Last checkpoint:\n";
225
+ if (info.checkpoint.workingOn) {
226
+ output += ` \u2022 Working on: ${info.checkpoint.workingOn}
227
+ `;
228
+ }
229
+ if (info.checkpoint.focus) {
230
+ output += ` \u2022 Focus: ${info.checkpoint.focus}
231
+ `;
232
+ }
233
+ if (info.checkpoint.blocked) {
234
+ output += ` \u2022 Blocked: ${info.checkpoint.blocked}
235
+ `;
236
+ }
237
+ if (info.checkpoint.sessionKey || info.checkpoint.model || info.checkpoint.tokenEstimate !== void 0) {
238
+ output += " \u2022 Session:\n";
239
+ if (info.checkpoint.sessionKey) {
240
+ output += ` - Key: ${info.checkpoint.sessionKey}
241
+ `;
242
+ }
243
+ if (info.checkpoint.model) {
244
+ output += ` - Model: ${info.checkpoint.model}
245
+ `;
246
+ }
247
+ if (info.checkpoint.tokenEstimate !== void 0) {
248
+ output += ` - Token estimate: ${info.checkpoint.tokenEstimate}
249
+ `;
250
+ }
251
+ }
252
+ output += "\n";
253
+ } else {
254
+ output += "No checkpoint data found.\n\n";
255
+ }
256
+ if (info.handoffPath) {
257
+ output += `Last handoff: ${path.basename(info.handoffPath)}
258
+ `;
259
+ } else {
260
+ output += "No handoff found - context may be lost.\n";
261
+ }
262
+ if (verbose) {
263
+ if (info.checkpoint) {
264
+ output += "\nCheckpoint JSON:\n";
265
+ output += JSON.stringify(info.checkpoint, null, 2) + "\n";
266
+ }
267
+ if (info.handoffContent) {
268
+ output += "\nHandoff content:\n";
269
+ output += info.handoffContent.trim() + "\n";
270
+ }
271
+ }
272
+ output += "\n" + "\u2550".repeat(40) + "\n";
273
+ output += "Run `clawvault recap` to see full context.\n";
274
+ return output;
275
+ }
276
+
277
+ export {
278
+ checkRecoveryStatus,
279
+ listCheckpoints,
280
+ recover,
281
+ formatRecoveryCheckStatus,
282
+ formatCheckpointList,
283
+ formatRecoveryInfo
284
+ };