clawvault 2.1.2 → 2.2.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.
Files changed (55) hide show
  1. package/bin/command-registration.test.js +6 -1
  2. package/bin/help-contract.test.js +2 -0
  3. package/bin/register-core-commands.js +14 -4
  4. package/bin/register-maintenance-commands.js +111 -0
  5. package/bin/register-query-commands.js +32 -1
  6. package/bin/register-session-lifecycle-commands.js +2 -0
  7. package/dist/{chunk-5MQB7B37.js → chunk-2HM7ZI4X.js} +268 -434
  8. package/dist/chunk-73P7XCQM.js +104 -0
  9. package/dist/{chunk-MIIXBNO3.js → chunk-FDJIZKCW.js} +12 -1
  10. package/dist/chunk-GJEGPO7U.js +49 -0
  11. package/dist/chunk-GQVYQCY5.js +396 -0
  12. package/dist/{chunk-TXO34J3O.js → chunk-H7JW4L7H.js} +1 -1
  13. package/dist/{chunk-TBVI4N53.js → chunk-I5X6J4FX.js} +120 -95
  14. package/dist/chunk-K6XHCUFL.js +123 -0
  15. package/dist/chunk-L6NB43WV.js +472 -0
  16. package/dist/{chunk-FEQ2CQ3Y.js → chunk-LB6P4CD5.js} +20 -7
  17. package/dist/chunk-MGDEINGP.js +99 -0
  18. package/dist/chunk-MQUJNOHK.js +58 -0
  19. package/dist/{chunk-QFBKWDYR.js → chunk-OTQW3OMC.js} +91 -12
  20. package/dist/chunk-P5EPF6MB.js +182 -0
  21. package/dist/chunk-VR5NE7PZ.js +45 -0
  22. package/dist/{chunk-PIJGYMQZ.js → chunk-W463YRED.js} +1 -1
  23. package/dist/chunk-WZI3OAE5.js +111 -0
  24. package/dist/chunk-Z2XBWN7A.js +247 -0
  25. package/dist/{chunk-O5V7SD5C.js → chunk-ZZA73MFY.js} +1 -1
  26. package/dist/commands/archive.d.ts +11 -0
  27. package/dist/commands/archive.js +11 -0
  28. package/dist/commands/context.d.ts +1 -1
  29. package/dist/commands/context.js +6 -4
  30. package/dist/commands/doctor.js +6 -6
  31. package/dist/commands/graph.js +2 -2
  32. package/dist/commands/link.js +1 -1
  33. package/dist/commands/migrate-observations.d.ts +19 -0
  34. package/dist/commands/migrate-observations.js +13 -0
  35. package/dist/commands/observe.js +5 -2
  36. package/dist/commands/rebuild.d.ts +11 -0
  37. package/dist/commands/rebuild.js +12 -0
  38. package/dist/commands/reflect.d.ts +11 -0
  39. package/dist/commands/reflect.js +13 -0
  40. package/dist/commands/replay.d.ts +16 -0
  41. package/dist/commands/replay.js +14 -0
  42. package/dist/commands/setup.js +2 -2
  43. package/dist/commands/sleep.d.ts +1 -0
  44. package/dist/commands/sleep.js +29 -6
  45. package/dist/commands/status.js +6 -6
  46. package/dist/commands/sync-bd.d.ts +10 -0
  47. package/dist/commands/sync-bd.js +9 -0
  48. package/dist/commands/wake.js +53 -35
  49. package/dist/{context-COo8oq1k.d.ts → context-BUGaWpyL.d.ts} +1 -0
  50. package/dist/index.d.ts +56 -20
  51. package/dist/index.js +67 -16
  52. package/hooks/clawvault/HOOK.md +3 -2
  53. package/hooks/clawvault/handler.js +51 -0
  54. package/hooks/clawvault/handler.test.js +20 -0
  55. package/package.json +2 -2
@@ -8,10 +8,10 @@ import {
8
8
  hasQmd,
9
9
  qmdEmbed,
10
10
  qmdUpdate
11
- } from "./chunk-MIIXBNO3.js";
11
+ } from "./chunk-FDJIZKCW.js";
12
12
  import {
13
13
  buildOrUpdateMemoryGraphIndex
14
- } from "./chunk-O5V7SD5C.js";
14
+ } from "./chunk-ZZA73MFY.js";
15
15
 
