recallx 1.0.0

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 (37) hide show
  1. package/README.md +205 -0
  2. package/app/cli/bin/recallx-mcp.js +2 -0
  3. package/app/cli/bin/recallx.js +8 -0
  4. package/app/cli/src/cli.js +808 -0
  5. package/app/cli/src/format.js +242 -0
  6. package/app/cli/src/http.js +35 -0
  7. package/app/mcp/api-client.js +101 -0
  8. package/app/mcp/index.js +128 -0
  9. package/app/mcp/server.js +786 -0
  10. package/app/server/app.js +2263 -0
  11. package/app/server/config.js +27 -0
  12. package/app/server/db.js +399 -0
  13. package/app/server/errors.js +17 -0
  14. package/app/server/governance.js +466 -0
  15. package/app/server/index.js +26 -0
  16. package/app/server/inferred-relations.js +247 -0
  17. package/app/server/observability.js +495 -0
  18. package/app/server/project-graph.js +199 -0
  19. package/app/server/relation-scoring.js +59 -0
  20. package/app/server/repositories.js +2992 -0
  21. package/app/server/retrieval.js +486 -0
  22. package/app/server/semantic/chunker.js +85 -0
  23. package/app/server/semantic/provider.js +124 -0
  24. package/app/server/semantic/types.js +1 -0
  25. package/app/server/semantic/vector-store.js +169 -0
  26. package/app/server/utils.js +43 -0
  27. package/app/server/workspace-session.js +128 -0
  28. package/app/server/workspace.js +79 -0
  29. package/app/shared/contracts.js +268 -0
  30. package/app/shared/request-runtime.js +30 -0
  31. package/app/shared/types.js +1 -0
  32. package/app/shared/version.js +1 -0
  33. package/dist/renderer/assets/ProjectGraphCanvas-BMvz9DmE.js +312 -0
  34. package/dist/renderer/assets/index-C2-KXqBO.css +1 -0
  35. package/dist/renderer/assets/index-CrDu22h7.js +76 -0
  36. package/dist/renderer/index.html +13 -0
  37. package/package.json +49 -0
