clawvault 2.5.1 → 2.5.3

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 (88) hide show
  1. package/README.md +159 -199
  2. package/bin/clawvault.js +111 -111
  3. package/bin/command-registration.test.js +166 -165
  4. package/bin/command-runtime.js +93 -77
  5. package/bin/command-runtime.test.js +154 -102
  6. package/bin/help-contract.test.js +39 -28
  7. package/bin/register-config-commands.js +153 -153
  8. package/bin/register-config-route-commands.test.js +121 -121
  9. package/bin/register-core-commands.js +237 -237
  10. package/bin/register-kanban-commands.js +56 -56
  11. package/bin/register-kanban-commands.test.js +83 -83
  12. package/bin/register-maintenance-commands.js +282 -248
  13. package/bin/register-project-commands.js +209 -209
  14. package/bin/register-project-commands.test.js +206 -201
  15. package/bin/register-query-commands.js +317 -312
  16. package/bin/register-query-commands.test.js +65 -0
  17. package/bin/register-resilience-commands.js +182 -182
  18. package/bin/register-resilience-commands.test.js +81 -81
  19. package/bin/register-route-commands.js +114 -114
  20. package/bin/register-session-lifecycle-commands.js +206 -206
  21. package/bin/register-tailscale-commands.js +106 -106
  22. package/bin/register-task-commands.js +348 -348
  23. package/bin/register-task-commands.test.js +69 -69
  24. package/bin/register-template-commands.js +72 -72
  25. package/bin/register-vault-operations-commands.js +300 -300
  26. package/bin/test-helpers/cli-command-fixtures.js +119 -119
  27. package/dashboard/lib/graph-diff.js +104 -104
  28. package/dashboard/lib/graph-diff.test.js +75 -75
  29. package/dashboard/lib/vault-parser.js +556 -556
  30. package/dashboard/lib/vault-parser.test.js +254 -254
  31. package/dashboard/public/app.js +796 -796
  32. package/dashboard/public/index.html +52 -52
  33. package/dashboard/public/styles.css +221 -221
  34. package/dashboard/server.js +374 -374
  35. package/dist/{chunk-G3OQJ2NQ.js → chunk-2YDBJS7M.js} +1 -1
  36. package/dist/chunk-3FP5BJ42.js +88 -0
  37. package/dist/{chunk-C3PF7WBA.js → chunk-4IV3R2F5.js} +2 -2
  38. package/dist/{chunk-7OHQFMJK.js → chunk-AY4PGUVL.js} +5 -4
  39. package/dist/chunk-FG6RJMCN.js +33 -0
  40. package/dist/{chunk-WIICLBNF.js → chunk-GFJ3LIIB.js} +1 -1
  41. package/dist/chunk-IZEY5S74.js +541 -0
  42. package/dist/chunk-LMEMZGUV.js +332 -0
  43. package/dist/{chunk-6RQPD7X6.js → chunk-M25QVSJM.js} +4 -3
  44. package/dist/{chunk-6B3JWM7J.js → chunk-O7XHXF7F.js} +34 -7
  45. package/dist/chunk-OSMS7QIG.js +406 -0
  46. package/dist/{chunk-PAYUH64O.js → chunk-QVMXF7FY.js} +11 -1
  47. package/dist/{chunk-TMZMN7OS.js → chunk-S2IG7VNM.js} +24 -12
  48. package/dist/{chunk-LMCC5OC7.js → chunk-TPDH3JPP.js} +1 -1
  49. package/dist/cli/index.d.ts +5 -0
  50. package/dist/cli/index.js +31 -0
  51. package/dist/commands/canvas.js +3 -3
  52. package/dist/commands/compat.js +1 -1
  53. package/dist/commands/context.js +4 -4
  54. package/dist/commands/doctor.js +16 -309
  55. package/dist/commands/embed.d.ts +17 -0
  56. package/dist/commands/embed.js +10 -0
  57. package/dist/commands/migrate-observations.js +2 -2
  58. package/dist/commands/observe.d.ts +1 -0
  59. package/dist/commands/observe.js +7 -6
  60. package/dist/commands/rebuild.js +5 -5
  61. package/dist/commands/reflect.js +3 -3
  62. package/dist/commands/replay.js +7 -7
  63. package/dist/commands/setup.d.ts +1 -0
  64. package/dist/commands/setup.js +2 -2
  65. package/dist/commands/sleep.d.ts +2 -1
  66. package/dist/commands/sleep.js +15 -15
  67. package/dist/commands/status.d.ts +9 -1
  68. package/dist/commands/status.js +33 -8
  69. package/dist/commands/wake.d.ts +1 -1
  70. package/dist/commands/wake.js +6 -6
  71. package/dist/index.d.ts +82 -5
  72. package/dist/index.js +127 -105
  73. package/dist/{types-jjuYN2Xn.d.ts → types-C74wgGL1.d.ts} +2 -0
  74. package/hooks/clawvault/HOOK.md +83 -74
  75. package/hooks/clawvault/handler.js +816 -812
  76. package/hooks/clawvault/handler.test.js +263 -263
  77. package/package.json +94 -125
  78. package/templates/checkpoint.md +19 -19
  79. package/templates/daily-note.md +19 -19
  80. package/templates/daily.md +19 -19
  81. package/templates/decision.md +17 -17
  82. package/templates/handoff.md +19 -19
  83. package/templates/lesson.md +16 -16
  84. package/templates/person.md +19 -19
  85. package/templates/project.md +23 -23
  86. package/dist/chunk-2RK2AG32.js +0 -743
  87. package/dist/{chunk-FW465EEA.js → chunk-VXEOHTSL.js} +3 -3
  88. package/dist/{chunk-KCCHROBR.js → chunk-YOSEUUNB.js} +4 -4