16
16
  // src/lib/vault.ts
17
17
  import * as fs from "fs";
@@ -58,11 +58,19 @@ var ClawVault = class {
58
58
  fs.mkdirSync(catPath, { recursive: true });
59
59
  }
60
60
  }
61
+ const ledgerDirs = ["ledger/raw", "ledger/observations", "ledger/reflections"];
62
+ for (const dir of ledgerDirs) {
63
+ const dirPath = path.join(vaultPath, dir);
64
+ if (!fs.existsSync(dirPath)) {
65
+ fs.mkdirSync(dirPath, { recursive: true });
66
+ }
67
+ }
61
68
  await this.createTemplates();
62
69
  const readmePath = path.join(vaultPath, "README.md");
63
70
  if (!fs.existsSync(readmePath)) {
64
71
  fs.writeFileSync(readmePath, this.generateReadme());
65
72
  }
73
+ await this.createWelcomeNote();
66
74
  const configPath = path.join(vaultPath, CONFIG_FILE);
67
75
  const meta = {
68
76
  name: this.config.name,
@@ -111,7 +119,7 @@ var ClawVault = class {
111
119
  this.search.clear();
112
120
  const files = await glob("**/*.md", {
113
121
  cwd: this.config.path,
114
- ignore: ["**/node_modules/**", "**/.*"]
122
+ ignore: ["**/node_modules/**", "**/.*", "**/ledger/archive/**"]
115
123
  });
116
124
  for (const file of files) {
117
125
  const doc = await this.loadDocument(file);
@@ -628,6 +636,51 @@ var ClawVault = class {
628
636
  }
629
637
  }
630
638
  }
639
+ async createWelcomeNote() {
640
+ const inboxPath = path.join(this.config.path, "inbox", "welcome.md");
641
+ if (fs.existsSync(inboxPath)) return;
642
+ const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
643
+ const content = `---
644
+ title: "Welcome to ${this.config.name}"
645
+ date: ${now}
646
+ type: fact
647
+ tags: [welcome, getting-started]
648
+ ---
649
+
650
+ # Welcome to ${this.config.name}
651
+
652
+ Your vault is ready. Here's what you can do:
653
+
654
+ ## Quick Start
655
+
656
+ - **Capture a thought:** \`clawvault capture "your note here"\`
657
+ - **Store structured memory:** \`clawvault store --category decisions --title "My Choice" --content "..."\`
658
+ - **Search your vault:** \`clawvault search "query"\`
659
+ - **See your knowledge graph:** \`clawvault graph\`
660
+ - **Get context for a topic:** \`clawvault context "topic"\`
661
+
662
+ ## Vault Structure
663
+
664
+ Your vault organizes memories by type \u2014 decisions, lessons, people, projects, and more.
665
+ Each category is a folder. Each memory is a markdown file with frontmatter.
666
+
667
+ ## Observational Memory
668
+
669
+ When connected to an AI agent (like OpenClaw), your vault can automatically observe
670
+ conversations and extract important memories \u2014 decisions, lessons, commitments \u2014 without
671
+ manual effort.
672
+
673
+ ## Wiki-Links
674
+
675
+ Use \`[[double brackets]]\` to link between notes. Your memory graph tracks these
676
+ connections, building a knowledge network that grows with you.
677
+
678
+ ---
679
+
680
+ *Delete this file anytime. It's just here to say hello.*
681
+ `;
682
+ fs.writeFileSync(inboxPath, content);
683
+ }
631
684
  async syncMemoryGraphIndex(options = {}) {
632
685
  try {
633
686
  await buildOrUpdateMemoryGraphIndex(this.config.path, options);
@@ -635,29 +688,51 @@ var ClawVault = class {
635
688
  }
636
689
  }
637
690
  generateReadme() {
638
- return `# ${this.config.name} \u{1F418}
691
+ const coreCategories = this.config.categories.filter((c) => !["templates", "tasks", "backlog"].includes(c));
692
+ const workCategories = this.config.categories.filter((c) => ["tasks", "backlog"].includes(c));
693
+ return `# ${this.config.name}
639
694
 
640
695
  An elephant never forgets.
641
696
 
642
697
  ## Structure
643
698
 
644
- ${this.config.categories.map((c) => `- \`/${c}/\` \u2014 ${this.getCategoryDescription(c)}`).join("\n")}
699
+ ### Memory Categories
700
+ ${coreCategories.map((c) => `- \`${c}/\` \u2014 ${this.getCategoryDescription(c)}`).join("\n")}
645
701
 
646
- ## Quick Search
702
+ ### Work Tracking
703
+ ${workCategories.map((c) => `- \`${c}/\` \u2014 ${this.getCategoryDescription(c)}`).join("\n")}
704
+
705
+ ### Observational Memory
706
+ - \`ledger/raw/\` \u2014 Raw session transcripts (source of truth)
707
+ - \`ledger/observations/\` \u2014 Compressed observations with importance scores
708
+ - \`ledger/reflections/\` \u2014 Weekly reflection summaries
709
+
710
+ ## Quick Reference
647
711
 
648
712
  \`\`\`bash
713
+ # Capture a thought
714
+ clawvault capture "important insight about X"
715
+
716
+ # Store structured memory
717
+ clawvault store --category decisions --title "Choice" --content "We chose X because..."
718
+
719
+ # Search
649
720
  clawvault search "query"
650
- \`\`\`
721
+ clawvault vsearch "semantic query" # vector search
651
722
 
652
- ## Quick Capture
723
+ # Knowledge graph
724
+ clawvault graph # vault stats
725
+ clawvault context "topic" # graph-aware context retrieval
653
726
 
654
- \`\`\`bash
655
- clawvault store --category inbox --title "note" --content "..."
727
+ # Session lifecycle
728
+ clawvault checkpoint --working-on "task"
729
+ clawvault sleep "what I did" --next "what's next"
730
+ clawvault wake # restore context on startup
656
731
  \`\`\`
657
732
 
658
733
  ---
659
734
 
660
- *Managed by [ClawVault](https://github.com/Versatly/clawvault)*
735
+ *Managed by [ClawVault](https://clawvault.dev)*
661
736
  `;
662
737
  }
663
738
  getCategoryDescription(category) {
@@ -677,7 +752,11 @@ clawvault store --category inbox --title "note" --content "..."
677
752
  goals: "Long-term and short-term objectives",
678
753
  patterns: "Recurring behaviors (\u2192 lessons)",
679
754
  inbox: "Quick capture \u2192 process later",
680
- templates: "Templates for each document type"
755
+ templates: "Templates for each document type",
756
+ agents: "Other agents \u2014 capabilities, trust levels, coordination notes",
757
+ research: "Deep dives, analysis, reference material",
758
+ tasks: "Active work items with status and context",
759
+ backlog: "Future work \u2014 ideas and tasks not yet started"
681
760
  };
682
761
  return descriptions[category] || category;
683
762
  }
@@ -0,0 +1,182 @@
1
+ // src/observer/session-parser.ts
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ var JSONL_SAMPLE_LIMIT = 20;
5
+ var MARKDOWN_SIGNAL_RE = /^(#{1,6}\s|[-*+]\s|>\s)/;
6
+ var MARKDOWN_INLINE_RE = /(\[[^\]]+\]\([^)]+\)|[*_`~])/;
7
+ function normalizeText(value) {
8
+ return value.replace(/\s+/g, " ").trim();
9
+ }
10
+ function extractText(value) {
11
+ if (typeof value === "string") {
12
+ return normalizeText(value);
13
+ }
14
+ if (Array.isArray(value)) {
15
+ const parts = [];
16
+ for (const part of value) {
17
+ const extracted = extractText(part);
18
+ if (extracted) {
19
+ parts.push(extracted);
20
+ }
21
+ }
22
+ return normalizeText(parts.join(" "));
23
+ }
24
+ if (!value || typeof value !== "object") {
25
+ return "";
26
+ }
27
+ const record = value;
28
+ if (typeof record.text === "string") {
29
+ return normalizeText(record.text);
30
+ }
31
+ if (typeof record.content === "string") {
32
+ return normalizeText(record.content);
33
+ }
34
+ return "";
35
+ }
36
+ function normalizeRole(role) {
37
+ if (typeof role !== "string") {
38
+ return "";
39
+ }
40
+ const normalized = role.trim().toLowerCase();
41
+ if (!normalized) {
42
+ return "";
43
+ }
44
+ return normalized;
45
+ }
46
+ function isLikelyJsonMessage(value) {
47
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
48
+ return false;
49
+ }
50
+ const record = value;
51
+ if ("role" in record && "content" in record) {
52
+ return true;
53
+ }
54
+ if (record.type === "message" && record.message && typeof record.message === "object") {
55
+ return true;
56
+ }
57
+ return false;
58
+ }
59
+ function parseJsonLine(line) {
60
+ let parsed;
61
+ try {
62
+ parsed = JSON.parse(line);
63
+ } catch {
64
+ return "";
65
+ }
66
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
67
+ return "";
68
+ }
69
+ const entry = parsed;
70
+ if ("role" in entry && "content" in entry) {
71
+ const role = normalizeRole(entry.role);
72
+ const content = extractText(entry.content);
73
+ if (!content) return "";
74
+ return role ? `${role}: ${content}` : content;
75
+ }
76
+ if (entry.type === "message" && entry.message && typeof entry.message === "object") {
77
+ const message = entry.message;
78
+ const role = normalizeRole(message.role);
79
+ const content = extractText(message.content);
80
+ if (!content) return "";
81
+ return role ? `${role}: ${content}` : content;
82
+ }
83
+ return "";
84
+ }
85
+ function parseJsonLines(raw) {
86
+ const messages = [];
87
+ for (const line of raw.split(/\r?\n/)) {
88
+ const trimmed = line.trim();
89
+ if (!trimmed) continue;
90
+ const parsed = parseJsonLine(trimmed);
91
+ if (parsed) {
92
+ messages.push(parsed);
93
+ }
94
+ }
95
+ return messages;
96
+ }
97
+ function stripMarkdownSyntax(text) {
98
+ return normalizeText(
99
+ text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/[*_`~]/g, "").replace(/<[^>]+>/g, "")
100
+ );
101
+ }
102
+ function normalizeMarkdownLine(line) {
103
+ return stripMarkdownSyntax(
104
+ line.replace(/^>\s*/, "").replace(/^[-*+]\s+/, "").replace(/^#{1,6}\s+/, "")
105
+ );
106
+ }
107
+ function parseMarkdown(raw) {
108
+ const withoutCodeBlocks = raw.replace(/```[\s\S]*?```/g, " ");
109
+ const blocks = withoutCodeBlocks.split(/\r?\n\s*\r?\n/).map((block) => block.trim()).filter(Boolean);
110
+ const messages = [];
111
+ for (const block of blocks) {
112
+ const lines = block.split(/\r?\n/).map((line) => normalizeMarkdownLine(line)).filter(Boolean);
113
+ if (lines.length === 0) {
114
+ continue;
115
+ }
116
+ const joined = stripMarkdownSyntax(lines.join(" "));
117
+ if (!joined) continue;
118
+ const roleMatch = /^(user|assistant|system|tool)\s*:?\s*(.+)$/i.exec(joined);
119
+ if (roleMatch) {
120
+ const role = normalizeRole(roleMatch[1]);
121
+ const content = normalizeText(roleMatch[2]);
122
+ if (content) {
123
+ messages.push(`${role}: ${content}`);
124
+ }
125
+ continue;
126
+ }
127
+ messages.push(joined);
128
+ }
129
+ return messages;
130
+ }
131
+ function parsePlainText(raw) {
132
+ return raw.split(/\r?\n/).map((line) => normalizeText(line)).filter(Boolean);
133
+ }
134
+ function detectSessionFormat(raw, filePath) {
135
+ const nonEmptyLines = raw.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
136
+ if (nonEmptyLines.length === 0) {
137
+ return "plain";
138
+ }
139
+ const sample = nonEmptyLines.slice(0, JSONL_SAMPLE_LIMIT);
140
+ const jsonHits = sample.filter((line) => {
141
+ try {
142
+ const parsed = JSON.parse(line);
143
+ return isLikelyJsonMessage(parsed);
144
+ } catch {
145
+ return false;
146
+ }
147
+ }).length;
148
+ if (jsonHits >= Math.max(1, Math.ceil(sample.length * 0.6))) {
149
+ return "jsonl";
150
+ }
151
+ const ext = path.extname(filePath).toLowerCase();
152
+ if (ext === ".md" || ext === ".markdown") {
153
+ return "markdown";
154
+ }
155
+ const markdownSignals = sample.filter((line) => MARKDOWN_SIGNAL_RE.test(line) || MARKDOWN_INLINE_RE.test(line)).length;
156
+ if (markdownSignals >= Math.max(2, Math.ceil(sample.length * 0.4))) {
157
+ return "markdown";
158
+ }
159
+ return "plain";
160
+ }
161
+ function parseSessionFile(filePath) {
162
+ const resolved = path.resolve(filePath);
163
+ const raw = fs.readFileSync(resolved, "utf-8");
164
+ const format = detectSessionFormat(raw, resolved);
165
+ if (format === "jsonl") {
166
+ const parsed = parseJsonLines(raw);
167
+ if (parsed.length > 0) {
168
+ return parsed;
169
+ }
170
+ }
171
+ if (format === "markdown") {
172
+ const parsed = parseMarkdown(raw);
173
+ if (parsed.length > 0) {
174
+ return parsed;
175
+ }
176
+ }
177
+ return parsePlainText(raw);
178
+ }
179
+
180
+ export {
181
+ parseSessionFile
182
+ };
@@ -0,0 +1,45 @@
1
+ import {
2
+ archiveObservations
3
+ } from "./chunk-MQUJNOHK.js";
4
+ import {
5
+ resolveVaultPath
6
+ } from "./chunk-MXSSG3QU.js";
7
+
8
+ // src/commands/archive.ts
9
+ function parsePositiveInteger(raw, label) {
10
+ const parsed = Number.parseInt(raw, 10);
11
+ if (!Number.isFinite(parsed) || parsed <= 0) {
12
+ throw new Error(`Invalid ${label}: ${raw}`);
13
+ }
14
+ return parsed;
15
+ }
16
+ async function archiveCommand(options) {
17
+ const vaultPath = resolveVaultPath({ explicitPath: options.vaultPath });
18
+ const result = archiveObservations(vaultPath, {
19
+ olderThanDays: options.olderThan,
20
+ dryRun: options.dryRun
21
+ });
22
+ if (result.archived === 0) {
23
+ console.log("No observations matched archive criteria.");
24
+ return;
25
+ }
26
+ if (result.dryRun) {
27
+ console.log(`Dry run: ${result.archived} observation file(s) would be archived.`);
28
+ return;
29
+ }
30
+ console.log(`Archived ${result.archived} observation file(s).`);
31
+ }
32
+ function registerArchiveCommand(program) {
33
+ program.command("archive").description("Archive old observations into ledger/archive").option("--older-than <days>", "Archive observations older than this many days", "14").option("--dry-run", "Show archive candidates without moving files").option("-v, --vault <path>", "Vault path").action(async (rawOptions) => {
34
+ await archiveCommand({
35
+ vaultPath: rawOptions.vault,
36
+ olderThan: parsePositiveInteger(rawOptions.olderThan, "older-than"),
37
+ dryRun: rawOptions.dryRun
38
+ });
39
+ });
40
+ }
41
+
42
+ export {
43
+ archiveCommand,
44
+ registerArchiveCommand
45
+ };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DEFAULT_CATEGORIES,
3
3
  hasQmd
4
- } from "./chunk-MIIXBNO3.js";
4
+ } from "./chunk-FDJIZKCW.js";
5
5
 
