@veewo/claw-core 0.2.14 → 0.2.16

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/README.md CHANGED
@@ -1,16 +1,16 @@
1
- # @veewo/claw-core
2
-
3
- `@veewo/claw-core` is the shared workflow engine behind `claw-kit`.
4
-
5
- It provides the core primitives that let `.claw` act as a project-level working surface for config resolution, planning, project recall, truth ingestion, and task retention.
6
-
7
- ## Responsibilities
8
-
9
- - project config normalization and runtime resolution
10
- - plan lifecycle and workflow guidance primitives
11
- - project memory and search indexing behavior
12
- - truth ingestion and retention mechanics
13
-
14
- ## Repository
15
-
16
- - [claw-kit](https://github.com/chanyuenpang/claw-kit)
1
+ # @veewo/claw-core
2
+
3
+ `@veewo/claw-core` is the shared workflow engine behind `claw-kit`.
4
+
5
+ It provides the core primitives that let `.claw` act as a project-level working surface for config resolution, planning, project recall, truth ingestion, and task retention.
6
+
7
+ ## Responsibilities
8
+
9
+ - project config normalization and runtime resolution
10
+ - plan lifecycle and workflow guidance primitives
11
+ - project memory and search indexing behavior
12
+ - truth ingestion and retention mechanics
13
+
14
+ ## Repository
15
+
16
+ - [claw-kit](https://github.com/chanyuenpang/claw-kit)
@@ -5,11 +5,11 @@ import { spawnSync } from "node:child_process";
5
5
  import { createHash } from "node:crypto";
6
6
  import { DatabaseSync } from "node:sqlite";
7
7
  import { fileURLToPath } from "node:url";
8
- import { resolveProjectContext, resolveTaskContext } from "./context.js";
8
+ import { resolveProjectContext } from "./context.js";
9
9
  import { resolveDefaultLocalEmbeddingDimensions } from "./embedding-defaults.js";
10
10
  import { requestPersistentEmbedding } from "./embedding-daemon-protocol.js";
11
11
  import { ClawError } from "./errors.js";
12
- import { readJsonFile, readTextFile } from "./io.js";
12
+ import { readTextFile } from "./io.js";
13
13
  import { analyzeKnowledgeDocument } from "./knowledge-document.js";
14
14
  import { buildProjectKeywordSearchPlan, buildProjectQueryIntent } from "./memory-query.js";
15
15
  const DEFAULT_PROJECT_REFRESH_FILE_LIMIT = 100;
@@ -28,32 +28,27 @@ const DEFAULT_PERSISTENT_SEARCH_DATABASE_LIMIT = 2;
28
28
  const persistentMemoryDatabases = new Map();
29
29
  const projectVectorRowsByDatabase = new WeakMap();
30
30
  export function buildMemoryIndex(input) {
31
- const { scope, project, task } = resolveMemoryScope(input);
31
+ const project = resolveProjectContext(input.cwd);
32
32
  if (!isProjectMemoryEnabled(project)) {
33
33
  return {
34
- scope,
35
- storePath: getMemoryStorePath(project, scope, task),
34
+ scope: "project",
35
+ storePath: getMemoryStorePath(project),
36
36
  indexedCount: 0,
37
37
  processedFileCount: 0,
38
38
  pendingFileCount: 0,
39
39
  sources: [],
40
- ...(scope === "project" ? { embedding: null, vectorIndex: null } : {}),
40
+ embedding: null,
41
+ vectorIndex: null,
41
42
  };
42
43
  }
43
- const storePath = getMemoryStorePath(project, scope, task);
44
- const sources = collectMemorySources(project, scope, task);
45
- const embedding = scope === "project" ? resolveProjectMemoryEmbeddingConfig(project) : undefined;
44
+ const storePath = getMemoryStorePath(project);
45
+ const sources = collectProjectMemorySources(project);
46
+ const embedding = resolveProjectMemoryEmbeddingConfig(project);
46
47
  fs.mkdirSync(path.dirname(storePath), { recursive: true });
47
48
  return withMemoryDatabase(storePath, "index refresh", "write", (db) => {
48
49
  prepareSchema(db);
49
- const syncResult = scope === "project"
50
- ? syncProjectMemoryIndex(db, sources, embedding ?? null, input.maxFiles ?? DEFAULT_PROJECT_REFRESH_FILE_LIMIT)
51
- : {
52
- vectorIndex: rebuildTaskMemoryIndex(db, sources),
53
- processedFileCount: sources.length,
54
- pendingFileCount: 0,
55
- };
56
- upsertMetadata(db, "scope", scope);
50
+ const syncResult = syncProjectMemoryIndex(db, sources, embedding ?? null, input.maxFiles ?? DEFAULT_PROJECT_REFRESH_FILE_LIMIT);
51
+ upsertMetadata(db, "scope", "project");
57
52
  upsertMetadata(db, "indexed_at", new Date().toISOString());
58
53
  if (embedding) {
59
54
  upsertMetadata(db, "embedding_config", JSON.stringify(embedding));
@@ -70,29 +65,17 @@ export function buildMemoryIndex(input) {
70
65
  deleteMetadata(db, "vector_index");
71
66
  }
72
67
  return {
73
- scope,
68
+ scope: "project",
74
69
  storePath,
75
70
  indexedCount: sources.length,
76
71
  processedFileCount: syncResult.processedFileCount,
77
72
  pendingFileCount: syncResult.pendingFileCount,
78
73
  sources: sources.map((entry) => entry.sourcePath),
79
- ...(scope === "project" ? { embedding, vectorIndex: syncResult.vectorIndex } : {}),
74
+ embedding,
75
+ vectorIndex: syncResult.vectorIndex,
80
76
  };
81
77
  });
82
78
  }
83
- function rebuildTaskMemoryIndex(db, sources) {
84
- db.exec("DELETE FROM docs;");
85
- db.exec("DELETE FROM docs_fts;");
86
- db.exec("DELETE FROM doc_embeddings;");
87
- db.exec("DELETE FROM doc_embedding_vectors;");
88
- const insertDoc = db.prepare("INSERT INTO docs (source_path, kind, content, content_hash) VALUES (?, ?, ?, ?)");
89
- const insertFts = db.prepare("INSERT INTO docs_fts (rowid, source_path, kind, content) VALUES (?, ?, ?, ?)");
90
- for (const source of sources) {
91
- const result = insertDoc.run(source.sourcePath, source.kind, source.content, hashMemoryContent(source.content));
92
- insertFts.run(Number(result.lastInsertRowid), source.sourcePath, source.kind, source.content);
93
- }
94
- return null;
95
- }
96
79
  function syncProjectMemoryIndex(db, sources, embedding, maxFiles) {
97
80
  const currentEmbeddingConfig = embedding ? JSON.stringify(embedding) : null;
98
81
  const storedEmbeddingConfig = getMetadata(db, "embedding_config");
@@ -226,37 +209,22 @@ export function searchMemory(input) {
226
209
  if (!input.query.trim()) {
227
210
  throw new ClawError("MEMORY_QUERY_REQUIRED", "memory search requires a non-empty query.");
228
211
  }
229
- const { scope, project, task } = resolveMemoryScope(input);
212
+ const project = resolveProjectContext(input.cwd);
230
213
  if (!isProjectMemoryEnabled(project)) {
231
214
  throw new ClawError("MEMORY_DISABLED", "Project memory is disabled by .claw/project.json `memory.enabled = false`.", {
232
- scope,
215
+ scope: "project",
233
216
  projectRoot: project.projectRoot,
234
217
  });
235
218
  }
236
- const storePath = getMemoryStorePath(project, scope, task);
219
+ const storePath = getMemoryStorePath(project);
237
220
  if (!fs.existsSync(storePath)) {
238
- if (scope === "project") {
239
- throw new ClawError("MEMORY_VECTOR_INDEX_REQUIRED", "Project search requires a refreshed vector index. Run `claw search index --refresh` first.");
240
- }
241
- buildMemoryIndex({
242
- cwd: input.cwd,
243
- scope,
244
- ...(task ? { taskName: task.taskName } : {}),
245
- });
221
+ throw new ClawError("MEMORY_VECTOR_INDEX_REQUIRED", "Project search requires a refreshed vector index. Run `claw search index --refresh` first.");
246
222
  }
247
223
  return withMemoryDatabase(storePath, "search", "read", (db) => {
248
224
  prepareSchema(db);
249
- const searchResult = scope === "project"
250
- ? searchProjectMemoryHybrid(db, input.query, input.limit ?? 10, project)
251
- : {
252
- results: searchTaskMemoryFts(db, input.query, input.limit ?? 10),
253
- telemetry: {
254
- route: "task_fts",
255
- queryEmbedding: "skipped",
256
- },
257
- };
225
+ const searchResult = searchProjectMemoryHybrid(db, input.query, input.limit ?? 10, project);
258
226
  return {
259
- scope,
227
+ scope: "project",
260
228
  storePath,
261
229
  results: searchResult.results,
262
230
  telemetry: {
@@ -278,15 +246,14 @@ export async function searchMemoryAsync(input) {
278
246
  return result;
279
247
  }
280
248
  async function primePersistentProjectQueryEmbedding(input) {
281
- if (input.scope === "task"
282
- || process.env.CLAW_EMBEDDING_MOCK === "1"
249
+ if (process.env.CLAW_EMBEDDING_MOCK === "1"
283
250
  || process.env.CLAW_EMBEDDING_PERSISTENT_WORKER === "0"
284
251
  || !input.query.trim()) {
285
252
  return null;
286
253
  }
287
254
  const project = resolveProjectContext(input.cwd);
288
255
  const embedding = resolveProjectMemoryEmbeddingConfig(project);
289
- const storePath = getMemoryStorePath(project, "project");
256
+ const storePath = getMemoryStorePath(project);
290
257
  if (embedding?.provider !== "local" || !fs.existsSync(storePath)) {
291
258
  return null;
292
259
  }
@@ -321,34 +288,16 @@ async function primePersistentProjectQueryEmbedding(input) {
321
288
  return "persistent_daemon";
322
289
  }
323
290
  export function getMemory(input) {
324
- const { scope, project, task } = resolveMemoryScope(input);
325
- const storePath = getMemoryStorePath(project, scope, task);
326
- const sources = isProjectMemoryEnabled(project) ? collectMemorySources(project, scope, task) : [];
291
+ const project = resolveProjectContext(input.cwd);
292
+ const storePath = getMemoryStorePath(project);
293
+ const sources = isProjectMemoryEnabled(project) ? collectProjectMemorySources(project) : [];
327
294
  return {
328
- scope,
295
+ scope: "project",
329
296
  storePath,
330
297
  sources,
331
298
  };
332
299
  }
333
- function resolveMemoryScope(input) {
334
- const project = resolveProjectContext(input.cwd);
335
- const scope = input.scope ?? "project";
336
- if (scope === "task") {
337
- if (!input.taskName) {
338
- throw new ClawError("TASK_NOT_FOUND", "Task scope memory commands require --task.", { scope });
339
- }
340
- return {
341
- scope,
342
- project,
343
- task: resolveTaskContext(project, input.taskName),
344
- };
345
- }
346
- return { scope, project };
347
- }
348
- function getMemoryStorePath(project, scope, task) {
349
- if (scope === "task" && task) {
350
- return path.join(task.taskDir, "memory.sqlite");
351
- }
300
+ function getMemoryStorePath(project) {
352
301
  return path.join(project.clawDir, "memory.sqlite");
353
302
  }
354
303
  function withMemoryDatabase(storePath, operation, access, callback) {
@@ -431,12 +380,6 @@ function buildMemoryStoreBusyError(storePath, operation, cause) {
431
380
  cause: causeMessage,
432
381
  });
433
382
  }
434
- function collectMemorySources(project, scope, task) {
435
- if (scope === "task" && task) {
436
- return collectTaskMemorySources(task);
437
- }
438
- return collectProjectMemorySources(project);
439
- }
440
383
  function collectProjectMemorySources(project) {
441
384
  const sources = [];
442
385
  addTextSourceIfExists(sources, path.join(project.clawDir, "memory.md"), "project_memory");
@@ -451,17 +394,6 @@ function collectProjectMemorySources(project) {
451
394
  }
452
395
  return sources;
453
396
  }
454
- function collectTaskMemorySources(task) {
455
- const sources = [];
456
- const plan = readJsonFile(task.activePlanPath);
457
- sources.push({
458
- sourcePath: task.activePlanPath,
459
- kind: "active_plan",
460
- content: renderStructuredPlanMemory(plan),
461
- });
462
- addTextSourceIfExists(sources, path.join(task.taskDir, "memory.md"), "task_memory");
463
- return sources;
464
- }
465
397
  function addTextSourceIfExists(sources, sourcePath, kind) {
466
398
  if (!fs.existsSync(sourcePath) || !fs.statSync(sourcePath).isFile()) {
467
399
  return;
@@ -491,40 +423,6 @@ function addExternalDocSources(sources, projectRoot, externalPath) {
491
423
  addTextSourceIfExists(sources, filePath, "external_doc");
492
424
  }
493
425
  }
494
- function renderStructuredPlanMemory(plan) {
495
- const parts = [
496
- `# ${plan.title}`,
497
- `Status: ${plan.status}`,
498
- `Goal: ${plan.goal.text}`,
499
- ];
500
- if (plan.rules?.length) {
501
- parts.push("Rules:");
502
- parts.push(...plan.rules.map((rule) => `- ${rule}`));
503
- }
504
- if (plan.references?.length) {
505
- parts.push("References:");
506
- parts.push(...plan.references.map((reference) => `- ${reference.why}: ${reference.path}`));
507
- }
508
- if (plan.keyDecisions?.length) {
509
- parts.push("Key Decisions:");
510
- parts.push(...plan.keyDecisions.map((decision) => `- ${decision}`));
511
- }
512
- if (plan.retrospective) {
513
- parts.push("Retrospective:");
514
- parts.push(`- Summary: ${plan.retrospective.summary}`);
515
- for (const item of plan.retrospective.whatWorked ?? []) {
516
- parts.push(`- Worked: ${item}`);
517
- }
518
- for (const item of plan.retrospective.issues ?? []) {
519
- parts.push(`- Issue: ${item}`);
520
- }
521
- }
522
- if (plan.tasks.length) {
523
- parts.push("Tasks:");
524
- parts.push(...plan.tasks.map((task) => `- [${task.status}] ${task.id}: ${task.title}`));
525
- }
526
- return `${parts.join("\n")}\n`;
527
- }
528
426
  function prepareSchema(db) {
529
427
  db.exec([
530
428
  "CREATE TABLE IF NOT EXISTS docs (",
@@ -1084,23 +982,6 @@ function resolveEmbeddingDimensions(embedding, fallback) {
1084
982
  }
1085
983
  return fallback > 0 ? fallback : 1536;
1086
984
  }
1087
- function searchTaskMemoryFts(db, query, limit) {
1088
- const rows = db
1089
- .prepare([
1090
- "SELECT source_path, kind, snippet(docs_fts, 2, '[', ']', ' ... ', 18) AS snippet, bm25(docs_fts) AS score",
1091
- "FROM docs_fts",
1092
- "WHERE docs_fts MATCH ?",
1093
- "ORDER BY score ASC",
1094
- "LIMIT ?",
1095
- ].join(" "))
1096
- .all(query, limit);
1097
- return rows.map((row) => ({
1098
- sourcePath: row.source_path,
1099
- kind: row.kind,
1100
- snippet: row.snippet,
1101
- score: row.score,
1102
- }));
1103
- }
1104
985
  function searchProjectMemoryHybrid(db, query, limit, project) {
1105
986
  const embedding = resolveProjectMemoryEmbeddingConfig(project);
1106
987
  if (!embedding) {