@@ -0,0 +1,406 @@
1
+ import {
2
+ parseSessionFile
3
+ } from "./chunk-P5EPF6MB.js";
4
+ import {
5
+ observeActiveSessions
6
+ } from "./chunk-IZEY5S74.js";
7
+ import {
8
+ Observer
9
+ } from "./chunk-S2IG7VNM.js";
10
+ import {
11
+ resolveVaultPath
12
+ } from "./chunk-MXSSG3QU.js";
13
+ import {
14
+ getObservationPath
15
+ } from "./chunk-Z2XBWN7A.js";
16
+
17
+ // src/commands/observe.ts
18
+ import * as fs2 from "fs";
19
+ import * as path2 from "path";
20
+ import { spawn } from "child_process";
21
+
22
+ // src/observer/watcher.ts
23
+ import * as fs from "fs";
24
+ import * as path from "path";
25
+ import chokidar from "chokidar";
26
+ var DEFAULT_FLUSH_THRESHOLD_CHARS = 500;
27
+ var SessionWatcher = class {
28
+ watchPath;
29
+ observer;
30
+ ignoreInitial;
31
+ debounceMs;
32
+ flushThresholdChars;
33
+ watcher = null;
34
+ fileOffsets = /* @__PURE__ */ new Map();
35
+ pendingPaths = /* @__PURE__ */ new Set();
36
+ debounceTimer = null;
37
+ processingQueue = Promise.resolve();
38
+ bufferedChars = 0;
39
+ constructor(watchPath, observer, options = {}) {
40
+ this.watchPath = path.resolve(watchPath);
41
+ this.observer = observer;
42
+ this.ignoreInitial = options.ignoreInitial ?? false;
43
+ this.debounceMs = options.debounceMs ?? 500;
44
+ this.flushThresholdChars = Math.max(1, options.flushThresholdChars ?? DEFAULT_FLUSH_THRESHOLD_CHARS);
45
+ }
46
+ async start() {
47
+ if (!fs.existsSync(this.watchPath)) {
48
+ throw new Error(`Watch path does not exist: ${this.watchPath}`);
49
+ }
50
+ this.watcher = chokidar.watch(this.watchPath, {
51
+ persistent: true,
52
+ ignoreInitial: this.ignoreInitial,
53
+ awaitWriteFinish: {
54
+ stabilityThreshold: 120,
55
+ pollInterval: 30
56
+ }
57
+ });
58
+ const enqueue = (changedPath) => {
59
+ this.pendingPaths.add(path.resolve(changedPath));
60
+ this.scheduleDrain();
61
+ };
62
+ this.watcher.on("add", enqueue);
63
+ this.watcher.on("change", enqueue);
64
+ this.watcher.on("unlink", (deletedPath) => {
65
+ const resolved = path.resolve(deletedPath);
66
+ this.fileOffsets.delete(resolved);
67
+ this.pendingPaths.delete(resolved);
68
+ });
69
+ await new Promise((resolve3, reject) => {
70
+ this.watcher?.once("ready", () => resolve3());
71
+ this.watcher?.once("error", (error) => reject(error));
72
+ });
73
+ if (this.ignoreInitial) {
74
+ this.primeInitialOffsets();
75
+ }
76
+ }
77
+ async stop() {
78
+ if (this.debounceTimer) {
79
+ clearTimeout(this.debounceTimer);
80
+ this.debounceTimer = null;
81
+ this.drainPendingPaths();
82
+ }
83
+ await this.processingQueue.catch(() => void 0);
84
+ if (this.bufferedChars > 0) {
85
+ await this.observer.flush();
86
+ this.bufferedChars = 0;
87
+ }
88
+ this.pendingPaths.clear();
89
+ await this.watcher?.close();
90
+ this.watcher = null;
91
+ }
92
+ scheduleDrain() {
93
+ if (this.debounceTimer) {
94
+ clearTimeout(this.debounceTimer);
95
+ }
96
+ this.debounceTimer = setTimeout(() => {
97
+ this.debounceTimer = null;
98
+ this.drainPendingPaths();
99
+ }, this.debounceMs);
100
+ }
101
+ drainPendingPaths() {
102
+ const nextPaths = [...this.pendingPaths];
103
+ this.pendingPaths.clear();
104
+ for (const changedPath of nextPaths) {
105
+ this.processingQueue = this.processingQueue.then(() => this.consumeFile(changedPath)).catch(() => void 0);
106
+ }
107
+ }
108
+ async consumeFile(filePath) {
109
+ const resolved = path.resolve(filePath);
110
+ if (!fs.existsSync(resolved)) {
111
+ return;
112
+ }
113
+ const stats = fs.statSync(resolved);
114
+ if (!stats.isFile()) {
115
+ return;
116
+ }
117
+ const previousOffset = this.fileOffsets.get(resolved) ?? 0;
118
+ const startOffset = stats.size < previousOffset ? 0 : previousOffset;
119
+ if (stats.size <= startOffset) {
120
+ this.fileOffsets.set(resolved, stats.size);
121
+ return;
122
+ }
123
+ const bytesToRead = stats.size - startOffset;
124
+ const buffer = Buffer.alloc(bytesToRead);
125
+ const fd = fs.openSync(resolved, "r");
126
+ try {
127
+ fs.readSync(fd, buffer, 0, bytesToRead, startOffset);
128
+ } finally {
129
+ fs.closeSync(fd);
130
+ }
131
+ this.fileOffsets.set(resolved, stats.size);
132
+ const chunk = buffer.toString("utf-8");
133
+ const messages = chunk.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
134
+ if (messages.length === 0) {
135
+ return;
136
+ }
137
+ await this.observer.processMessages(messages);
138
+ this.bufferedChars += chunk.length;
139
+ if (this.bufferedChars >= this.flushThresholdChars) {
140
+ await this.observer.flush();
141
+ this.bufferedChars = 0;
142
+ }
143
+ }
144
+ primeInitialOffsets() {
145
+ for (const filePath of this.collectFiles(this.watchPath)) {
146
+ try {
147
+ const stats = fs.statSync(filePath);
148
+ if (stats.isFile()) {
149
+ this.fileOffsets.set(filePath, stats.size);
150
+ }
151
+ } catch {
152
+ }
153
+ }
154
+ }
155
+ collectFiles(targetPath) {
156
+ if (!fs.existsSync(targetPath)) {
157
+ return [];
158
+ }
159
+ const resolved = path.resolve(targetPath);
160
+ const stats = fs.statSync(resolved);
161
+ if (stats.isFile()) {
162
+ return [resolved];
163
+ }
164
+ if (!stats.isDirectory()) {
165
+ return [];
166
+ }
167
+ const collected = [];
168
+ for (const entry of fs.readdirSync(resolved, { withFileTypes: true })) {
169
+ const childPath = path.join(resolved, entry.name);
170
+ if (entry.isDirectory()) {
171
+ collected.push(...this.collectFiles(childPath));
172
+ } else if (entry.isFile()) {
173
+ collected.push(path.resolve(childPath));
174
+ }
175
+ }
176
+ return collected;
177
+ }
178
+ };
179
+
180
+ // src/commands/observe.ts
181
+ var ONE_KIB = 1024;
182
+ var ONE_MIB = ONE_KIB * ONE_KIB;
183
+ function parsePositiveInteger(raw, optionName) {
184
+ const parsed = Number.parseInt(raw, 10);
185
+ if (!Number.isFinite(parsed) || parsed <= 0) {
186
+ throw new Error(`Invalid ${optionName}: ${raw}`);
187
+ }
188
+ return parsed;
189
+ }
190
+ function buildDaemonArgs(options) {
191
+ const cliPath = process.argv[1];
192
+ if (!cliPath) {
193
+ throw new Error("Unable to resolve CLI script path for daemon mode.");
194
+ }
195
+ const args = [cliPath, "observe"];
196
+ if (options.watch) {
197
+ args.push("--watch", options.watch);
198
+ }
199
+ if (options.threshold) {
200
+ args.push("--threshold", String(options.threshold));
201
+ }
202
+ if (options.reflectThreshold) {
203
+ args.push("--reflect-threshold", String(options.reflectThreshold));
204
+ }
205
+ if (options.model) {
206
+ args.push("--model", options.model);
207
+ }
208
+ if (options.extractTasks === false) {
209
+ args.push("--no-extract-tasks");
210
+ }
211
+ if (options.vaultPath) {
212
+ args.push("--vault", options.vaultPath);
213
+ }
214
+ return args;
215
+ }
216
+ function formatByteSummary(bytes) {
217
+ const normalized = Number.isFinite(bytes) ? Math.max(0, bytes) : 0;
218
+ if (normalized === 0) {
219
+ return "0KB";
220
+ }
221
+ if (normalized >= ONE_MIB) {
222
+ return `${(normalized / ONE_MIB).toFixed(1)}MB`;
223
+ }
224
+ return `${Math.max(1, Math.round(normalized / ONE_KIB))}KB`;
225
+ }
226
+ function formatCronSummary(result) {
227
+ const decisionCount = result.routedCounts.decisions ?? 0;
228
+ return `observed ${result.observedSessions} sessions, ${formatByteSummary(result.observedNewBytes)} new content, ${decisionCount} decision${decisionCount === 1 ? "" : "s"} extracted`;
229
+ }
230
+ async function runOneShotCompression(observer, sourceFile, vaultPath) {
231
+ const resolved = path2.resolve(sourceFile);
232
+ if (!fs2.existsSync(resolved) || !fs2.statSync(resolved).isFile()) {
233
+ throw new Error(`Conversation file not found: ${resolved}`);
234
+ }
235
+ const messages = parseSessionFile(resolved);
236
+ const transcriptStat = fs2.statSync(resolved);
237
+ await observer.processMessages(messages, {
238
+ source: "openclaw",
239
+ transcriptId: path2.basename(resolved),
240
+ timestamp: transcriptStat.mtime
241
+ });
242
+ const { observations, routingSummary } = await observer.flush();
243
+ const outputPath = getObservationPath(vaultPath, /* @__PURE__ */ new Date());
244
+ console.log(`Observations updated: ${outputPath}`);
245
+ if (routingSummary) {
246
+ console.log(routingSummary);
247
+ }
248
+ }
249
+ async function watchSessions(observer, watchPath) {
250
+ const watcher = new SessionWatcher(watchPath, observer);
251
+ await watcher.start();
252
+ console.log(`Watching session updates: ${watchPath}`);
253
+ await new Promise((resolve3) => {
254
+ const shutdown = async () => {
255
+ process.off("SIGINT", onSigInt);
256
+ process.off("SIGTERM", onSigTerm);
257
+ await watcher.stop();
258
+ resolve3();
259
+ };
260
+ const onSigInt = () => {
261
+ void shutdown();
262
+ };
263
+ const onSigTerm = () => {
264
+ void shutdown();
265
+ };
266
+ process.once("SIGINT", onSigInt);
267
+ process.once("SIGTERM", onSigTerm);
268
+ });
269
+ }
270
+ async function observeCommand(options) {
271
+ if (options.cron && (options.active || options.watch || options.compress || options.daemon)) {
272
+ throw new Error("--cron cannot be combined with --active, --watch, --compress, or --daemon.");
273
+ }
274
+ if (options.cron && options.dryRun) {
275
+ throw new Error("--cron cannot be combined with --dry-run.");
276
+ }
277
+ if (options.active && (options.watch || options.compress || options.daemon)) {
278
+ throw new Error("--active cannot be combined with --watch, --compress, or --daemon.");
279
+ }
280
+ if (options.compress && options.daemon) {
281
+ throw new Error("--compress cannot be combined with --daemon.");
282
+ }
283
+ const vaultPath = resolveVaultPath({ explicitPath: options.vaultPath });
284
+ if (options.active || options.cron) {
285
+ const result = await observeActiveSessions({
286
+ vaultPath,
287
+ agentId: options.agent,
288
+ minNewBytes: options.minNew,
289
+ sessionsDir: options.sessionsDir,
290
+ dryRun: options.dryRun,
291
+ threshold: options.threshold,
292
+ reflectThreshold: options.reflectThreshold,
293
+ model: options.model,
294
+ extractTasks: options.extractTasks
295
+ });
296
+ const failedSessionCount = result.failedSessionCount ?? 0;
297
+ if (options.cron) {
298
+ if (failedSessionCount > 0) {
299
+ const firstFailure = result.failedSessions[0];
300
+ if (firstFailure) {
301
+ throw new Error(
302
+ `observer failed for ${failedSessionCount} session(s); first error: ${firstFailure.sessionKey} - ${firstFailure.error}`
303
+ );
304
+ }
305
+ throw new Error(`observer failed for ${failedSessionCount} session(s).`);
306
+ }
307
+ if (result.candidateSessions === 0) {
308
+ console.log("nothing new");
309
+ return;
310
+ }
311
+ console.log(formatCronSummary({
312
+ observedSessions: result.observedSessions,
313
+ observedNewBytes: result.observedNewBytes ?? result.totalNewBytes,
314
+ routedCounts: result.routedCounts ?? {}
315
+ }));
316
+ return;
317
+ }
318
+ if (result.candidateSessions === 0) {
319
+ console.log(`No active sessions crossed threshold (${result.checkedSessions} checked).`);
320
+ return;
321
+ }
322
+ if (result.dryRun) {
323
+ console.log(
324
+ `Dry run: ${result.candidateSessions} session(s) would be observed (${result.totalNewBytes} new bytes).`
325
+ );
326
+ for (const candidate of result.candidates) {
327
+ console.log(
328
+ `- ${candidate.sessionKey} [${candidate.sourceLabel}] \u0394${candidate.newBytes}B (threshold ${candidate.thresholdBytes}B)`
329
+ );
330
+ }
331
+ return;
332
+ }
333
+ console.log(
334
+ `Active observation complete: ${result.observedSessions}/${result.candidateSessions} session(s) observed.${failedSessionCount > 0 ? ` ${failedSessionCount} failed.` : ""}`
335
+ );
336
+ if (failedSessionCount > 0) {
337
+ for (const failure of result.failedSessions) {
338
+ console.error(
339
+ `[observer] session failed ${failure.sessionKey} (${failure.sessionId}): ${failure.error}`
340
+ );
341
+ }
342
+ }
343
+ return;
344
+ }
345
+ const observer = new Observer(vaultPath, {
346
+ tokenThreshold: options.threshold,
347
+ reflectThreshold: options.reflectThreshold,
348
+ model: options.model,
349
+ extractTasks: options.extractTasks
350
+ });
351
+ if (options.compress) {
352
+ await runOneShotCompression(observer, options.compress, vaultPath);
353
+ return;
354
+ }
355
+ let watchPath = options.watch ? path2.resolve(options.watch) : "";
356
+ if (!watchPath && options.daemon) {
357
+ watchPath = path2.join(vaultPath, "sessions");
358
+ }
359
+ if (!watchPath) {
360
+ throw new Error("Either --watch or --compress must be provided.");
361
+ }
362
+ if (!fs2.existsSync(watchPath)) {
363
+ if (options.daemon && !options.watch) {
364
+ fs2.mkdirSync(watchPath, { recursive: true });
365
+ } else {
366
+ throw new Error(`Watch path does not exist: ${watchPath}`);
367
+ }
368
+ }
369
+ if (options.daemon) {
370
+ const daemonArgs = buildDaemonArgs({ ...options, watch: watchPath, vaultPath });
371
+ const child = spawn(process.execPath, daemonArgs, {
372
+ detached: true,
373
+ stdio: "ignore"
374
+ });
375
+ child.unref();
376
+ console.log(`Observer daemon started (pid: ${child.pid})`);
377
+ return;
378
+ }
379
+ await watchSessions(observer, watchPath);
380
+ }
381
+ function registerObserveCommand(program) {
382
+ program.command("observe").description("Observe session files and build observational memory").option("--watch <path>", "Watch session file or directory").option("--active", "Observe active OpenClaw sessions incrementally").option("--cron", "Run one-shot active observation for cron hooks").option("--agent <id>", "OpenClaw agent ID (default: OPENCLAW_AGENT_ID or clawdious)").option("--min-new <bytes>", "Override minimum new-content threshold in bytes").option("--sessions-dir <path>", "Override OpenClaw sessions directory").option("--dry-run", "Show active observation candidates without compressing").option("--threshold <n>", "Compression token threshold", "30000").option("--reflect-threshold <n>", "Reflection token threshold", "40000").option("--model <model>", "LLM model override").option("--extract-tasks", "Extract task-like observations into backlog", true).option("--no-extract-tasks", "Disable task extraction from observations").option("--compress <file>", "One-shot compression for a conversation file").option("--daemon", "Run in detached background mode").option("-v, --vault <path>", "Vault path").action(async (rawOptions) => {
383
+ await observeCommand({
384
+ watch: rawOptions.watch,
385
+ active: rawOptions.active,
386
+ cron: rawOptions.cron,
387
+ agent: rawOptions.agent,
388
+ minNew: rawOptions.minNew ? parsePositiveInteger(rawOptions.minNew, "min-new") : void 0,
389
+ sessionsDir: rawOptions.sessionsDir,
390
+ dryRun: rawOptions.dryRun,
391
+ threshold: parsePositiveInteger(rawOptions.threshold, "threshold"),
392
+ reflectThreshold: parsePositiveInteger(rawOptions.reflectThreshold, "reflect-threshold"),
393
+ model: rawOptions.model,
394
+ extractTasks: rawOptions.extractTasks,
395
+ compress: rawOptions.compress,
396
+ daemon: rawOptions.daemon,
397
+ vaultPath: rawOptions.vault
398
+ });
399
+ });
400
+ }
401
+
402
+ export {
403
+ SessionWatcher,
404
+ observeCommand,
405
+ registerObserveCommand
406
+ };
@@ -14,6 +14,16 @@ function readOptionalFile(filePath) {
14
14
  return null;
15
15
  }
