@context-vault/core 2.15.0 → 2.17.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.
@@ -5,6 +5,7 @@ import { categoryFor, defaultTierFor } from "../../core/categories.js";
5
5
  import { normalizeKind } from "../../core/files.js";
6
6
  import { ok, err, ensureVaultExists, ensureValidKind } from "../helpers.js";
7
7
  import { maybeShowFeedbackPrompt } from "../../core/telemetry.js";
8
+ import { validateRelatedTo } from "../../core/linking.js";
8
9
  import {
9
10
  MAX_BODY_LENGTH,
10
11
  MAX_TITLE_LENGTH,
@@ -43,9 +44,15 @@ async function findSimilar(
43
44
 
44
45
  const rowids = vecRows.map((vr) => vr.rowid);
45
46
  const placeholders = rowids.map(() => "?").join(",");
46
- const columns = hydrate
47
- ? "rowid, id, title, body, kind, tags, category, user_id, updated_at"
48
- : "rowid, id, title, category, user_id";
47
+ // Local mode has no user_id column — omit it from the SELECT list.
48
+ const isLocal = ctx.stmts._mode === "local";
49
+ const columns = isLocal
50
+ ? hydrate
51
+ ? "rowid, id, title, body, kind, tags, category, updated_at"
52
+ : "rowid, id, title, category"
53
+ : hydrate
54
+ ? "rowid, id, title, body, kind, tags, category, user_id, updated_at"
55
+ : "rowid, id, title, category, user_id";
49
56
  const hydratedRows = ctx.db
50
57
  .prepare(`SELECT ${columns} FROM vault WHERE rowid IN (${placeholders})`)
51
58
  .all(...rowids);
@@ -59,7 +66,7 @@ async function findSimilar(
59
66
  if (similarity < threshold) continue;
60
67
  const row = byRowid.get(vr.rowid);
61
68
  if (!row) continue;
62
- if (userId !== undefined && row.user_id !== userId) continue;
69
+ if (!isLocal && userId !== undefined && row.user_id !== userId) continue;
63
70
  if (row.category === "entity") continue;
64
71
  const entry = { id: row.id, title: row.title, score: similarity };
65
72
  if (hydrate) {
@@ -294,6 +301,12 @@ export const inputSchema = {
294
301
  .describe(
295
302
  "Array of entry IDs that this entry supersedes/replaces. Those entries will be marked with superseded_by pointing to this new entry and excluded from future search results by default.",
296
303
  ),
304
+ related_to: z
305
+ .array(z.string())
306
+ .optional()
307
+ .describe(
308
+ "Array of entry IDs this entry is related to. Enables bidirectional graph traversal — use get_context with follow_links:true to retrieve linked entries.",
309
+ ),
297
310
  source_files: z
298
311
  .array(
299
312
  z.object({
@@ -359,6 +372,7 @@ export async function handler(
359
372
  identity_key,
360
373
  expires_at,
361
374
  supersedes,
375
+ related_to,
362
376
  source_files,
363
377
  dry_run,
364
378
  similarity_threshold,
@@ -375,6 +389,9 @@ export async function handler(
375
389
  const vaultErr = ensureVaultExists(config);
376
390
  if (vaultErr) return vaultErr;
377
391
 
392
+ const relatedToErr = validateRelatedTo(related_to);
393
+ if (relatedToErr) return err(relatedToErr, "INVALID_INPUT");
394
+
378
395
  const inputErr = validateSaveInput({
379
396
  kind,
380
397
  title,
@@ -428,9 +445,15 @@ export async function handler(
428
445
  source,
429
446
  expires_at,
430
447
  supersedes,
448
+ related_to,
431
449
  source_files,
432
450
  });
433
451
  await indexEntry(ctx, entry);
452
+ if (entry.related_to?.length && ctx.stmts.updateRelatedTo) {
453
+ ctx.stmts.updateRelatedTo.run(JSON.stringify(entry.related_to), entry.id);
454
+ } else if (entry.related_to === null && ctx.stmts.updateRelatedTo) {
455
+ ctx.stmts.updateRelatedTo.run(null, entry.id);
456
+ }
434
457
  const relPath = entry.filePath
435
458
  ? entry.filePath.replace(config.vaultDir + "/", "")
436
459
  : entry.filePath;
@@ -531,6 +554,7 @@ export async function handler(
531
554
  identity_key,
532
555
  expires_at,
533
556
  supersedes,
557
+ related_to,
534
558
  source_files,
535
559
  userId,
536
560
  tier: effectiveTier,
@@ -562,8 +586,7 @@ export async function handler(
562
586
  );
563
587
  for (const bt of bucketTags) {
564
588
  const bucketUserClause = userId !== undefined ? "AND user_id = ?" : "";
565
- const bucketParams =
566
- userId !== undefined ? [bt, userId] : [bt];
589
+ const bucketParams = userId !== undefined ? [bt, userId] : [bt];
567
590
  const exists = ctx.db
568
591
  .prepare(
569
592
  `SELECT 1 FROM vault WHERE kind = 'bucket' AND identity_key = ? ${bucketUserClause} LIMIT 1`,
@@ -8,7 +8,6 @@ import * as getContext from "./tools/get-context.js";
8
8
  import * as saveContext from "./tools/save-context.js";
9
9
  import * as listContext from "./tools/list-context.js";
10
10
  import * as deleteContext from "./tools/delete-context.js";
11
- import * as submitFeedback from "./tools/submit-feedback.js";
12
11
  import * as ingestUrl from "./tools/ingest-url.js";
13
12
  import * as contextStatus from "./tools/context-status.js";
14
13
  import * as clearContext from "./tools/clear-context.js";
@@ -22,7 +21,6 @@ const toolModules = [
22
21
  saveContext,
23
22
  listContext,
24
23
  deleteContext,
25
- submitFeedback,
26
24
  ingestUrl,
27
25
  ingestProject,
28
26
  contextStatus,
@@ -1,55 +0,0 @@
1
- import { z } from "zod";
2
- import { captureAndIndex } from "../../capture/index.js";
3
- import { ok, ensureVaultExists } from "../helpers.js";
4
-
5
- export const name = "submit_feedback";
6
-
7
- export const description =
8
- "Report a bug, request a feature, or suggest an improvement. Feedback is stored in the vault and triaged by the development pipeline.";
9
-
10
- export const inputSchema = {
11
- type: z.enum(["bug", "feature", "improvement"]).describe("Type of feedback"),
12
- title: z.string().describe("Short summary of the feedback"),
13
- body: z.string().describe("Detailed description"),
14
- severity: z
15
- .enum(["low", "medium", "high"])
16
- .optional()
17
- .describe("Severity level (default: medium)"),
18
- };
19
-
20
- /**
21
- * @param {object} args
22
- * @param {import('../types.js').BaseCtx & Partial<import('../types.js').HostedCtxExtensions>} ctx
23
- * @param {import('../types.js').ToolShared} shared
24
- */
25
- export async function handler(
26
- { type, title, body, severity },
27
- ctx,
28
- { ensureIndexed },
29
- ) {
30
- const { config } = ctx;
31
- const userId = ctx.userId !== undefined ? ctx.userId : undefined;
32
-
33
- const vaultErr = ensureVaultExists(config);
34
- if (vaultErr) return vaultErr;
35
-
36
- await ensureIndexed();
37
-
38
- const effectiveSeverity = severity || "medium";
39
- const entry = await captureAndIndex(ctx, {
40
- kind: "feedback",
41
- title,
42
- body,
43
- tags: [type, effectiveSeverity],
44
- source: "submit_feedback",
45
- meta: { feedback_type: type, severity: effectiveSeverity, status: "new" },
46
- userId,
47
- });
48
-
49
- const relPath = entry.filePath
50
- ? entry.filePath.replace(config.vaultDir + "/", "")
51
- : entry.filePath;
52
- return ok(
53
- `Feedback submitted: ${type} [${effectiveSeverity}] → ${relPath}\n id: ${entry.id}\n title: ${title}`,
54
- );
55
- }