@neuledge/context 0.1.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.
package/dist/search.js ADDED
@@ -0,0 +1,152 @@
1
+ import { getMetaValue } from "./db.js";
2
+ // TODO: Future enhancements for better search quality:
3
+ // - Semantic search: Add local embeddings for meaning-based retrieval
4
+ // - GraphRAG: Add relations table to traverse related docs (e.g., middleware → auth)
5
+ // Schema: relations(from_id, to_id, type: 'references'|'extends'|'example_of', weight)
6
+ // - Smarter chunking: Improve section splitting with better context preservation
7
+ // - Quality heuristics: Detect README-only repos, score documentation completeness
8
+ const CONFIG = {
9
+ MAX_TOKENS: 2000,
10
+ RELEVANCE_DROP: 0.5,
11
+ };
12
+ /**
13
+ * Build an FTS5 query from user topic.
14
+ * - Cleans special characters (keeps alphanumeric, spaces, quotes)
15
+ * - Words are implicitly ANDed by FTS5
16
+ */
17
+ function buildQuery(topic) {
18
+ return topic
19
+ .trim()
20
+ .replace(/[^\w\s"]/g, " ")
21
+ .replace(/\s+/g, " ")
22
+ .trim();
23
+ }
24
+ function searchFts(db, query) {
25
+ const ftsQuery = buildQuery(query);
26
+ if (!ftsQuery)
27
+ return [];
28
+ // BM25 weights: doc_title, section_title, content
29
+ // Higher weight = more important for ranking
30
+ const stmt = db.prepare(`
31
+ SELECT
32
+ c.id,
33
+ c.doc_path as docPath,
34
+ c.doc_title as docTitle,
35
+ c.section_title as sectionTitle,
36
+ c.content,
37
+ c.tokens,
38
+ (bm25(chunks_fts, 5.0, 10.0, 1.0) * -1) as score
39
+ FROM chunks_fts
40
+ JOIN chunks c ON chunks_fts.rowid = c.id
41
+ WHERE chunks_fts MATCH ?
42
+ ORDER BY score DESC
43
+ LIMIT 20
44
+ `);
45
+ return stmt.all(ftsQuery);
46
+ }
47
+ function filterByRelevance(matches) {
48
+ const first = matches[0];
49
+ if (!first)
50
+ return [];
51
+ const topScore = first.score;
52
+ // Keep matches that are within RELEVANCE_DROP of top score
53
+ const minScore = topScore * CONFIG.RELEVANCE_DROP;
54
+ return matches.filter((m) => m.score >= minScore);
55
+ }
56
+ function applyTokenBudget(matches) {
57
+ const result = [];
58
+ let totalTokens = 0;
59
+ for (const match of matches) {
60
+ if (totalTokens + match.tokens > CONFIG.MAX_TOKENS)
61
+ break;
62
+ result.push(match);
63
+ totalTokens += match.tokens;
64
+ }
65
+ return result;
66
+ }
67
+ function groupByDocument(matches) {
68
+ const byDoc = new Map();
69
+ for (const match of matches) {
70
+ const existing = byDoc.get(match.docPath);
71
+ if (existing) {
72
+ existing.push(match);
73
+ }
74
+ else {
75
+ byDoc.set(match.docPath, [match]);
76
+ }
77
+ }
78
+ // Sort chunks within each doc by ID (document order)
79
+ for (const chunks of byDoc.values()) {
80
+ chunks.sort((a, b) => a.id - b.id);
81
+ }
82
+ return byDoc;
83
+ }
84
+ function cloneChunk(chunk) {
85
+ return {
86
+ id: chunk.id,
87
+ docPath: chunk.docPath,
88
+ docTitle: chunk.docTitle,
89
+ sectionTitle: chunk.sectionTitle,
90
+ content: chunk.content,
91
+ tokens: chunk.tokens,
92
+ score: chunk.score,
93
+ };
94
+ }
95
+ function mergeAdjacentChunks(chunks) {
96
+ const first = chunks[0];
97
+ if (!first || chunks.length <= 1)
98
+ return chunks;
99
+ const merged = [];
100
+ let current = cloneChunk(first);
101
+ for (let i = 1; i < chunks.length; i++) {
102
+ const next = chunks[i];
103
+ if (!next)
104
+ continue;
105
+ // Merge if IDs are adjacent
106
+ if (next.id === current.id + 1) {
107
+ current.sectionTitle = `${current.sectionTitle} / ${next.sectionTitle}`;
108
+ current.content = `${current.content}\n\n${next.content}`;
109
+ current.tokens = current.tokens + next.tokens;
110
+ }
111
+ else {
112
+ merged.push(current);
113
+ current = cloneChunk(next);
114
+ }
115
+ }
116
+ merged.push(current);
117
+ return merged;
118
+ }
119
+ function assembleResults(matches) {
120
+ const byDoc = groupByDocument(matches);
121
+ const results = [];
122
+ // Maintain order: docs with highest-scoring chunk first
123
+ const docOrder = [...new Set(matches.map((m) => m.docPath))];
124
+ for (const docPath of docOrder) {
125
+ const chunks = byDoc.get(docPath);
126
+ if (!chunks)
127
+ continue;
128
+ const merged = mergeAdjacentChunks(chunks);
129
+ for (const chunk of merged) {
130
+ results.push({
131
+ title: `${chunk.docTitle} > ${chunk.sectionTitle}`,
132
+ content: chunk.content,
133
+ source: chunk.docPath,
134
+ });
135
+ }
136
+ }
137
+ return results;
138
+ }
139
+ export function search(db, topic) {
140
+ const name = getMetaValue(db, "name") ?? "unknown";
141
+ const version = getMetaValue(db, "version") ?? "unknown";
142
+ const matches = searchFts(db, topic);
143
+ const filtered = filterByRelevance(matches);
144
+ const budgeted = applyTokenBudget(filtered);
145
+ const results = assembleResults(budgeted);
146
+ return {
147
+ library: `${name}@${version}`,
148
+ version,
149
+ results,
150
+ };
151
+ }
152
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { PackageStore } from "./store.js";
3
+ /**
4
+ * MCP server for documentation retrieval.
5
+ * Accepts a PackageStore to provide the get_docs tool.
6
+ */
7
+ export declare class ContextServer {
8
+ private mcp;
9
+ private store;
10
+ constructor(store: PackageStore);
11
+ /**
12
+ * Start the server with stdio transport.
13
+ * Registers the get_docs tool if packages are available.
14
+ */
15
+ start(): Promise<void>;
16
+ /** Access the underlying McpServer for testing. */
17
+ get server(): McpServer;
18
+ private registerGetDocsTool;
19
+ private handleGetDocs;
20
+ }
package/dist/server.js ADDED
@@ -0,0 +1,108 @@
1
+ import { createRequire } from "node:module";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import { search } from "./search.js";
6
+ const require = createRequire(import.meta.url);
7
+ const { version } = require("../package.json");
8
+ /**
9
+ * MCP server for documentation retrieval.
10
+ * Accepts a PackageStore to provide the get_docs tool.
11
+ */
12
+ export class ContextServer {
13
+ mcp;
14
+ store;
15
+ constructor(store) {
16
+ this.store = store;
17
+ this.mcp = new McpServer({
18
+ name: "context",
19
+ version,
20
+ });
21
+ }
22
+ /**
23
+ * Start the server with stdio transport.
24
+ * Registers the get_docs tool if packages are available.
25
+ */
26
+ async start() {
27
+ const packages = this.store.list();
28
+ if (packages.length > 0) {
29
+ this.registerGetDocsTool(packages);
30
+ }
31
+ const transport = new StdioServerTransport();
32
+ await this.mcp.connect(transport);
33
+ }
34
+ /** Access the underlying McpServer for testing. */
35
+ get server() {
36
+ return this.mcp;
37
+ }
38
+ registerGetDocsTool(packages) {
39
+ const libraryEnum = packages.map(formatLibraryName);
40
+ this.mcp.registerTool("get_docs", {
41
+ description: "Retrieves up-to-date documentation for a library. Use this to get accurate, version-specific information about APIs, usage patterns, and best practices. Returns instantly from local cache - no network latency.",
42
+ inputSchema: {
43
+ library: z
44
+ .enum(libraryEnum)
45
+ .describe("The library to search (name@version)"),
46
+ topic: z
47
+ .string()
48
+ .describe("What you need help with (e.g., 'middleware authentication', 'server components')"),
49
+ },
50
+ }, async ({ library, topic }) => {
51
+ return this.handleGetDocs(packages, library, topic);
52
+ });
53
+ }
54
+ handleGetDocs(packages, library, topic) {
55
+ const pkg = packages.find((p) => formatLibraryName(p) === library);
56
+ if (!pkg) {
57
+ return {
58
+ content: [
59
+ {
60
+ type: "text",
61
+ text: JSON.stringify({ error: `Package not found: ${library}` }),
62
+ },
63
+ ],
64
+ };
65
+ }
66
+ const db = this.store.openDb(pkg.name);
67
+ if (!db) {
68
+ return {
69
+ content: [
70
+ {
71
+ type: "text",
72
+ text: JSON.stringify({
73
+ error: `Failed to open package database: ${library}`,
74
+ }),
75
+ },
76
+ ],
77
+ };
78
+ }
79
+ try {
80
+ const result = search(db, topic);
81
+ return {
82
+ content: [{ type: "text", text: formatSearchResult(result) }],
83
+ };
84
+ }
85
+ finally {
86
+ db.close();
87
+ }
88
+ }
89
+ }
90
+ function formatLibraryName(pkg) {
91
+ return `${pkg.name}@${pkg.version}`;
92
+ }
93
+ function formatSearchResult(result) {
94
+ if (result.results.length === 0) {
95
+ return JSON.stringify({
96
+ library: result.library,
97
+ version: result.version,
98
+ results: [],
99
+ message: "No documentation found. Try different keywords.",
100
+ });
101
+ }
102
+ return JSON.stringify({
103
+ library: result.library,
104
+ version: result.version,
105
+ results: result.results,
106
+ });
107
+ }
108
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import Database from "better-sqlite3";
2
+ export interface PackageMeta {
3
+ name: string;
4
+ version: string;
5
+ description?: string;
6
+ sourceUrl?: string;
7
+ }
8
+ export interface PackageInfo extends PackageMeta {
9
+ path: string;
10
+ sizeBytes: number;
11
+ sectionCount: number;
12
+ }
13
+ /**
14
+ * Registry of documentation packages.
15
+ * Manages an in-memory list of packages without file system operations.
16
+ */
17
+ export declare class PackageStore {
18
+ private packages;
19
+ /** Add a package to the registry. */
20
+ add(info: PackageInfo): void;
21
+ /** Remove a package from the registry by name. Returns true if removed. */
22
+ remove(name: string): boolean;
23
+ /** Get all registered packages. */
24
+ list(): PackageInfo[];
25
+ /** Get a package by name. */
26
+ get(name: string): PackageInfo | null;
27
+ /** Open a package database for searching. */
28
+ openDb(name: string): Database.Database | null;
29
+ }
30
+ /** Read package info from a database file. */
31
+ export declare function readPackageInfo(packagePath: string): PackageInfo;
package/dist/store.js ADDED
@@ -0,0 +1,58 @@
1
+ import { statSync } from "node:fs";
2
+ import Database from "better-sqlite3";
3
+ import { getMetaValue, getSectionCount, validatePackageSchema } from "./db.js";
4
+ /**
5
+ * Registry of documentation packages.
6
+ * Manages an in-memory list of packages without file system operations.
7
+ */
8
+ export class PackageStore {
9
+ packages = new Map();
10
+ /** Add a package to the registry. */
11
+ add(info) {
12
+ this.packages.set(info.name, info);
13
+ }
14
+ /** Remove a package from the registry by name. Returns true if removed. */
15
+ remove(name) {
16
+ return this.packages.delete(name);
17
+ }
18
+ /** Get all registered packages. */
19
+ list() {
20
+ return [...this.packages.values()].sort((a, b) => a.name.localeCompare(b.name));
21
+ }
22
+ /** Get a package by name. */
23
+ get(name) {
24
+ return this.packages.get(name) ?? null;
25
+ }
26
+ /** Open a package database for searching. */
27
+ openDb(name) {
28
+ const pkg = this.packages.get(name);
29
+ if (!pkg)
30
+ return null;
31
+ return new Database(pkg.path, { readonly: true });
32
+ }
33
+ }
34
+ /** Read package info from a database file. */
35
+ export function readPackageInfo(packagePath) {
36
+ const db = new Database(packagePath, { readonly: true });
37
+ try {
38
+ validatePackageSchema(db);
39
+ const name = getMetaValue(db, "name");
40
+ const version = getMetaValue(db, "version");
41
+ if (!name || !version) {
42
+ throw new Error("Invalid package: missing name or version in meta table");
43
+ }
44
+ return {
45
+ name,
46
+ version,
47
+ description: getMetaValue(db, "description"),
48
+ sourceUrl: getMetaValue(db, "source_url"),
49
+ path: packagePath,
50
+ sizeBytes: statSync(packagePath).size,
51
+ sectionCount: getSectionCount(db),
52
+ };
53
+ }
54
+ finally {
55
+ db.close();
56
+ }
57
+ }
58
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAe/E;;;GAGG;AACH,MAAM,OAAO,YAAY;IACf,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;IAElD,qCAAqC;IACrC,GAAG,CAAC,IAAiB;QACnB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACrC,CAAC;IAED,2EAA2E;IAC3E,MAAM,CAAC,IAAY;QACjB,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,mCAAmC;IACnC,IAAI;QACF,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAC/C,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAC7B,CAAC;IACJ,CAAC;IAED,6BAA6B;IAC7B,GAAG,CAAC,IAAY;QACd,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;IACzC,CAAC;IAED,6CAA6C;IAC7C,MAAM,CAAC,IAAY;QACjB,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,OAAO,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC;CACF;AAED,8CAA8C;AAC9C,MAAM,UAAU,eAAe,CAAC,WAAmB;IACjD,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACzD,IAAI,CAAC;QACH,qBAAqB,CAAC,EAAE,CAAC,CAAC;QAE1B,MAAM,IAAI,GAAG,YAAY,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QACtC,MAAM,OAAO,GAAG,YAAY,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;QAE5C,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAC5E,CAAC;QAED,OAAO;YACL,IAAI;YACJ,OAAO;YACP,WAAW,EAAE,YAAY,CAAC,EAAE,EAAE,aAAa,CAAC;YAC5C,SAAS,EAAE,YAAY,CAAC,EAAE,EAAE,YAAY,CAAC;YACzC,IAAI,EAAE,WAAW;YACjB,SAAS,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC,IAAI;YACrC,YAAY,EAAE,eAAe,CAAC,EAAE,CAAC;SAClC,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC"}
@@ -0,0 +1,17 @@
1
+ import Database from "better-sqlite3";
2
+ export interface TestPackageOptions {
3
+ name?: string;
4
+ version?: string;
5
+ description?: string;
6
+ }
7
+ export interface TestChunk {
8
+ docPath: string;
9
+ docTitle: string;
10
+ sectionTitle: string;
11
+ content: string;
12
+ tokens: number;
13
+ hasCode?: number;
14
+ }
15
+ export declare function createTestDb(path: string, options?: TestPackageOptions): Database.Database;
16
+ export declare function insertChunk(db: Database.Database, chunk: TestChunk): void;
17
+ export declare function rebuildFtsIndex(db: Database.Database): void;
@@ -0,0 +1,37 @@
1
+ import Database from "better-sqlite3";
2
+ export function createTestDb(path, options = {}) {
3
+ const db = new Database(path);
4
+ db.exec(`
5
+ CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT);
6
+ CREATE TABLE chunks (
7
+ id INTEGER PRIMARY KEY,
8
+ doc_path TEXT NOT NULL,
9
+ doc_title TEXT NOT NULL,
10
+ section_title TEXT NOT NULL,
11
+ content TEXT NOT NULL,
12
+ tokens INTEGER NOT NULL,
13
+ has_code INTEGER DEFAULT 0
14
+ );
15
+ CREATE VIRTUAL TABLE chunks_fts USING fts5(
16
+ doc_title, section_title, content,
17
+ content='chunks', content_rowid='id',
18
+ tokenize='porter unicode61'
19
+ );
20
+ `);
21
+ const insertMeta = db.prepare("INSERT INTO meta (key, value) VALUES (?, ?)");
22
+ insertMeta.run("name", options.name ?? "test-lib");
23
+ insertMeta.run("version", options.version ?? "1.0.0");
24
+ if (options.description)
25
+ insertMeta.run("description", options.description);
26
+ return db;
27
+ }
28
+ export function insertChunk(db, chunk) {
29
+ db.prepare(`
30
+ INSERT INTO chunks (doc_path, doc_title, section_title, content, tokens, has_code)
31
+ VALUES (?, ?, ?, ?, ?, ?)
32
+ `).run(chunk.docPath, chunk.docTitle, chunk.sectionTitle, chunk.content, chunk.tokens, chunk.hasCode ?? 0);
33
+ }
34
+ export function rebuildFtsIndex(db) {
35
+ db.exec("INSERT INTO chunks_fts(chunks_fts) VALUES('rebuild')");
36
+ }
37
+ //# sourceMappingURL=test-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-utils.js","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAiBtC,MAAM,UAAU,YAAY,CAC1B,IAAY,EACZ,UAA8B,EAAE;IAEhC,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC;IAE9B,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;GAgBP,CAAC,CAAC;IAEH,MAAM,UAAU,GAAG,EAAE,CAAC,OAAO,CAAC,6CAA6C,CAAC,CAAC;IAC7E,UAAU,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,IAAI,UAAU,CAAC,CAAC;IACnD,UAAU,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,CAAC;IACtD,IAAI,OAAO,CAAC,WAAW;QAAE,UAAU,CAAC,GAAG,CAAC,aAAa,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAE5E,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,EAAqB,EAAE,KAAgB;IACjE,EAAE,CAAC,OAAO,CAAC;;;GAGV,CAAC,CAAC,GAAG,CACJ,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,QAAQ,EACd,KAAK,CAAC,YAAY,EAClB,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,MAAM,EACZ,KAAK,CAAC,OAAO,IAAI,CAAC,CACnB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,EAAqB;IACnD,EAAE,CAAC,IAAI,CAAC,sDAAsD,CAAC,CAAC;AAClE,CAAC"}
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@neuledge/context",
3
+ "version": "0.1.0",
4
+ "description": "Context management for Neuledge packages",
5
+ "keywords": [],
6
+ "homepage": "https://neuledge.com",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/neuledge/context.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/neuledge/context/issues"
13
+ },
14
+ "main": "dist/index.js",
15
+ "types": "dist/index.d.ts",
16
+ "type": "module",
17
+ "bin": {
18
+ "context": "./dist/cli.js"
19
+ },
20
+ "exports": {
21
+ ".": "./dist/index.js"
22
+ },
23
+ "files": [
24
+ "dist/",
25
+ "package.json",
26
+ "README.md",
27
+ "LICENSE"
28
+ ],
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "author": "Neuledge",
33
+ "license": "Apache-2.0",
34
+ "dependencies": {
35
+ "@modelcontextprotocol/sdk": "^1.11.4",
36
+ "better-sqlite3": "^11.9.1",
37
+ "commander": "^14.0.0",
38
+ "ignore": "^7.0.5",
39
+ "remark-frontmatter": "^5.0.0",
40
+ "remark-parse": "^11.0.0",
41
+ "unified": "^11.0.5",
42
+ "yaml": "^2.8.2",
43
+ "zod": "^4.3.6"
44
+ },
45
+ "devDependencies": {
46
+ "@total-typescript/tsconfig": "^1.0.4",
47
+ "@types/better-sqlite3": "^7.6.13",
48
+ "@types/mdast": "^4.0.4",
49
+ "@types/node": "^24.9.1",
50
+ "rimraf": "^6.0.1",
51
+ "tsconfig-paths": "^4.2.0",
52
+ "tsx": "^4.20.6",
53
+ "typescript": "^5.9.3",
54
+ "vite-tsconfig-paths": "^5.1.4",
55
+ "vitest": "^4.0.3"
56
+ },
57
+ "tsx": {
58
+ "require": [
59
+ "tsconfig-paths/register"
60
+ ]
61
+ },
62
+ "scripts": {
63
+ "build": "rimraf --glob dist/** && tsc -p tsconfig.build.json",
64
+ "test": "vitest run",
65
+ "test:watch": "vitest",
66
+ "lint": "npx biome ci --error-on-warnings",
67
+ "fix": "npx biome check --write"
68
+ }
69
+ }