@turbomem/okf 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.
package/dist/index.js ADDED
@@ -0,0 +1,248 @@
1
+ import path from 'path';
2
+ import fs2 from 'fs/promises';
3
+ import matter from 'gray-matter';
4
+ import fg from 'fast-glob';
5
+ import { unified } from 'unified';
6
+ import remarkParse from 'remark-parse';
7
+ import { visit } from 'unist-util-visit';
8
+ import { z } from 'zod';
9
+
10
+ // src/parser.ts
11
+ function normalizeRelativePath(relativePath) {
12
+ return relativePath.split(path.sep).join("/");
13
+ }
14
+ function resolveLinkHref(href, documentPath, bundleRoot) {
15
+ const withoutFragment = href.split("#")[0] ?? href;
16
+ if (withoutFragment.startsWith("/")) {
17
+ return path.join(bundleRoot, withoutFragment.slice(1));
18
+ }
19
+ return path.resolve(path.dirname(documentPath), withoutFragment);
20
+ }
21
+ function extractLinks(body, documentPath, bundleRoot, followLinks) {
22
+ const links = [];
23
+ const tree = unified().use(remarkParse).parse(body);
24
+ visit(tree, "link", (node) => {
25
+ const linkNode = node;
26
+ const href = linkNode.url;
27
+ const text = linkNode.children?.[0]?.type === "text" ? linkNode.children[0].value ?? "" : "";
28
+ if (href.startsWith("http://") || href.startsWith("https://")) {
29
+ links.push({ text, href });
30
+ return;
31
+ }
32
+ const link = { text, href };
33
+ if (followLinks) {
34
+ const resolvedAbsolute = resolveLinkHref(href, documentPath, bundleRoot);
35
+ link.resolvedPath = normalizeRelativePath(path.relative(bundleRoot, resolvedAbsolute));
36
+ }
37
+ links.push(link);
38
+ });
39
+ return links;
40
+ }
41
+ function normalizeFrontmatter(data) {
42
+ const normalized = { ...data };
43
+ if (normalized.timestamp instanceof Date) {
44
+ normalized.timestamp = normalized.timestamp.toISOString();
45
+ }
46
+ return normalized;
47
+ }
48
+ async function parseDocument(filePath, bundleRoot, options = {}) {
49
+ const { followLinks = true } = options;
50
+ const raw = await fs2.readFile(filePath, "utf-8");
51
+ const { data, content } = matter(raw);
52
+ const relativePath = normalizeRelativePath(path.relative(bundleRoot, filePath));
53
+ const links = extractLinks(content, filePath, bundleRoot, followLinks);
54
+ return {
55
+ path: filePath,
56
+ relativePath,
57
+ frontmatter: normalizeFrontmatter(data),
58
+ body: content,
59
+ links
60
+ };
61
+ }
62
+ async function parseBundle(rootDir, options = {}) {
63
+ const absoluteRoot = path.resolve(rootDir);
64
+ const ignorePatterns = options.ignore ?? [];
65
+ const filePaths = await fg("**/*.md", {
66
+ cwd: absoluteRoot,
67
+ absolute: true,
68
+ ignore: ignorePatterns
69
+ });
70
+ const documents = await Promise.all(
71
+ filePaths.map((fp) => parseDocument(fp, absoluteRoot, options))
72
+ );
73
+ const index = /* @__PURE__ */ new Map();
74
+ for (const doc of documents) {
75
+ index.set(doc.relativePath, doc);
76
+ }
77
+ return { root: absoluteRoot, documents, index };
78
+ }
79
+ var frontmatterSchema = z.object({
80
+ type: z.string().min(1, "type field must be a non-empty string"),
81
+ title: z.string().optional(),
82
+ description: z.string().optional(),
83
+ resource: z.string().url("resource must be a valid URL").optional(),
84
+ tags: z.array(z.string()).optional(),
85
+ timestamp: z.string().datetime({ offset: true }).optional()
86
+ }).passthrough();
87
+ function validateDocument(doc) {
88
+ const errors = [];
89
+ const warnings = [];
90
+ const result = frontmatterSchema.safeParse(doc.frontmatter);
91
+ if (!result.success) {
92
+ for (const issue of result.error.issues) {
93
+ errors.push({
94
+ path: doc.relativePath,
95
+ message: issue.message,
96
+ rule: "frontmatter-schema"
97
+ });
98
+ }
99
+ }
100
+ if (!doc.body.trim()) {
101
+ warnings.push({
102
+ path: doc.relativePath,
103
+ message: "Document body is empty",
104
+ rule: "empty-body"
105
+ });
106
+ }
107
+ if (doc.frontmatter.timestamp) {
108
+ const d = Date.parse(doc.frontmatter.timestamp);
109
+ if (isNaN(d)) {
110
+ errors.push({
111
+ path: doc.relativePath,
112
+ message: `timestamp "${doc.frontmatter.timestamp}" is not a valid ISO 8601 date`,
113
+ rule: "timestamp-format"
114
+ });
115
+ }
116
+ }
117
+ return { errors, warnings };
118
+ }
119
+ function validateBundle(bundle) {
120
+ const allErrors = [];
121
+ const allWarnings = [];
122
+ for (const doc of bundle.documents) {
123
+ const { errors, warnings } = validateDocument(doc);
124
+ allErrors.push(...errors);
125
+ allWarnings.push(...warnings);
126
+ }
127
+ for (const doc of bundle.documents) {
128
+ for (const link of doc.links) {
129
+ if (link.resolvedPath && !bundle.index.has(link.resolvedPath)) {
130
+ allWarnings.push({
131
+ path: doc.relativePath,
132
+ message: `Cross-link "${link.href}" does not resolve to a known concept in this bundle`,
133
+ rule: "unresolved-cross-link"
134
+ });
135
+ }
136
+ }
137
+ }
138
+ return {
139
+ valid: allErrors.length === 0,
140
+ errors: allErrors,
141
+ warnings: allWarnings
142
+ };
143
+ }
144
+ function serializeDocument(frontmatter, body) {
145
+ return matter.stringify(body, frontmatter);
146
+ }
147
+ async function writeDocument(outputPath, frontmatter, body, options = {}) {
148
+ const { overwrite = false } = options;
149
+ let exists = false;
150
+ try {
151
+ await fs2.access(outputPath);
152
+ exists = true;
153
+ } catch {
154
+ exists = false;
155
+ }
156
+ if (exists && !overwrite) {
157
+ throw new Error(`File already exists: ${outputPath}. Pass overwrite: true to replace it.`);
158
+ }
159
+ await fs2.mkdir(path.dirname(outputPath), { recursive: true });
160
+ const content = serializeDocument(frontmatter, body);
161
+ await fs2.writeFile(outputPath, content, "utf-8");
162
+ }
163
+ async function writeIndex(dirPath, title, description, options = {}) {
164
+ const indexPath = path.join(dirPath, "index.md");
165
+ await writeDocument(
166
+ indexPath,
167
+ { type: "index", title, description },
168
+ `# ${title}
169
+
170
+ ${description}
171
+ `,
172
+ options
173
+ );
174
+ }
175
+
176
+ // src/graph.ts
177
+ function buildGraph(bundle) {
178
+ const nodes = /* @__PURE__ */ new Map();
179
+ const edges = [];
180
+ for (const doc of bundle.documents) {
181
+ nodes.set(doc.relativePath, {
182
+ document: doc,
183
+ outgoing: [],
184
+ incoming: []
185
+ });
186
+ }
187
+ for (const doc of bundle.documents) {
188
+ const fromNode = nodes.get(doc.relativePath);
189
+ for (const link of doc.links) {
190
+ if (!link.resolvedPath) continue;
191
+ const toNode = nodes.get(link.resolvedPath);
192
+ if (!toNode) continue;
193
+ const edge = {
194
+ from: doc,
195
+ to: toNode.document,
196
+ linkText: link.text
197
+ };
198
+ edges.push(edge);
199
+ fromNode.outgoing.push(edge);
200
+ toNode.incoming.push(edge);
201
+ }
202
+ }
203
+ return { nodes, edges };
204
+ }
205
+ function reachableFrom(graph, startRelativePath) {
206
+ const visited = /* @__PURE__ */ new Set();
207
+ const queue = [startRelativePath];
208
+ while (queue.length > 0) {
209
+ const current = queue.shift();
210
+ if (visited.has(current)) continue;
211
+ visited.add(current);
212
+ const node = graph.nodes.get(current);
213
+ if (!node) continue;
214
+ for (const edge of node.outgoing) {
215
+ queue.push(edge.to.relativePath);
216
+ }
217
+ }
218
+ return [...visited];
219
+ }
220
+
221
+ // src/turbomem.ts
222
+ function documentToFacts(doc) {
223
+ const facts = [];
224
+ const fm = doc.frontmatter;
225
+ const parts = [`[${fm.type}]`];
226
+ if (fm.title) parts.push(fm.title);
227
+ if (fm.description) parts.push("\u2014", fm.description);
228
+ if (fm.resource) parts.push(`(resource: ${fm.resource})`);
229
+ if (fm.tags?.length) parts.push(`tags: ${fm.tags.join(", ")}`);
230
+ facts.push(parts.join(" "));
231
+ const body = doc.body.trim();
232
+ if (body) {
233
+ facts.push(`${fm.title ?? doc.relativePath} details: ${body}`);
234
+ }
235
+ return facts;
236
+ }
237
+ function bundleToFacts(bundle) {
238
+ return bundle.documents.flatMap(documentToFacts);
239
+ }
240
+ async function addFromBundle(memory, bundle, scope = {}) {
241
+ const facts = bundleToFacts(bundle);
242
+ if (facts.length === 0) return [];
243
+ return memory.addFacts(facts, scope);
244
+ }
245
+
246
+ export { addFromBundle, buildGraph, bundleToFacts, documentToFacts, parseBundle, parseDocument, reachableFrom, serializeDocument, validateBundle, validateDocument, writeDocument, writeIndex };
247
+ //# sourceMappingURL=index.js.map
248
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/parser.ts","../src/validator.ts","../src/writer.ts","../src/graph.ts","../src/turbomem.ts"],"names":["fs","matter","path"],"mappings":"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","file":"index.js","sourcesContent":["import path from \"node:path\";\nimport fs from \"node:fs/promises\";\nimport matter from \"gray-matter\";\nimport fg from \"fast-glob\";\nimport { unified } from \"unified\";\nimport remarkParse from \"remark-parse\";\nimport { visit } from \"unist-util-visit\";\nimport type { OKFBundle, OKFDocument, OKFLink, OKFFrontmatter, ParseOptions } from \"./types.js\";\n\nfunction normalizeRelativePath(relativePath: string): string {\n return relativePath.split(path.sep).join(\"/\");\n}\n\nfunction resolveLinkHref(href: string, documentPath: string, bundleRoot: string): string {\n const withoutFragment = href.split(\"#\")[0] ?? href;\n if (withoutFragment.startsWith(\"/\")) {\n return path.join(bundleRoot, withoutFragment.slice(1));\n }\n return path.resolve(path.dirname(documentPath), withoutFragment);\n}\n\nfunction extractLinks(\n body: string,\n documentPath: string,\n bundleRoot: string,\n followLinks: boolean,\n): OKFLink[] {\n const links: OKFLink[] = [];\n const tree = unified().use(remarkParse).parse(body);\n\n visit(tree, \"link\", (node) => {\n const linkNode = node as { url: string; children?: Array<{ type?: string; value?: string }> };\n const href = linkNode.url;\n const text = linkNode.children?.[0]?.type === \"text\" ? (linkNode.children[0].value ?? \"\") : \"\";\n\n if (href.startsWith(\"http://\") || href.startsWith(\"https://\")) {\n links.push({ text, href });\n return;\n }\n\n const link: OKFLink = { text, href };\n if (followLinks) {\n const resolvedAbsolute = resolveLinkHref(href, documentPath, bundleRoot);\n link.resolvedPath = normalizeRelativePath(path.relative(bundleRoot, resolvedAbsolute));\n }\n links.push(link);\n });\n\n return links;\n}\n\nfunction normalizeFrontmatter(data: Record<string, unknown>): OKFFrontmatter {\n const normalized = { ...data };\n if (normalized.timestamp instanceof Date) {\n normalized.timestamp = normalized.timestamp.toISOString();\n }\n return normalized as OKFFrontmatter;\n}\n\nexport async function parseDocument(filePath: string, bundleRoot: string, options: ParseOptions = {}): Promise<OKFDocument> {\n const { followLinks = true } = options;\n const raw = await fs.readFile(filePath, \"utf-8\");\n const { data, content } = matter(raw);\n\n const relativePath = normalizeRelativePath(path.relative(bundleRoot, filePath));\n const links = extractLinks(content, filePath, bundleRoot, followLinks);\n\n return {\n path: filePath,\n relativePath,\n frontmatter: normalizeFrontmatter(data as Record<string, unknown>),\n body: content,\n links,\n };\n}\n\nexport async function parseBundle(rootDir: string, options: ParseOptions = {}): Promise<OKFBundle> {\n const absoluteRoot = path.resolve(rootDir);\n const ignorePatterns = options.ignore ?? [];\n\n const filePaths = await fg(\"**/*.md\", {\n cwd: absoluteRoot,\n absolute: true,\n ignore: ignorePatterns,\n });\n\n const documents = await Promise.all(\n filePaths.map((fp) => parseDocument(fp, absoluteRoot, options)),\n );\n\n const index = new Map<string, OKFDocument>();\n for (const doc of documents) {\n index.set(doc.relativePath, doc);\n }\n\n return { root: absoluteRoot, documents, index };\n}\n","import { z } from \"zod\";\nimport type {\n OKFBundle,\n OKFDocument,\n OKFValidationResult,\n OKFValidationError,\n OKFValidationWarning,\n} from \"./types.js\";\n\nconst frontmatterSchema = z\n .object({\n type: z.string().min(1, \"type field must be a non-empty string\"),\n title: z.string().optional(),\n description: z.string().optional(),\n resource: z.string().url(\"resource must be a valid URL\").optional(),\n tags: z.array(z.string()).optional(),\n timestamp: z.string().datetime({ offset: true }).optional(),\n })\n .passthrough();\n\nexport function validateDocument(doc: OKFDocument): {\n errors: OKFValidationError[];\n warnings: OKFValidationWarning[];\n} {\n const errors: OKFValidationError[] = [];\n const warnings: OKFValidationWarning[] = [];\n\n const result = frontmatterSchema.safeParse(doc.frontmatter);\n if (!result.success) {\n for (const issue of result.error.issues) {\n errors.push({\n path: doc.relativePath,\n message: issue.message,\n rule: \"frontmatter-schema\",\n });\n }\n }\n\n if (!doc.body.trim()) {\n warnings.push({\n path: doc.relativePath,\n message: \"Document body is empty\",\n rule: \"empty-body\",\n });\n }\n\n if (doc.frontmatter.timestamp) {\n const d = Date.parse(doc.frontmatter.timestamp);\n if (isNaN(d)) {\n errors.push({\n path: doc.relativePath,\n message: `timestamp \"${doc.frontmatter.timestamp}\" is not a valid ISO 8601 date`,\n rule: \"timestamp-format\",\n });\n }\n }\n\n return { errors, warnings };\n}\n\nexport function validateBundle(bundle: OKFBundle): OKFValidationResult {\n const allErrors: OKFValidationError[] = [];\n const allWarnings: OKFValidationWarning[] = [];\n\n for (const doc of bundle.documents) {\n const { errors, warnings } = validateDocument(doc);\n allErrors.push(...errors);\n allWarnings.push(...warnings);\n }\n\n for (const doc of bundle.documents) {\n for (const link of doc.links) {\n if (link.resolvedPath && !bundle.index.has(link.resolvedPath)) {\n allWarnings.push({\n path: doc.relativePath,\n message: `Cross-link \"${link.href}\" does not resolve to a known concept in this bundle`,\n rule: \"unresolved-cross-link\",\n });\n }\n }\n }\n\n return {\n valid: allErrors.length === 0,\n errors: allErrors,\n warnings: allWarnings,\n };\n}\n","import path from \"node:path\";\nimport fs from \"node:fs/promises\";\nimport matter from \"gray-matter\";\nimport type { OKFFrontmatter, WriteOptions } from \"./types.js\";\n\nexport function serializeDocument(frontmatter: OKFFrontmatter, body: string): string {\n return matter.stringify(body, frontmatter as Record<string, unknown>);\n}\n\nexport async function writeDocument(\n outputPath: string,\n frontmatter: OKFFrontmatter,\n body: string,\n options: WriteOptions = {},\n): Promise<void> {\n const { overwrite = false } = options;\n\n let exists = false;\n try {\n await fs.access(outputPath);\n exists = true;\n } catch {\n exists = false;\n }\n\n if (exists && !overwrite) {\n throw new Error(`File already exists: ${outputPath}. Pass overwrite: true to replace it.`);\n }\n\n await fs.mkdir(path.dirname(outputPath), { recursive: true });\n const content = serializeDocument(frontmatter, body);\n await fs.writeFile(outputPath, content, \"utf-8\");\n}\n\nexport async function writeIndex(\n dirPath: string,\n title: string,\n description: string,\n options: WriteOptions = {},\n): Promise<void> {\n const indexPath = path.join(dirPath, \"index.md\");\n await writeDocument(\n indexPath,\n { type: \"index\", title, description },\n `# ${title}\\n\\n${description}\\n`,\n options,\n );\n}\n","import type { OKFBundle, OKFGraph, OKFNode, OKFEdge } from \"./types.js\";\n\nexport function buildGraph(bundle: OKFBundle): OKFGraph {\n const nodes = new Map<string, OKFNode>();\n const edges: OKFEdge[] = [];\n\n for (const doc of bundle.documents) {\n nodes.set(doc.relativePath, {\n document: doc,\n outgoing: [],\n incoming: [],\n });\n }\n\n for (const doc of bundle.documents) {\n const fromNode = nodes.get(doc.relativePath)!;\n\n for (const link of doc.links) {\n if (!link.resolvedPath) continue;\n const toNode = nodes.get(link.resolvedPath);\n if (!toNode) continue;\n\n const edge: OKFEdge = {\n from: doc,\n to: toNode.document,\n linkText: link.text,\n };\n\n edges.push(edge);\n fromNode.outgoing.push(edge);\n toNode.incoming.push(edge);\n }\n }\n\n return { nodes, edges };\n}\n\nexport function reachableFrom(graph: OKFGraph, startRelativePath: string): string[] {\n const visited = new Set<string>();\n const queue = [startRelativePath];\n\n while (queue.length > 0) {\n const current = queue.shift()!;\n if (visited.has(current)) continue;\n visited.add(current);\n\n const node = graph.nodes.get(current);\n if (!node) continue;\n\n for (const edge of node.outgoing) {\n queue.push(edge.to.relativePath);\n }\n }\n\n return [...visited];\n}\n","import type { OKFBundle, OKFDocument } from \"./types.js\";\n\nexport interface OKFMemoryScope {\n userId?: string;\n agentId?: string;\n sessionId?: string;\n}\n\nexport interface OKFMemory {\n addFacts: (facts: string[], scope: OKFMemoryScope) => Promise<unknown[]>;\n}\n\nexport function documentToFacts(doc: OKFDocument): string[] {\n const facts: string[] = [];\n const fm = doc.frontmatter;\n\n const parts: string[] = [`[${fm.type}]`];\n if (fm.title) parts.push(fm.title);\n if (fm.description) parts.push(\"—\", fm.description);\n if (fm.resource) parts.push(`(resource: ${fm.resource})`);\n if (fm.tags?.length) parts.push(`tags: ${fm.tags.join(\", \")}`);\n\n facts.push(parts.join(\" \"));\n\n const body = doc.body.trim();\n if (body) {\n facts.push(`${fm.title ?? doc.relativePath} details: ${body}`);\n }\n\n return facts;\n}\n\nexport function bundleToFacts(bundle: OKFBundle): string[] {\n return bundle.documents.flatMap(documentToFacts);\n}\n\nexport async function addFromBundle(\n memory: OKFMemory,\n bundle: OKFBundle,\n scope: OKFMemoryScope = {},\n): Promise<unknown[]> {\n const facts = bundleToFacts(bundle);\n if (facts.length === 0) return [];\n return memory.addFacts(facts, scope);\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@turbomem/okf",
3
+ "version": "0.2.0",
4
+ "description": "Open Knowledge Format (OKF) parser, validator, and writer for Node.js",
5
+ "homepage": "https://turbomem.dev",
6
+ "license": "Apache-2.0",
7
+ "author": "arneesh aima",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/turbomem/turbomem.git",
11
+ "directory": "packages/okf"
12
+ },
13
+ "keywords": [
14
+ "okf",
15
+ "open-knowledge-format",
16
+ "knowledge",
17
+ "markdown",
18
+ "agent",
19
+ "turbomem"
20
+ ],
21
+ "type": "module",
22
+ "main": "./dist/index.cjs",
23
+ "module": "./dist/index.js",
24
+ "types": "./dist/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js",
29
+ "require": "./dist/index.cjs"
30
+ }
31
+ },
32
+ "bin": {
33
+ "okf": "./dist/cli/index.js"
34
+ },
35
+ "files": [
36
+ "dist"
37
+ ],
38
+ "dependencies": {
39
+ "fast-glob": "^3.3.3",
40
+ "gray-matter": "^4.0.3",
41
+ "remark": "^15.0.1",
42
+ "remark-parse": "^11.0.0",
43
+ "unified": "^11.0.5",
44
+ "unist-util-visit": "^5.0.0",
45
+ "zod": "^3.22.0"
46
+ },
47
+ "peerDependencies": {
48
+ "turbomem": ">=0.1.0"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "turbomem": {
52
+ "optional": true
53
+ }
54
+ },
55
+ "devDependencies": {
56
+ "turbomem": "0.7.0"
57
+ },
58
+ "scripts": {
59
+ "build": "tsup",
60
+ "test": "vitest run",
61
+ "dev": "tsup --watch",
62
+ "lint": "eslint src",
63
+ "typecheck": "tsc --noEmit"
64
+ },
65
+ "readme": "# @turbomem/okf\n\n[![npm version](https://img.shields.io/npm/v/@turbomem/okf)](https://www.npmjs.com/package/@turbomem/okf) · [Documentation](https://turbomem.dev/adapters/okf)\n\nOpen Knowledge Format (OKF) v0.1 parser, validator, writer, and concept graph for Node.js. Optionally seed [turbomem](https://www.npmjs.com/package/turbomem) agent memory from OKF bundles.\n\n## Install\n\n```bash\nnpm install @turbomem/okf\n```\n\nFor turbomem integration, also install `turbomem`:\n\n```bash\nnpm install @turbomem/okf turbomem\n```\n\n## Standalone usage\n\n```ts\nimport { parseBundle, validateBundle, buildGraph } from \"@turbomem/okf\";\n\nconst bundle = await parseBundle(\"./knowledge\");\nconst result = validateBundle(bundle);\nconst graph = buildGraph(bundle);\n```\n\n## CLI\n\n```bash\nnpx okf validate ./knowledge\nnpx okf parse ./knowledge\n```\n\n## turbomem integration\n\n```ts\nimport { TurboMemory } from \"turbomem\";\nimport { parseBundle, addFromBundle } from \"@turbomem/okf\";\n\nconst memory = new TurboMemory({\n embeddings: \"openai\",\n storage: \"pglite\",\n extraction: { provider: \"openai\", model: \"gpt-4.1-mini\" },\n openai: { apiKey: process.env.OPENAI_API_KEY },\n});\n\nawait memory.init();\n\nconst bundle = await parseBundle(\"./knowledge\");\nawait addFromBundle(memory, bundle, { userId: \"agent_1\" });\n\nconst results = await memory.search(\"How do I compute weekly active users?\", {\n userId: \"agent_1\",\n limit: 5,\n});\n```\n\n## Documentation\n\n**https://turbomem.dev/adapters/okf**\n\n## License\n\nApache License 2.0\n"
66
+ }