6
6
  // src/commands/setup.ts
7
7
  import * as fs from "fs";
@@ -0,0 +1,111 @@
1
+ import {
2
+ DATE_HEADING_RE,
3
+ parseObservationLine,
4
+ renderScoredObservationLine
5
+ } from "./chunk-K6XHCUFL.js";
6
+ import {
7
+ listObservationFiles
8
+ } from "./chunk-Z2XBWN7A.js";
9
+ import {
10
+ resolveVaultPath
11
+ } from "./chunk-MXSSG3QU.js";
12
+
13
+ // src/commands/migrate-observations.ts
14
+ import * as fs from "fs";
15
+ function toBackupPath(filePath) {
16
+ if (filePath.toLowerCase().endsWith(".md")) {
17
+ return `${filePath.slice(0, -3)}.emoji-backup.md`;
18
+ }
19
+ return `${filePath}.emoji-backup`;
20
+ }
21
+ function convertObservationMarkdown(markdown) {
22
+ const lines = markdown.split(/\r?\n/);
23
+ let currentDate = "";
24
+ let changed = false;
25
+ const nextLines = lines.map((line) => {
26
+ const heading = line.match(DATE_HEADING_RE);
27
+ if (heading) {
28
+ currentDate = heading[1];
29
+ return line;
30
+ }
31
+ if (!currentDate) {
32
+ return line;
33
+ }
34
+ const parsed = parseObservationLine(line.trim(), currentDate);
35
+ if (!parsed || parsed.format !== "emoji") {
36
+ return line;
37
+ }
38
+ changed = true;
39
+ return renderScoredObservationLine({
40
+ type: parsed.type,
41
+ confidence: parsed.confidence,
42
+ importance: parsed.importance,
43
+ content: parsed.content
44
+ });
45
+ });
46
+ return {
47
+ converted: nextLines.join("\n"),
48
+ changed
49
+ };
50
+ }
51
+ function migrateObservations(vaultPath, options = {}) {
52
+ const dryRun = options.dryRun ?? false;
53
+ const files = listObservationFiles(vaultPath, {
54
+ includeLegacy: true,
55
+ includeArchive: false,
56
+ dedupeByDate: false
57
+ });
58
+ let migrated = 0;
59
+ let backups = 0;
60
+ for (const entry of files) {
61
+ const raw = fs.readFileSync(entry.path, "utf-8");
62
+ const { converted, changed } = convertObservationMarkdown(raw);
63
+ if (!changed) {
64
+ continue;
65
+ }
66
+ migrated += 1;
67
+ if (dryRun) {
68
+ continue;
69
+ }
70
+ const backupPath = toBackupPath(entry.path);
71
+ if (!fs.existsSync(backupPath)) {
72
+ fs.copyFileSync(entry.path, backupPath);
73
+ backups += 1;
74
+ }
75
+ fs.writeFileSync(entry.path, `${converted.trim()}
76
+ `, "utf-8");
77
+ }
78
+ return {
79
+ scanned: files.length,
80
+ migrated,
81
+ backups,
82
+ dryRun
83
+ };
84
+ }
85
+ async function migrateObservationsCommand(options) {
86
+ const vaultPath = resolveVaultPath({ explicitPath: options.vaultPath });
87
+ const result = migrateObservations(vaultPath, { dryRun: options.dryRun });
88
+ if (result.migrated === 0) {
89
+ console.log("No emoji observations found for migration.");
90
+ return;
91
+ }
92
+ if (result.dryRun) {
93
+ console.log(`Dry run: ${result.migrated} file(s) would be migrated.`);
94
+ return;
95
+ }
96
+ console.log(`Migrated ${result.migrated} file(s); created ${result.backups} backup(s).`);
97
+ }
98
+ function registerMigrateObservationsCommand(program) {
99
+ program.command("migrate-observations").description("Convert legacy emoji observations to scored format with backups").option("--dry-run", "Preview migration without writing files").option("-v, --vault <path>", "Vault path").action(async (rawOptions) => {
100
+ await migrateObservationsCommand({
101
+ vaultPath: rawOptions.vault,
102
+ dryRun: rawOptions.dryRun
103
+ });
104
+ });
105
+ }
106
+
107
+ export {
108
+ migrateObservations,
109
+ migrateObservationsCommand,
110
+ registerMigrateObservationsCommand
111
+ };