16
16
  }
17
+ function findPackageRoot() {
18
+ let dir = path.dirname(fileURLToPath(import.meta.url));
19
+ while (dir !== path.dirname(dir)) {
20
+ if (fs.existsSync(path.join(dir, "package.json"))) {
21
+ return dir;
22
+ }
23
+ dir = path.dirname(dir);
24
+ }
25
+ return path.dirname(fileURLToPath(import.meta.url));
26
+ }
17
27
  function resolveProjectFile(relativePath, baseDir) {
18
28
  if (baseDir) {
19
29
  return path.resolve(baseDir, relativePath);
@@ -22,7 +32,7 @@ function resolveProjectFile(relativePath, baseDir) {
22
32
  if (fs.existsSync(fromCwd)) {
23
33
  return fromCwd;
24
34
  }
25
- return fileURLToPath(new URL(`../../${relativePath}`, import.meta.url));
35
+ return path.resolve(findPackageRoot(), relativePath);
26
36
  }
27
37
  function checkOpenClawCli() {
28
38
  const result = spawnSync("openclaw", ["--version"], { stdio: "ignore" });
@@ -1,18 +1,6 @@
1
1
  import {
2
2
  listProjects
3
3
  } from "./chunk-5GZFTAL7.js";
4
- import {
5
- listConfig,
6
- listRouteRules,
7
- matchRouteRule
8
- } from "./chunk-ITPEXLHA.js";
9
- import {
10
- createBacklogItem,
11
- listBacklogItems,
12
- listTasks,
13
- updateBacklogItem,
14
- updateTask
15
- } from "./chunk-IOALNTAN.js";
16
4
  import {
17
5
  DATE_HEADING_RE,
18
6
  inferObservationType,
@@ -22,6 +10,11 @@ import {
22
10
  renderObservationMarkdown,
23
11
  renderScoredObservationLine
24
12
  } from "./chunk-FHFUXL6G.js";
13
+ import {
14
+ listConfig,
15
+ listRouteRules,
16
+ matchRouteRule
17
+ } from "./chunk-ITPEXLHA.js";
25
18
  import {
26
19
  ensureLedgerStructure,
27
20
  ensureParentDir,
@@ -30,6 +23,13 @@ import {
30
23
  getRawTranscriptPath,
31
24
  toDateKey
32
25
  } from "./chunk-Z2XBWN7A.js";
26
+ import {
27
+ createBacklogItem,
28
+ listBacklogItems,
29
+ listTasks,
30
+ updateBacklogItem,
31
+ updateTask
32
+ } from "./chunk-IOALNTAN.js";
33
33
 
34
34
  // src/observer/compressor.ts
35
35
  var OPENAI_BASE_URL = "https://api.openai.com/v1";
@@ -759,6 +759,8 @@ var TYPE_TO_CATEGORY = {
759
759
  relationship: "people",
760
760
  project: "projects"
761
761
  };
762
+ var PAST_TENSE_TASK_HINT_RE = /\b(completed|shipped|deployed|fixed|merged|finished|resolved|closed)\b/i;
763
+ var FUTURE_TASK_HINT_RE = /\b(need to|should|todo|must|plan to)\b/i;
762
764
  var Router = class {
763
765
  vaultPath;
764
766
  extractTasks;
@@ -815,6 +817,10 @@ var Router = class {
815
817
  return type === "task" || type === "todo" || type === "commitment-unresolved";
816
818
  }
817
819
  routeTaskObservation(item, context, knownWorkItems) {
820
+ if (this.shouldSkipCompletedTaskCandidate(item.content)) {
821
+ console.log("[observer] skipped likely-completed task candidate");
822
+ return { routedItem: null, dedupHit: false };
823
+ }
818
824
  const title = this.deriveTaskTitle(item.content, item.type);
819
825
  if (!title) {
820
826
  return { routedItem: null, dedupHit: false };
@@ -943,6 +949,12 @@ var Router = class {
943
949
  title = title.replace(/\s+/g, " ").replace(/^[^a-zA-Z0-9]+/, "").replace(/[.?!:;,]+$/, "").trim();
944
950
  return title.slice(0, 120);
945
951
  }
952
+ shouldSkipCompletedTaskCandidate(content) {
953
+ if (!PAST_TENSE_TASK_HINT_RE.test(content)) {
954
+ return false;
955
+ }
956
+ return !FUTURE_TASK_HINT_RE.test(content);
957
+ }
946
958
  buildTaskContextContent(item, context) {
947
959
  const lines = ["Auto-extracted by observer from session transcript."];
948
960
  if (context.sessionKey) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Observer
3
- } from "./chunk-TMZMN7OS.js";
3
+ } from "./chunk-S2IG7VNM.js";
4
4
  import {
5
5
  resolveVaultPath
6
6
  } from "./chunk-MXSSG3QU.js";
@@ -0,0 +1,5 @@
1
+ import { Command } from 'commander';
2
+
3
+ declare function registerCliCommands(program: Command): Command;
4
+
5
+ export { registerCliCommands };
@@ -0,0 +1,31 @@
1
+ import {
2
+ registerCliCommands
3
+ } from "../chunk-FG6RJMCN.js";
4
+ import "../chunk-NZ4ZZNSR.js";
5
+ import "../chunk-4GBPTBFJ.js";
6
+ import "../chunk-CLE2HHNT.js";
7
+ import "../chunk-OSMS7QIG.js";
8
+ import "../chunk-P5EPF6MB.js";
9
+ import "../chunk-2YDBJS7M.js";
10
+ import "../chunk-YOSEUUNB.js";
11
+ import "../chunk-GFJ3LIIB.js";
12
+ import "../chunk-IZEY5S74.js";
13
+ import "../chunk-HRLWZGMA.js";
14
+ import "../chunk-S2IG7VNM.js";
15
+ import "../chunk-5GZFTAL7.js";
16
+ import "../chunk-AY4PGUVL.js";
17
+ import "../chunk-FHFUXL6G.js";
18
+ import "../chunk-3FP5BJ42.js";
19
+ import "../chunk-O7XHXF7F.js";
20
+ import "../chunk-GSD4ALSI.js";
21
+ import "../chunk-K3CDT7IH.js";
22
+ import "../chunk-ITPEXLHA.js";
23
+ import "../chunk-2CDEETQN.js";
24
+ import "../chunk-MQUJNOHK.js";
25
+ import "../chunk-MXSSG3QU.js";
26
+ import "../chunk-ZZA73MFY.js";
27
+ import "../chunk-Z2XBWN7A.js";
28
+ import "../chunk-IOALNTAN.js";
29
+ export {
30
+ registerCliCommands
31
+ };
@@ -7,15 +7,15 @@ import {
7
7
  positionGroupsVertically,
8
8
  truncateText
9
9
  } from "../chunk-MDIH26GC.js";
10
- import {
11
- listTasks
12
- } from "../chunk-IOALNTAN.js";
13
10
  import {
14
11
  loadMemoryGraphIndex
15
12
  } from "../chunk-ZZA73MFY.js";
16
13
  import {
17
14
  listObservationFiles
18
15
  } from "../chunk-Z2XBWN7A.js";
16
+ import {
17
+ listTasks
18
+ } from "../chunk-IOALNTAN.js";
19
19
 
20
20
  // src/commands/canvas.ts
21
21
  import * as fs2 from "fs";
@@ -2,7 +2,7 @@ import {
2
2
  checkOpenClawCompatibility,
3
3
  compatCommand,
4
4
  compatibilityExitCode
5
- } from "../chunk-PAYUH64O.js";
5
+ } from "../chunk-QVMXF7FY.js";
6
6
  export {
7
7
  checkOpenClawCompatibility,
8
8
  compatCommand,
@@ -3,11 +3,11 @@ import {
3
3
  contextCommand,
4
4
  formatContextMarkdown,
5
5
  registerContextCommand
6
- } from "../chunk-WIICLBNF.js";
7
- import "../chunk-7OHQFMJK.js";
8
- import "../chunk-6B3JWM7J.js";
9
- import "../chunk-2CDEETQN.js";
6
+ } from "../chunk-GFJ3LIIB.js";
7
+ import "../chunk-AY4PGUVL.js";
10
8
  import "../chunk-FHFUXL6G.js";
9
+ import "../chunk-O7XHXF7F.js";
10
+ import "../chunk-2CDEETQN.js";
11
11
  import "../chunk-ZZA73MFY.js";
12
12
  import "../chunk-Z2XBWN7A.js";
13
13
  export {