@nebutra/knowledge-rag 0.2.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.
@@ -0,0 +1,97 @@
1
+ // =============================================================================
2
+ // @nebutra/knowledge-rag — pgvector store (interface-only, opt-in)
3
+ // =============================================================================
4
+ // This is the production store shape. It is NOT exercised by unit tests and
5
+ // does NOT run migrations. The Prisma model + DDL live in the package README.
6
+ //
7
+ // Every query is parameterised by tenantId and MUST include a
8
+ // `WHERE tenant_id = $tenantId` predicate — cross-tenant leakage via pgvector
9
+ // is a security defect, identical to the in-memory contract.
10
+ //
11
+ // To activate: supply a Prisma client (or pg pool) that exposes a
12
+ // `$queryRaw`-style executor and apply the README migration.
13
+ // =============================================================================
14
+ import { KnowledgeRagError } from "../errors";
15
+ export class PgvectorStore {
16
+ name = "pgvector";
17
+ exec;
18
+ table;
19
+ dim;
20
+ constructor(options) {
21
+ if (!options?.executor) {
22
+ throw new KnowledgeRagError("PgvectorStore requires an `executor`", {
23
+ code: "E_PGVECTOR_CONFIG",
24
+ suggestion: "Pass a Prisma client wrapper exposing query(sql, params). See the pgvector section of the @nebutra/knowledge-rag README, or use InMemoryVectorStore for zero-config.",
25
+ });
26
+ }
27
+ this.exec = options.executor;
28
+ this.table = options.table ?? "knowledge_rag_chunk";
29
+ this.dim = options.embeddingDim ?? 1536;
30
+ }
31
+ vecLiteral(v) {
32
+ if (v.length !== this.dim) {
33
+ throw new KnowledgeRagError(`Embedding dim ${v.length} != table dim ${this.dim}`, {
34
+ code: "E_PGVECTOR_DIM",
35
+ suggestion: `Recreate the table with vector(${v.length}) or use an embedder producing ${this.dim}-d vectors.`,
36
+ });
37
+ }
38
+ return `[${v.join(",")}]`;
39
+ }
40
+ async upsert(chunks) {
41
+ for (const c of chunks) {
42
+ await this.exec.query(`INSERT INTO ${this.table}
43
+ (id, doc_id, tenant_id, text, ordinal, embedding, meta)
44
+ VALUES ($1,$2,$3,$4,$5,$6::vector,$7)
45
+ ON CONFLICT (id) DO UPDATE SET
46
+ text=EXCLUDED.text, embedding=EXCLUDED.embedding, meta=EXCLUDED.meta`, [
47
+ c.id,
48
+ c.docId,
49
+ c.tenantId,
50
+ c.text,
51
+ c.ordinal,
52
+ this.vecLiteral(c.embedding),
53
+ JSON.stringify(c.meta),
54
+ ]);
55
+ }
56
+ }
57
+ async queryByVector(tenantId, vector, topK) {
58
+ // tenant_id predicate is mandatory and first — never query without it.
59
+ const rows = await this.exec.query(`SELECT id, doc_id, tenant_id, text, ordinal, meta,
60
+ (embedding <=> $2::vector) AS distance
61
+ FROM ${this.table}
62
+ WHERE tenant_id = $1
63
+ ORDER BY embedding <=> $2::vector
64
+ LIMIT $3`, [tenantId, this.vecLiteral(vector), Math.max(0, topK)]);
65
+ return rows.map((r) => ({
66
+ score: 1 - r.distance, // cosine distance → similarity
67
+ chunk: {
68
+ id: r.id,
69
+ docId: r.doc_id,
70
+ tenantId: r.tenant_id,
71
+ text: r.text,
72
+ ordinal: r.ordinal,
73
+ embedding: [],
74
+ meta: (typeof r.meta === "string" ? JSON.parse(r.meta) : r.meta) ?? {},
75
+ },
76
+ }));
77
+ }
78
+ async deleteByDoc(docId, tenantId) {
79
+ const rows = await this.exec.query(`WITH d AS (
80
+ DELETE FROM ${this.table}
81
+ WHERE doc_id = $1 AND tenant_id = $2 RETURNING 1
82
+ ) SELECT COUNT(*)::int AS count FROM d`, [docId, tenantId]);
83
+ return rows[0]?.count ?? 0;
84
+ }
85
+ async health() {
86
+ try {
87
+ await this.exec.query("SELECT 1", []);
88
+ return { ok: true, detail: `pgvector: table ${this.table} reachable` };
89
+ }
90
+ catch (err) {
91
+ return {
92
+ ok: false,
93
+ detail: `pgvector unreachable: ${err?.message ?? String(err)}`,
94
+ };
95
+ }
96
+ }
97
+ }
package/dist/tool.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ import type { KnowledgeRagConfig, RankedChunk } from "./types";
2
+ export interface KnowledgeRagToolResult {
3
+ query: string;
4
+ results: Array<{
5
+ docId: string;
6
+ chunkId: string;
7
+ text: string;
8
+ score: number;
9
+ scores: RankedChunk["scores"];
10
+ source: RankedChunk["source"];
11
+ meta: Record<string, unknown>;
12
+ }>;
13
+ }
14
+ export interface KnowledgeRagTool {
15
+ name: "knowledge_search";
16
+ description: string;
17
+ /** Execute a tenant-scoped retrieval. tenantId is bound, not an argument. */
18
+ execute(args: {
19
+ query: string;
20
+ topK?: number;
21
+ }): Promise<KnowledgeRagToolResult>;
22
+ }
23
+ /**
24
+ * Build a knowledge-search tool bound to a single tenant.
25
+ *
26
+ * @param tenantId — resolved from the agent's tenant context
27
+ * (e.g. getCurrentTenant().tenantId from @nebutra/tenant).
28
+ */
29
+ export declare function createKnowledgeRagTool(tenantId: string, config?: KnowledgeRagConfig): KnowledgeRagTool;
30
+ //# sourceMappingURL=tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../src/tool.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE/D,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,KAAK,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;QAC9B,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;QAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAC/B,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,kBAAkB,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,OAAO,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;CAClF;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,kBAAkB,GAC1B,gBAAgB,CAgClB"}
package/dist/tool.js ADDED
@@ -0,0 +1,48 @@
1
+ // =============================================================================
2
+ // @nebutra/knowledge-rag — Agent tool factory
3
+ // =============================================================================
4
+ // Exposes the RAG pipeline as a tenant-scoped retrieval tool that
5
+ // @nebutra/agent-runtime / @nebutra/agents can register. This is the "real
6
+ // caller" that keeps the package at lifecycle tier `active`.
7
+ //
8
+ // The tenantId is bound at factory time (from the agent's tenant context) and
9
+ // is NEVER taken from model-generated arguments — preventing a tool-call from
10
+ // crossing tenant boundaries.
11
+ // =============================================================================
12
+ import { getKnowledgeRag } from "./index";
13
+ /**
14
+ * Build a knowledge-search tool bound to a single tenant.
15
+ *
16
+ * @param tenantId — resolved from the agent's tenant context
17
+ * (e.g. getCurrentTenant().tenantId from @nebutra/tenant).
18
+ */
19
+ export function createKnowledgeRagTool(tenantId, config) {
20
+ if (!tenantId) {
21
+ // Fail fast — a tool without a tenant binding is a security hole.
22
+ throw new Error("createKnowledgeRagTool requires a tenantId. Resolve it from getCurrentTenant() (@nebutra/tenant) before constructing the tool.");
23
+ }
24
+ return {
25
+ name: "knowledge_search",
26
+ description: "Search the tenant's knowledge base for passages relevant to a query. Returns ranked chunks with hybrid (vector + keyword) scores and source document IDs.",
27
+ async execute(args) {
28
+ const kb = await getKnowledgeRag(config);
29
+ const ranked = await kb.query({
30
+ query: args.query,
31
+ tenantId,
32
+ topK: args.topK ?? 5,
33
+ });
34
+ return {
35
+ query: args.query,
36
+ results: ranked.map((r) => ({
37
+ docId: r.chunk.docId,
38
+ chunkId: r.chunk.id,
39
+ text: r.chunk.text,
40
+ score: r.score,
41
+ scores: r.scores,
42
+ source: r.source,
43
+ meta: r.chunk.meta,
44
+ })),
45
+ };
46
+ },
47
+ };
48
+ }
@@ -0,0 +1,101 @@
1
+ import { z } from "zod";
2
+ export declare const IngestDocumentSchema: z.ZodObject<{
3
+ id: z.ZodString;
4
+ text: z.ZodString;
5
+ tenantId: z.ZodString;
6
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
7
+ }, z.core.$strip>;
8
+ export type IngestDocument = z.infer<typeof IngestDocumentSchema>;
9
+ /** A persisted chunk. `tenantId` is non-nullable by construction. */
10
+ export interface KnowledgeChunk {
11
+ /** Stable chunk ID: `${docId}::${ordinal}`. */
12
+ readonly id: string;
13
+ readonly docId: string;
14
+ readonly tenantId: string;
15
+ readonly text: string;
16
+ /** 0-based position of the chunk within its source document. */
17
+ readonly ordinal: number;
18
+ readonly embedding: readonly number[];
19
+ readonly meta: Readonly<Record<string, unknown>>;
20
+ }
21
+ /** A scored retrieval result. */
22
+ export interface RankedChunk {
23
+ readonly chunk: KnowledgeChunk;
24
+ /** Final blended score (0..1, higher is better). */
25
+ readonly score: number;
26
+ /** Per-leg scores for explainability. */
27
+ readonly scores: {
28
+ readonly vector: number;
29
+ readonly keyword: number;
30
+ };
31
+ /** Which legs contributed. */
32
+ readonly source: "hybrid" | "vector" | "keyword";
33
+ }
34
+ export declare const QueryInputSchema: z.ZodObject<{
35
+ query: z.ZodString;
36
+ tenantId: z.ZodString;
37
+ topK: z.ZodOptional<z.ZodNumber>;
38
+ }, z.core.$strip>;
39
+ export type QueryInput = z.infer<typeof QueryInputSchema>;
40
+ export interface Chunker {
41
+ /** Split text into chunk strings. Pure, deterministic. */
42
+ split(text: string): string[];
43
+ }
44
+ export interface Embedder {
45
+ readonly name: string;
46
+ /** Embed a batch of strings into fixed-length vectors. */
47
+ embed(texts: string[]): Promise<number[][]>;
48
+ }
49
+ export interface VectorStore {
50
+ readonly name: string;
51
+ upsert(chunks: KnowledgeChunk[]): Promise<void>;
52
+ /** MUST filter by tenantId — cross-tenant leakage is a security defect. */
53
+ queryByVector(tenantId: string, vector: readonly number[], topK: number): Promise<Array<{
54
+ chunk: KnowledgeChunk;
55
+ score: number;
56
+ }>>;
57
+ deleteByDoc(docId: string, tenantId: string): Promise<number>;
58
+ /** Health probe for doctor(). */
59
+ health(): Promise<{
60
+ ok: boolean;
61
+ detail: string;
62
+ }>;
63
+ }
64
+ export interface Reranker {
65
+ readonly name: string;
66
+ rerank(query: string, candidates: RankedChunk[]): Promise<RankedChunk[]>;
67
+ }
68
+ export interface KnowledgeRagConfig {
69
+ chunker?: Chunker;
70
+ embedder?: Embedder;
71
+ vectorStore?: VectorStore;
72
+ reranker?: Reranker;
73
+ /** Logical index/namespace for the keyword leg. Default: "knowledge_rag". */
74
+ index?: string;
75
+ /** Vector-leg weight in the hybrid blend (0..1). Default 0.6. */
76
+ vectorWeight?: number;
77
+ /** Disable the keyword leg even if @nebutra/search is configured. */
78
+ disableKeyword?: boolean;
79
+ }
80
+ export interface IngestResult {
81
+ readonly docId: string;
82
+ readonly tenantId: string;
83
+ readonly chunks: number;
84
+ }
85
+ export interface DoctorReport {
86
+ readonly ok: boolean;
87
+ readonly checkedAt: string;
88
+ readonly durationMs: number;
89
+ readonly components: Array<{
90
+ readonly name: string;
91
+ readonly ok: boolean;
92
+ readonly detail: string;
93
+ }>;
94
+ }
95
+ export interface KnowledgeRag {
96
+ ingest(doc: IngestDocument): Promise<IngestResult>;
97
+ query(input: QueryInput): Promise<RankedChunk[]>;
98
+ deleteByDoc(docId: string, tenantId: string): Promise<number>;
99
+ doctor(): Promise<DoctorReport>;
100
+ }
101
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,oBAAoB;;;;;iBAS/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,qEAAqE;AACrE,MAAM,WAAW,cAAc;IAC7B,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAClD;AAED,iCAAiC;AACjC,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yCAAyC;IACzC,QAAQ,CAAC,MAAM,EAAE;QACf,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;KAC1B,CAAC;IACF,8BAA8B;IAC9B,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;CAClD;AAID,eAAO,MAAM,gBAAgB;;;;iBAI3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI1D,MAAM,WAAW,OAAO;IACtB,0DAA0D;IAC1D,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;CAC7C;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,2EAA2E;IAC3E,aAAa,CACX,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,KAAK,CAAC;QAAE,KAAK,EAAE,cAAc,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IAC5D,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9D,iCAAiC;IACjC,MAAM,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;CAC1E;AAID,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC;QACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;QACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACzB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IACnD,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IACjD,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9D,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC;CACjC"}
package/dist/types.js ADDED
@@ -0,0 +1,27 @@
1
+ // =============================================================================
2
+ // @nebutra/knowledge-rag — Public types
3
+ // =============================================================================
4
+ // Multi-tenant hybrid RAG: ingestion → chunk → embed → vector + keyword index
5
+ // → hybrid retrieval → optional rerank.
6
+ //
7
+ // Every persisted record carries a tenantId. Tenant isolation is enforced at
8
+ // every store/query boundary — never optional.
9
+ // =============================================================================
10
+ import { z } from "zod";
11
+ // ── Documents & chunks ───────────────────────────────────────────────────────
12
+ export const IngestDocumentSchema = z.object({
13
+ /** Globally unique document ID (caller-supplied). */
14
+ id: z.string().min(1),
15
+ /** Raw document text. */
16
+ text: z.string().min(1),
17
+ /** Tenant/workspace ID — REQUIRED. Every record is scoped to this. */
18
+ tenantId: z.string().min(1),
19
+ /** Arbitrary metadata propagated to every chunk and returned at query time. */
20
+ meta: z.record(z.string(), z.unknown()).optional(),
21
+ });
22
+ // ── Query ────────────────────────────────────────────────────────────────────
23
+ export const QueryInputSchema = z.object({
24
+ query: z.string().min(1),
25
+ tenantId: z.string().min(1),
26
+ topK: z.number().int().min(1).max(100).optional(),
27
+ });
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@nebutra/knowledge-rag",
3
+ "version": "0.2.0",
4
+ "private": false,
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "nebutra": {
8
+ "status": "active",
9
+ "productionReady": false,
10
+ "requires": [
11
+ "(none) — zero-config: in-memory vector store + deterministic local embedder",
12
+ "optional: OPENAI_API_KEY or OPENROUTER_API_KEY (real embeddings via @nebutra/agents)",
13
+ "optional: MEILISEARCH_URL / TYPESENSE_URL / ALGOLIA_APP_ID (keyword leg via @nebutra/search)",
14
+ "optional: DATABASE_URL (pgvector store)"
15
+ ],
16
+ "gaps": [
17
+ "pgvector store is interface-only (Prisma snippet in README, not migrated)",
18
+ "reranker interface ships with identity impl only"
19
+ ]
20
+ },
21
+ "main": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js"
27
+ }
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "README.md"
32
+ ],
33
+ "dependencies": {
34
+ "zod": "^4.3.6",
35
+ "@nebutra/agents": "1.1.0",
36
+ "@nebutra/errors": "0.1.0",
37
+ "@nebutra/logger": "0.1.0",
38
+ "@nebutra/search": "0.1.0",
39
+ "@nebutra/tenant-store": "0.2.0"
40
+ },
41
+ "devDependencies": {
42
+ "typescript": "^5.9.3",
43
+ "vitest": "^4.1.4",
44
+ "@vitest/coverage-v8": "^4.1.4"
45
+ },
46
+ "homepage": "https://github.com/Nebutra/Nebutra-Sailor/tree/main/packages/ai/knowledge-rag#readme",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/Nebutra/Nebutra-Sailor.git",
50
+ "directory": "packages/ai/knowledge-rag"
51
+ },
52
+ "publishConfig": {
53
+ "access": "public"
54
+ },
55
+ "scripts": {
56
+ "build": "tsc",
57
+ "typecheck": "tsc --noEmit",
58
+ "test": "vitest run",
59
+ "test:watch": "vitest"
60
+ }
61
+ }