@@ -0,0 +1,169 @@
1
+ export class VectorIndexStoreError extends Error {
2
+ code;
3
+ constructor(code, message) {
4
+ super(message);
5
+ this.code = code;
6
+ this.name = "VectorIndexStoreError";
7
+ }
8
+ }
9
+ function encodeVectorBlob(vector) {
10
+ return new Uint8Array(new Float32Array(vector).buffer);
11
+ }
12
+ function decodeVectorBlob(blob) {
13
+ return new Float32Array(blob.buffer, blob.byteOffset, blob.byteLength / Float32Array.BYTES_PER_ELEMENT);
14
+ }
15
+ function buildEmbeddingByOrdinal(embeddings) {
16
+ return new Map(embeddings.map((embedding) => [embedding.chunkOrdinal, embedding]));
17
+ }
18
+ function computeCosineSimilarity(left, right) {
19
+ const length = Math.min(left.length, right.length);
20
+ let dot = 0;
21
+ let leftMagnitude = 0;
22
+ let rightMagnitude = 0;
23
+ for (let index = 0; index < length; index += 1) {
24
+ const leftValue = Number(left[index] ?? 0);
25
+ const rightValue = Number(right[index] ?? 0);
26
+ dot += leftValue * rightValue;
27
+ leftMagnitude += leftValue * leftValue;
28
+ rightMagnitude += rightValue * rightValue;
29
+ }
30
+ if (!leftMagnitude || !rightMagnitude) {
31
+ return 0;
32
+ }
33
+ return dot / (Math.sqrt(leftMagnitude) * Math.sqrt(rightMagnitude));
34
+ }
35
+ class SqliteVectorIndexStore {
36
+ db;
37
+ backend = "sqlite";
38
+ constructor(db) {
39
+ this.db = db;
40
+ }
41
+ async upsertNodeChunks(input) {
42
+ const embeddingsByOrdinal = buildEmbeddingByOrdinal(input.embeddings);
43
+ const ledgerRows = [];
44
+ for (const chunk of input.chunks) {
45
+ const embedding = embeddingsByOrdinal.get(chunk.ordinal);
46
+ if (!embedding) {
47
+ continue;
48
+ }
49
+ ledgerRows.push({
50
+ chunkOrdinal: chunk.ordinal,
51
+ vectorRef: null,
52
+ vectorBlob: encodeVectorBlob(embedding.vector)
53
+ });
54
+ }
55
+ return ledgerRows;
56
+ }
57
+ async deleteNode(_nodeId) { }
58
+ async searchCandidates(input) {
59
+ if (!input.candidateNodeIds.length) {
60
+ return [];
61
+ }
62
+ const rows = this.db
63
+ .prepare(`SELECT owner_id, chunk_ordinal, vector_ref, vector_blob
64
+ FROM node_embeddings
65
+ WHERE owner_type = 'node'
66
+ AND status = 'ready'
67
+ AND vector_blob IS NOT NULL
68
+ AND embedding_provider = ?
69
+ AND embedding_model ${input.embeddingModel === null ? "IS NULL" : "= ?"}
70
+ AND embedding_version ${input.embeddingVersion === null ? "IS NULL" : "= ?"}
71
+ AND owner_id IN (${input.candidateNodeIds.map(() => "?").join(", ")})`)
72
+ .all(...[
73
+ input.embeddingProvider,
74
+ ...(input.embeddingModel === null ? [] : [input.embeddingModel]),
75
+ ...(input.embeddingVersion === null ? [] : [input.embeddingVersion]),
76
+ ...input.candidateNodeIds
77
+ ]);
78
+ const matches = [];
79
+ for (const row of rows) {
80
+ if (!(row.vector_blob instanceof Uint8Array)) {
81
+ continue;
82
+ }
83
+ const similarity = computeCosineSimilarity(input.queryVector, decodeVectorBlob(row.vector_blob));
84
+ if (!Number.isFinite(similarity)) {
85
+ continue;
86
+ }
87
+ matches.push({
88
+ nodeId: String(row.owner_id),
89
+ chunkOrdinal: Number(row.chunk_ordinal ?? 0),
90
+ similarity,
91
+ vectorRef: row.vector_ref ? String(row.vector_ref) : null
92
+ });
93
+ }
94
+ return matches;
95
+ }
96
+ }
97
+ class SqliteVecVectorIndexStore {
98
+ db;
99
+ backend = "sqlite-vec";
100
+ constructor(db) {
101
+ this.db = db;
102
+ }
103
+ async upsertNodeChunks(input) {
104
+ const embeddingsByOrdinal = buildEmbeddingByOrdinal(input.embeddings);
105
+ const ledgerRows = [];
106
+ for (const chunk of input.chunks) {
107
+ const embedding = embeddingsByOrdinal.get(chunk.ordinal);
108
+ if (!embedding) {
109
+ continue;
110
+ }
111
+ ledgerRows.push({
112
+ chunkOrdinal: chunk.ordinal,
113
+ vectorRef: null,
114
+ vectorBlob: encodeVectorBlob(embedding.vector)
115
+ });
116
+ }
117
+ return ledgerRows;
118
+ }
119
+ async deleteNode(_nodeId) { }
120
+ async searchCandidates(input) {
121
+ if (!input.queryVector.length || !input.candidateNodeIds.length) {
122
+ return [];
123
+ }
124
+ const queryVectorBlob = encodeVectorBlob(input.queryVector);
125
+ const rows = this.db
126
+ .prepare(`SELECT
127
+ owner_id,
128
+ chunk_ordinal,
129
+ vector_ref,
130
+ vector_blob,
131
+ 1 - vec_distance_cosine(vector_blob, ?) AS similarity
132
+ FROM node_embeddings
133
+ WHERE owner_type = 'node'
134
+ AND status = 'ready'
135
+ AND vector_blob IS NOT NULL
136
+ AND embedding_provider = ?
137
+ AND embedding_model ${input.embeddingModel === null ? "IS NULL" : "= ?"}
138
+ AND embedding_version ${input.embeddingVersion === null ? "IS NULL" : "= ?"}
139
+ AND owner_id IN (${input.candidateNodeIds.map(() => "?").join(", ")})
140
+ ORDER BY similarity DESC`)
141
+ .all(...[
142
+ queryVectorBlob,
143
+ input.embeddingProvider,
144
+ ...(input.embeddingModel === null ? [] : [input.embeddingModel]),
145
+ ...(input.embeddingVersion === null ? [] : [input.embeddingVersion]),
146
+ ...input.candidateNodeIds
147
+ ]);
148
+ return rows
149
+ .map((row) => {
150
+ const similarity = Number(row.similarity);
151
+ if (!Number.isFinite(similarity)) {
152
+ return null;
153
+ }
154
+ return {
155
+ nodeId: String(row.owner_id),
156
+ chunkOrdinal: Number(row.chunk_ordinal ?? 0),
157
+ similarity,
158
+ vectorRef: row.vector_ref ? String(row.vector_ref) : null
159
+ };
160
+ })
161
+ .filter((item) => item !== null);
162
+ }
163
+ }
164
+ export function createVectorIndexStore(db, input) {
165
+ if (input.backend === "sqlite-vec") {
166
+ return new SqliteVecVectorIndexStore(db);
167
+ }
168
+ return new SqliteVectorIndexStore(db);
169
+ }
@@ -0,0 +1,43 @@
1
+ import { createHash } from "node:crypto";
2
+ import path from "node:path";
3
+ import { ulid } from "ulid";
4
+ export function createId(prefix) {
5
+ return `${prefix}_${ulid().toLowerCase()}`;
6
+ }
7
+ export function nowIso() {
8
+ return new Date().toISOString();
9
+ }
10
+ export function parseJson(raw, fallback) {
11
+ if (!raw) {
12
+ return fallback;
13
+ }
14
+ try {
15
+ return JSON.parse(raw);
16
+ }
17
+ catch {
18
+ return fallback;
19
+ }
20
+ }
21
+ export function stableSummary(title, body) {
22
+ const content = [title, body].filter(Boolean).join(". ").replace(/\s+/g, " ").trim();
23
+ if (!content) {
24
+ return "No summary yet.";
25
+ }
26
+ return content.slice(0, 220);
27
+ }
28
+ export function countTokensApprox(text) {
29
+ if (!text) {
30
+ return 0;
31
+ }
32
+ return text
33
+ .trim()
34
+ .split(/\s+/)
35
+ .filter(Boolean).length;
36
+ }
37
+ export function checksumText(value) {
38
+ return createHash("sha256").update(value).digest("hex");
39
+ }
40
+ export function isPathWithinRoot(rootPath, candidatePath) {
41
+ const relative = path.relative(path.resolve(rootPath), path.resolve(candidatePath));
42
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
43
+ }
@@ -0,0 +1,128 @@
1
+ import { existsSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { workspaceInfo } from "./config.js";
4
+ import { openDatabase } from "./db.js";
5
+ import { AppError } from "./errors.js";
6
+ import { bootstrapAutomaticGovernance } from "./governance.js";
7
+ import { RecallXRepository } from "./repositories.js";
8
+ import { defaultWorkspaceName, ensureWorkspace } from "./workspace.js";
9
+ import { RECALLX_VERSION } from "../shared/version.js";
10
+ export class WorkspaceSessionManager {
11
+ serverConfig;
12
+ authMode;
13
+ currentState;
14
+ history = new Map();
15
+ constructor(serverConfig, initialWorkspaceRoot, authMode) {
16
+ this.serverConfig = serverConfig;
17
+ this.authMode = authMode;
18
+ this.currentState = this.loadWorkspace(initialWorkspaceRoot, {
19
+ workspaceName: serverConfig.workspaceName,
20
+ requireExistingRoot: false,
21
+ });
22
+ this.remember(this.currentState);
23
+ }
24
+ getCurrent() {
25
+ return this.currentState;
26
+ }
27
+ listWorkspaces() {
28
+ const currentRoot = this.currentState.workspaceRoot;
29
+ return [...this.history.values()]
30
+ .map((item) => ({
31
+ ...item,
32
+ isCurrent: item.rootPath === currentRoot,
33
+ }))
34
+ .sort((left, right) => right.lastOpenedAt.localeCompare(left.lastOpenedAt));
35
+ }
36
+ createWorkspace(rootPath, workspaceName) {
37
+ return this.swapWorkspace(rootPath, {
38
+ workspaceName,
39
+ requireExistingRoot: false,
40
+ });
41
+ }
42
+ openWorkspace(rootPath) {
43
+ return this.swapWorkspace(rootPath, {
44
+ requireExistingRoot: true,
45
+ });
46
+ }
47
+ swapWorkspace(rootPath, options) {
48
+ const nextState = this.loadWorkspace(rootPath, options);
49
+ const previousState = this.currentState;
50
+ this.currentState = nextState;
51
+ this.remember(nextState);
52
+ previousState.db.close();
53
+ return this.getWorkspaceCatalogItem(nextState);
54
+ }
55
+ loadWorkspace(rootPath, options) {
56
+ const resolvedRoot = path.resolve(rootPath);
57
+ if (options.requireExistingRoot && !existsSync(resolvedRoot)) {
58
+ throw new AppError(404, "WORKSPACE_NOT_FOUND", `Workspace root not found: ${resolvedRoot}`);
59
+ }
60
+ const paths = ensureWorkspace(resolvedRoot);
61
+ const db = openDatabase(paths);
62
+ const repository = new RecallXRepository(db, resolvedRoot);
63
+ const storedSettings = repository.getSettings(["workspace.name"]);
64
+ const resolvedName = typeof storedSettings["workspace.name"] === "string" && storedSettings["workspace.name"].trim()
65
+ ? String(storedSettings["workspace.name"])
66
+ : options.workspaceName?.trim() || defaultWorkspaceName(resolvedRoot);
67
+ repository.setSetting("workspace.name", resolvedName);
68
+ repository.setSetting("workspace.version", RECALLX_VERSION);
69
+ repository.setSetting("api.bind", `${this.serverConfig.bindAddress}:${this.serverConfig.port}`);
70
+ repository.setSetting("api.auth.mode", this.authMode);
71
+ repository.ensureBaseSettings({
72
+ "search.semantic.enabled": false,
73
+ "search.semantic.provider": "disabled",
74
+ "search.semantic.model": "none",
75
+ "search.semantic.indexBackend": "sqlite-vec",
76
+ "search.semantic.chunk.enabled": false,
77
+ "search.semantic.chunk.aggregation": "max",
78
+ "search.semantic.workspaceFallback.enabled": false,
79
+ "search.semantic.augmentation.minSimilarity": 0.2,
80
+ "search.semantic.augmentation.maxBonus": 18,
81
+ "search.semantic.last_backfill_at": null,
82
+ "search.semantic.autoIndex.enabled": true,
83
+ "search.semantic.autoIndex.debounceMs": 1500,
84
+ "search.semantic.autoIndex.batchLimit": 20,
85
+ "search.semantic.autoIndex.lastRunAt": null,
86
+ "search.tagIndex.version": 0,
87
+ "search.activityFts.version": 0,
88
+ "relations.autoRefresh.enabled": true,
89
+ "relations.autoRefresh.debounceMs": 150,
90
+ "relations.autoRefresh.maxStalenessMs": 2_000,
91
+ "relations.autoRefresh.batchLimit": 24,
92
+ "relations.autoRecompute.enabled": true,
93
+ "relations.autoRecompute.eventThreshold": 12,
94
+ "relations.autoRecompute.debounceMs": 30_000,
95
+ "relations.autoRecompute.maxStalenessMs": 300_000,
96
+ "relations.autoRecompute.batchLimit": 100,
97
+ "relations.autoRecompute.lastRunAt": null,
98
+ "observability.enabled": false,
99
+ "observability.retentionDays": 14,
100
+ "observability.slowRequestMs": 250,
101
+ "observability.capturePayloadShape": true,
102
+ "export.defaultFormat": "markdown",
103
+ });
104
+ repository.ensureSearchTagIndex();
105
+ repository.ensureActivitySearchIndex();
106
+ bootstrapAutomaticGovernance(repository);
107
+ return {
108
+ db,
109
+ repository,
110
+ paths,
111
+ workspaceRoot: resolvedRoot,
112
+ workspaceInfo: workspaceInfo(resolvedRoot, {
113
+ ...this.serverConfig,
114
+ workspaceName: resolvedName,
115
+ }, this.authMode),
116
+ };
117
+ }
118
+ remember(state) {
119
+ this.history.set(state.workspaceRoot, this.getWorkspaceCatalogItem(state));
120
+ }
121
+ getWorkspaceCatalogItem(state) {
122
+ return {
123
+ ...state.workspaceInfo,
124
+ isCurrent: state.workspaceRoot === this.currentState?.workspaceRoot,
125
+ lastOpenedAt: new Date().toISOString(),
126
+ };
127
+ }
128
+ }
@@ -0,0 +1,79 @@
1
+ import { cpSync, mkdirSync, existsSync } from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ export function recallxHomeDir() {
5
+ return path.join(os.homedir(), ".recallx");
6
+ }
7
+ function sanitizeWorkspaceName(value) {
8
+ return value.replace(/[<>:"/\\|?*\x00-\x1f]+/g, "-").replace(/\s+/g, " ").trim() || "RecallX";
9
+ }
10
+ function preferredWorkspaceName() {
11
+ const configured = process.env.RECALLX_WORKSPACE_NAME;
12
+ if (typeof configured === "string" && configured.trim()) {
13
+ return sanitizeWorkspaceName(configured);
14
+ }
15
+ return sanitizeWorkspaceName(path.basename(process.cwd()) || "RecallX");
16
+ }
17
+ export function resolveWorkspaceRoot(options) {
18
+ const configured = process.env.RECALLX_WORKSPACE_ROOT;
19
+ if (configured) {
20
+ return path.resolve(configured);
21
+ }
22
+ const preferredRoot = path.join(recallxHomeDir(), preferredWorkspaceName());
23
+ const allowLegacyProjectRoot = options?.allowLegacyProjectRoot ?? true;
24
+ const legacyRoot = path.resolve(options?.legacyProjectRoot ?? process.cwd(), ".recallx-workspace");
25
+ if (existsSync(preferredRoot)) {
26
+ return preferredRoot;
27
+ }
28
+ if (allowLegacyProjectRoot && existsSync(legacyRoot)) {
29
+ try {
30
+ mkdirSync(path.dirname(preferredRoot), { recursive: true });
31
+ cpSync(legacyRoot, preferredRoot, { recursive: true, errorOnExist: false, force: false });
32
+ return preferredRoot;
33
+ }
34
+ catch {
35
+ return legacyRoot;
36
+ }
37
+ }
38
+ return preferredRoot;
39
+ }
40
+ export function ensureWorkspace(root) {
41
+ const paths = {
42
+ root,
43
+ dbPath: path.join(root, "workspace.db"),
44
+ artifactsDir: path.join(root, "artifacts"),
45
+ exportsDir: path.join(root, "exports"),
46
+ importsDir: path.join(root, "imports"),
47
+ backupsDir: path.join(root, "backups"),
48
+ logsDir: path.join(root, "logs"),
49
+ configDir: path.join(root, "config"),
50
+ cacheDir: path.join(root, "cache"),
51
+ embeddingsDir: path.join(root, "cache", "embeddings"),
52
+ searchCacheDir: path.join(root, "cache", "search")
53
+ };
54
+ for (const directory of [
55
+ paths.root,
56
+ paths.artifactsDir,
57
+ paths.exportsDir,
58
+ paths.importsDir,
59
+ paths.backupsDir,
60
+ paths.logsDir,
61
+ paths.configDir,
62
+ paths.cacheDir,
63
+ paths.embeddingsDir,
64
+ paths.searchCacheDir
65
+ ]) {
66
+ if (!existsSync(directory)) {
67
+ mkdirSync(directory, { recursive: true });
68
+ }
69
+ }
70
+ return paths;
71
+ }
72
+ export function defaultWorkspaceName(root) {
73
+ const resolved = path.resolve(root);
74
+ const base = path.basename(resolved);
75
+ if (base.startsWith(".")) {
76
+ return path.basename(path.dirname(resolved)) || "RecallX";
77
+ }
78
+ return base.replace(/^\.*/, "") || "RecallX";
79
+ }
@@ -0,0 +1,268 @@
1
+ import { z } from "zod";
2
+ export const nodeTypes = [
3
+ "note",
4
+ "project",
5
+ "idea",
6
+ "question",
7
+ "decision",
8
+ "reference",
9
+ "artifact_ref",
10
+ "conversation"
11
+ ];
12
+ export const nodeStatuses = ["active", "draft", "contested", "archived"];
13
+ export const canonicalities = [
14
+ "canonical",
15
+ "appended",
16
+ "suggested",
17
+ "imported",
18
+ "generated"
19
+ ];
20
+ export const relationTypes = [
21
+ "related_to",
22
+ "supports",
23
+ "contradicts",
24
+ "elaborates",
25
+ "depends_on",
26
+ "relevant_to",
27
+ "derived_from",
28
+ "produced_by"
29
+ ];
30
+ export const relationStatuses = ["active", "suggested", "rejected", "archived"];
31
+ export const inferredRelationStatuses = ["active", "muted", "hidden", "expired"];
32
+ export const relationSources = ["canonical", "inferred"];
33
+ export const relationUsageEventTypes = [
34
+ "bundle_included",
35
+ "bundle_clicked",
36
+ "bundle_used_in_output",
37
+ "bundle_skipped",
38
+ "retrieval_confirmed",
39
+ "retrieval_muted",
40
+ "manual_hide"
41
+ ];
42
+ export const searchFeedbackResultTypes = ["node", "activity"];
43
+ export const searchFeedbackVerdicts = ["useful", "not_useful", "uncertain"];
44
+ export const governanceEntityTypes = ["node", "relation"];
45
+ export const governanceStates = ["healthy", "low_confidence", "contested"];
46
+ export const governanceEventTypes = [
47
+ "evaluated",
48
+ "promoted",
49
+ "contested",
50
+ "demoted",
51
+ "migrated"
52
+ ];
53
+ export const activityTypes = [
54
+ "note_appended",
55
+ "agent_run_summary",
56
+ "import_completed",
57
+ "artifact_attached",
58
+ "decision_recorded",
59
+ "review_action",
60
+ "context_bundle_generated"
61
+ ];
62
+ export const sourceTypes = ["human", "agent", "import", "system", "integration"];
63
+ export const captureModes = ["auto", "activity", "node", "decision"];
64
+ export const bundleModes = ["micro", "compact", "standard", "deep"];
65
+ export const bundlePresets = [
66
+ "for-coding",
67
+ "for-research",
68
+ "for-decision",
69
+ "for-writing",
70
+ "for-assistant"
71
+ ];
72
+ export const sourceSchema = z.object({
73
+ actorType: z.enum(sourceTypes),
74
+ actorLabel: z.string().min(1),
75
+ toolName: z.string().min(1),
76
+ toolVersion: z.string().optional()
77
+ });
78
+ export const nodeSearchSchema = z.object({
79
+ query: z.string().default(""),
80
+ filters: z
81
+ .object({
82
+ types: z.array(z.enum(nodeTypes)).optional(),
83
+ status: z.array(z.enum(nodeStatuses)).optional(),
84
+ sourceLabels: z.array(z.string()).optional(),
85
+ tags: z.array(z.string()).optional()
86
+ })
87
+ .default({}),
88
+ limit: z.number().int().min(1).max(100).default(10),
89
+ offset: z.number().int().min(0).default(0),
90
+ sort: z.enum(["relevance", "updated_at"]).default("relevance")
91
+ });
92
+ export const activitySearchSchema = z.object({
93
+ query: z.string().default(""),
94
+ filters: z
95
+ .object({
96
+ targetNodeIds: z.array(z.string()).optional(),
97
+ activityTypes: z.array(z.enum(activityTypes)).optional(),
98
+ sourceLabels: z.array(z.string()).optional(),
99
+ createdAfter: z.string().optional(),
100
+ createdBefore: z.string().optional()
101
+ })
102
+ .default({}),
103
+ limit: z.number().int().min(1).max(100).default(10),
104
+ offset: z.number().int().min(0).default(0),
105
+ sort: z.enum(["relevance", "updated_at"]).default("relevance")
106
+ });
107
+ export const workspaceSearchSchema = z.object({
108
+ query: z.string().default(""),
109
+ scopes: z.array(z.enum(["nodes", "activities"])).min(1).default(["nodes", "activities"]),
110
+ nodeFilters: nodeSearchSchema.shape.filters.optional(),
111
+ activityFilters: activitySearchSchema.shape.filters.optional(),
112
+ limit: z.number().int().min(1).max(100).default(10),
113
+ offset: z.number().int().min(0).default(0),
114
+ sort: z.enum(["relevance", "updated_at", "smart"]).default("relevance")
115
+ });
116
+ export const governanceIssuesQuerySchema = z.object({
117
+ states: z.array(z.enum(governanceStates)).optional(),
118
+ limit: z.number().int().min(1).max(100).default(20)
119
+ });
120
+ export const recomputeGovernanceSchema = z.object({
121
+ entityType: z.enum(governanceEntityTypes).optional(),
122
+ entityIds: z.array(z.string().min(1)).max(200).optional(),
123
+ limit: z.number().int().min(1).max(500).default(100)
124
+ });
125
+ export const createNodeSchema = z.object({
126
+ type: z.enum(nodeTypes),
127
+ title: z.string().min(1),
128
+ body: z.string().default(""),
129
+ summary: z.string().optional(),
130
+ tags: z.array(z.string()).default([]),
131
+ canonicality: z.enum(canonicalities).optional(),
132
+ status: z.enum(nodeStatuses).optional(),
133
+ source: sourceSchema,
134
+ metadata: z.record(z.any()).default({})
135
+ });
136
+ export const createNodesSchema = z.object({
137
+ nodes: z.array(createNodeSchema).min(1).max(100)
138
+ });
139
+ export const updateNodeSchema = z.object({
140
+ title: z.string().min(1).optional(),
141
+ body: z.string().optional(),
142
+ summary: z.string().optional(),
143
+ tags: z.array(z.string()).optional(),
144
+ metadata: z.record(z.any()).optional(),
145
+ status: z.enum(nodeStatuses).optional()
146
+ });
147
+ export const createRelationSchema = z.object({
148
+ fromNodeId: z.string().min(1),
149
+ toNodeId: z.string().min(1),
150
+ relationType: z.enum(relationTypes),
151
+ status: z.enum(relationStatuses).optional(),
152
+ source: sourceSchema,
153
+ metadata: z.record(z.any()).default({})
154
+ });
155
+ export const updateRelationSchema = z.object({
156
+ status: z.enum(relationStatuses),
157
+ source: sourceSchema,
158
+ metadata: z.record(z.any()).default({}),
159
+ notes: z.string().optional()
160
+ });
161
+ export const appendActivitySchema = z.object({
162
+ targetNodeId: z.string().min(1),
163
+ activityType: z.enum(activityTypes),
164
+ body: z.string().default(""),
165
+ source: sourceSchema,
166
+ metadata: z.record(z.any()).default({})
167
+ });
168
+ export const captureMemorySchema = z.object({
169
+ mode: z.enum(captureModes).default("auto"),
170
+ body: z.string().min(1),
171
+ title: z.string().min(1).optional(),
172
+ targetNodeId: z.string().min(1).optional(),
173
+ nodeType: z.enum(nodeTypes).default("note"),
174
+ tags: z.array(z.string()).default([]),
175
+ source: sourceSchema.optional(),
176
+ metadata: z.record(z.any()).default({})
177
+ });
178
+ export const upsertInferredRelationSchema = z.object({
179
+ fromNodeId: z.string().min(1),
180
+ toNodeId: z.string().min(1),
181
+ relationType: z.enum(relationTypes),
182
+ baseScore: z.number(),
183
+ usageScore: z.number().default(0),
184
+ finalScore: z.number(),
185
+ status: z.enum(inferredRelationStatuses).default("active"),
186
+ generator: z.string().min(1),
187
+ evidence: z.record(z.any()).default({}),
188
+ expiresAt: z.string().optional(),
189
+ metadata: z.record(z.any()).default({})
190
+ });
191
+ export const appendRelationUsageEventSchema = z.object({
192
+ relationId: z.string().min(1),
193
+ relationSource: z.enum(relationSources),
194
+ eventType: z.enum(relationUsageEventTypes),
195
+ sessionId: z.string().optional(),
196
+ runId: z.string().optional(),
197
+ source: sourceSchema.optional(),
198
+ delta: z.number(),
199
+ metadata: z.record(z.any()).default({})
200
+ });
201
+ export const appendSearchFeedbackSchema = z.object({
202
+ resultType: z.enum(searchFeedbackResultTypes),
203
+ resultId: z.string().min(1),
204
+ verdict: z.enum(searchFeedbackVerdicts),
205
+ query: z.string().optional(),
206
+ sessionId: z.string().optional(),
207
+ runId: z.string().optional(),
208
+ source: sourceSchema.optional(),
209
+ confidence: z.number().min(0).max(1).default(1),
210
+ metadata: z.record(z.any()).default({})
211
+ });
212
+ export const recomputeInferredRelationsSchema = z.object({
213
+ relationIds: z.array(z.string().min(1)).max(200).optional(),
214
+ generator: z.string().min(1).optional(),
215
+ limit: z.number().int().min(1).max(500).default(100)
216
+ });
217
+ export const reindexInferredRelationsSchema = z.object({
218
+ limit: z.number().int().min(1).max(1000).default(250)
219
+ });
220
+ export const attachArtifactSchema = z.object({
221
+ nodeId: z.string().min(1),
222
+ path: z.string().min(1),
223
+ mimeType: z.string().optional(),
224
+ source: sourceSchema,
225
+ metadata: z.record(z.any()).default({})
226
+ });
227
+ export const buildContextBundleSchema = z.object({
228
+ target: z
229
+ .object({
230
+ id: z.string().min(1)
231
+ })
232
+ .optional(),
233
+ mode: z.enum(bundleModes).default("compact"),
234
+ preset: z.enum(bundlePresets).default("for-assistant"),
235
+ options: z
236
+ .object({
237
+ includeRelated: z.boolean().default(true),
238
+ includeInferred: z.boolean().default(true),
239
+ includeRecentActivities: z.boolean().default(true),
240
+ includeDecisions: z.boolean().default(true),
241
+ includeOpenQuestions: z.boolean().default(true),
242
+ maxInferred: z.number().int().min(0).max(10).default(4),
243
+ maxItems: z.number().int().min(1).max(30).default(10)
244
+ })
245
+ .default({})
246
+ });
247
+ export const registerIntegrationSchema = z.object({
248
+ name: z.string().min(1),
249
+ kind: z.string().min(1),
250
+ capabilities: z.array(z.string()).default([]),
251
+ config: z.record(z.any()).default({})
252
+ });
253
+ export const updateIntegrationSchema = z.object({
254
+ name: z.string().min(1).optional(),
255
+ status: z.string().min(1).optional(),
256
+ capabilities: z.array(z.string()).optional(),
257
+ config: z.record(z.any()).optional()
258
+ });
259
+ export const updateSettingsSchema = z.object({
260
+ values: z.record(z.any())
261
+ });
262
+ export const createWorkspaceSchema = z.object({
263
+ rootPath: z.string().min(1),
264
+ workspaceName: z.string().min(1).optional()
265
+ });
266
+ export const openWorkspaceSchema = z.object({
267
+ rootPath: z.string().min(1)
268
+ });