agent-knowledge 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 (130) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/LICENSE +21 -0
  3. package/README.md +191 -0
  4. package/dist/dashboard.d.ts +4 -0
  5. package/dist/dashboard.d.ts.map +1 -0
  6. package/dist/dashboard.js +434 -0
  7. package/dist/dashboard.js.map +1 -0
  8. package/dist/embeddings/claude.d.ts +21 -0
  9. package/dist/embeddings/claude.d.ts.map +1 -0
  10. package/dist/embeddings/claude.js +84 -0
  11. package/dist/embeddings/claude.js.map +1 -0
  12. package/dist/embeddings/factory.d.ts +9 -0
  13. package/dist/embeddings/factory.d.ts.map +1 -0
  14. package/dist/embeddings/factory.js +60 -0
  15. package/dist/embeddings/factory.js.map +1 -0
  16. package/dist/embeddings/gemini.d.ts +21 -0
  17. package/dist/embeddings/gemini.d.ts.map +1 -0
  18. package/dist/embeddings/gemini.js +86 -0
  19. package/dist/embeddings/gemini.js.map +1 -0
  20. package/dist/embeddings/index.d.ts +4 -0
  21. package/dist/embeddings/index.d.ts.map +1 -0
  22. package/dist/embeddings/index.js +3 -0
  23. package/dist/embeddings/index.js.map +1 -0
  24. package/dist/embeddings/local.d.ts +20 -0
  25. package/dist/embeddings/local.d.ts.map +1 -0
  26. package/dist/embeddings/local.js +73 -0
  27. package/dist/embeddings/local.js.map +1 -0
  28. package/dist/embeddings/openai.d.ts +20 -0
  29. package/dist/embeddings/openai.d.ts.map +1 -0
  30. package/dist/embeddings/openai.js +84 -0
  31. package/dist/embeddings/openai.js.map +1 -0
  32. package/dist/embeddings/types.d.ts +39 -0
  33. package/dist/embeddings/types.d.ts.map +1 -0
  34. package/dist/embeddings/types.js +12 -0
  35. package/dist/embeddings/types.js.map +1 -0
  36. package/dist/index.d.ts +2 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +18 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/knowledge/distill.d.ts +27 -0
  41. package/dist/knowledge/distill.d.ts.map +1 -0
  42. package/dist/knowledge/distill.js +407 -0
  43. package/dist/knowledge/distill.js.map +1 -0
  44. package/dist/knowledge/git.d.ts +30 -0
  45. package/dist/knowledge/git.d.ts.map +1 -0
  46. package/dist/knowledge/git.js +228 -0
  47. package/dist/knowledge/git.js.map +1 -0
  48. package/dist/knowledge/search.d.ts +20 -0
  49. package/dist/knowledge/search.d.ts.map +1 -0
  50. package/dist/knowledge/search.js +72 -0
  51. package/dist/knowledge/search.js.map +1 -0
  52. package/dist/knowledge/store.d.ts +47 -0
  53. package/dist/knowledge/store.d.ts.map +1 -0
  54. package/dist/knowledge/store.js +173 -0
  55. package/dist/knowledge/store.js.map +1 -0
  56. package/dist/search/excerpt.d.ts +12 -0
  57. package/dist/search/excerpt.d.ts.map +1 -0
  58. package/dist/search/excerpt.js +28 -0
  59. package/dist/search/excerpt.js.map +1 -0
  60. package/dist/search/fuzzy.d.ts +15 -0
  61. package/dist/search/fuzzy.d.ts.map +1 -0
  62. package/dist/search/fuzzy.js +81 -0
  63. package/dist/search/fuzzy.js.map +1 -0
  64. package/dist/search/tfidf.d.ts +19 -0
  65. package/dist/search/tfidf.d.ts.map +1 -0
  66. package/dist/search/tfidf.js +200 -0
  67. package/dist/search/tfidf.js.map +1 -0
  68. package/dist/search/types.d.ts +19 -0
  69. package/dist/search/types.d.ts.map +1 -0
  70. package/dist/search/types.js +2 -0
  71. package/dist/search/types.js.map +1 -0
  72. package/dist/server.d.ts +3 -0
  73. package/dist/server.d.ts.map +1 -0
  74. package/dist/server.js +518 -0
  75. package/dist/server.js.map +1 -0
  76. package/dist/sessions/indexer.d.ts +15 -0
  77. package/dist/sessions/indexer.d.ts.map +1 -0
  78. package/dist/sessions/indexer.js +182 -0
  79. package/dist/sessions/indexer.js.map +1 -0
  80. package/dist/sessions/parser.d.ts +58 -0
  81. package/dist/sessions/parser.d.ts.map +1 -0
  82. package/dist/sessions/parser.js +142 -0
  83. package/dist/sessions/parser.js.map +1 -0
  84. package/dist/sessions/scopes.d.ts +16 -0
  85. package/dist/sessions/scopes.d.ts.map +1 -0
  86. package/dist/sessions/scopes.js +153 -0
  87. package/dist/sessions/scopes.js.map +1 -0
  88. package/dist/sessions/search.d.ts +26 -0
  89. package/dist/sessions/search.d.ts.map +1 -0
  90. package/dist/sessions/search.js +256 -0
  91. package/dist/sessions/search.js.map +1 -0
  92. package/dist/sessions/summary.d.ts +28 -0
  93. package/dist/sessions/summary.d.ts.map +1 -0
  94. package/dist/sessions/summary.js +135 -0
  95. package/dist/sessions/summary.js.map +1 -0
  96. package/dist/types.d.ts +26 -0
  97. package/dist/types.d.ts.map +1 -0
  98. package/dist/types.js +109 -0
  99. package/dist/types.js.map +1 -0
  100. package/dist/ui/app.js +1029 -0
  101. package/dist/ui/index.html +373 -0
  102. package/dist/ui/styles.css +1508 -0
  103. package/dist/ui/ui/app.js +811 -0
  104. package/dist/ui/ui/index.html +300 -0
  105. package/dist/ui/ui/styles.css +1154 -0
  106. package/dist/validate.d.ts +21 -0
  107. package/dist/validate.d.ts.map +1 -0
  108. package/dist/validate.js +86 -0
  109. package/dist/validate.js.map +1 -0
  110. package/dist/vectorstore/chunker.d.ts +48 -0
  111. package/dist/vectorstore/chunker.d.ts.map +1 -0
  112. package/dist/vectorstore/chunker.js +165 -0
  113. package/dist/vectorstore/chunker.js.map +1 -0
  114. package/dist/vectorstore/index.d.ts +5 -0
  115. package/dist/vectorstore/index.d.ts.map +1 -0
  116. package/dist/vectorstore/index.js +3 -0
  117. package/dist/vectorstore/index.js.map +1 -0
  118. package/dist/vectorstore/store.d.ts +139 -0
  119. package/dist/vectorstore/store.d.ts.map +1 -0
  120. package/dist/vectorstore/store.js +500 -0
  121. package/dist/vectorstore/store.js.map +1 -0
  122. package/dist/version.d.ts +2 -0
  123. package/dist/version.d.ts.map +1 -0
  124. package/dist/version.js +2 -0
  125. package/dist/version.js.map +1 -0
  126. package/docs/ARCHITECTURE.md +244 -0
  127. package/docs/DASHBOARD.md +133 -0
  128. package/docs/SETUP.md +178 -0
  129. package/package.json +92 -0
  130. package/scripts/copy-ui.js +6 -0
@@ -0,0 +1,228 @@
1
+ import { execFileSync } from 'child_process';
2
+ import { existsSync, mkdirSync, writeFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ const CATEGORIES = ['projects', 'people', 'decisions', 'workflows', 'notes'];
5
+ /**
6
+ * Run a git command safely using execFileSync (no shell).
7
+ */
8
+ function execGit(args, cwd, timeout) {
9
+ try {
10
+ const output = execFileSync('git', args, {
11
+ cwd,
12
+ stdio: 'pipe',
13
+ timeout,
14
+ });
15
+ return output ? output.toString().trim() : '';
16
+ }
17
+ catch (err) {
18
+ const e = err;
19
+ const msg = e.stderr ? e.stderr.toString().trim() : (e.message ?? String(err));
20
+ throw new Error(`git ${args[0]} failed: ${msg}`, { cause: err });
21
+ }
22
+ }
23
+ /**
24
+ * Pull latest changes from remote with rebase.
25
+ */
26
+ export async function gitPull(dir) {
27
+ try {
28
+ const output = execGit(['pull', '--rebase', '--quiet'], dir, 15_000);
29
+ return { success: true, message: output || 'up to date' };
30
+ }
31
+ catch (err) {
32
+ return { success: false, message: err instanceof Error ? err.message : String(err) };
33
+ }
34
+ }
35
+ /**
36
+ * Stage all changes, commit if dirty, and push.
37
+ * Only commits when there are staged changes.
38
+ */
39
+ export async function gitPush(dir, commitMsg) {
40
+ const message = commitMsg ?? 'update knowledge base';
41
+ try {
42
+ execGit(['add', '-A'], dir, 5_000);
43
+ // Only commit if there are staged changes
44
+ try {
45
+ execGit(['diff', '--cached', '--quiet'], dir, 5_000);
46
+ // No error means no changes — skip commit
47
+ }
48
+ catch {
49
+ // diff --cached --quiet exits non-zero when there ARE changes
50
+ execGit(['commit', '-m', message], dir, 5_000);
51
+ }
52
+ execGit(['push', '--quiet'], dir, 15_000);
53
+ return { success: true, message: 'pushed successfully' };
54
+ }
55
+ catch (err) {
56
+ return { success: false, message: err instanceof Error ? err.message : String(err) };
57
+ }
58
+ }
59
+ /**
60
+ * Pull then push — full bidirectional sync.
61
+ */
62
+ export async function gitSync(dir) {
63
+ const pull = await gitPull(dir);
64
+ const push = await gitPush(dir);
65
+ return { pull, push };
66
+ }
67
+ // ── Scaffold templates ──────────────────────────────────────────────────────
68
+ const SCAFFOLD_GITIGNORE = `# OS
69
+ .DS_Store
70
+ Thumbs.db
71
+ Desktop.ini
72
+
73
+ # Editors
74
+ *.swp
75
+ *.swo
76
+ *~
77
+ .vscode/
78
+ .idea/
79
+
80
+ # Secrets — never commit these
81
+ .env
82
+ .env.*
83
+ *.pem
84
+ *.key
85
+ credentials.*
86
+ secrets.*
87
+
88
+ # Obsidian — keep config, ignore workspace cache
89
+ .obsidian/workspace.json
90
+ .obsidian/workspace-mobile.json
91
+ .obsidian/cache/
92
+ `;
93
+ const SCAFFOLD_README = `# Knowledge Base
94
+
95
+ Shared knowledge base managed by [agent-knowledge](https://github.com/keshrath/agent-knowledge).
96
+
97
+ ## Structure
98
+
99
+ \`\`\`
100
+ ├── projects/ — Per-project context, architecture, tech stacks
101
+ ├── people/ — Team members, roles, contacts
102
+ ├── decisions/ — Architecture decisions, trade-offs
103
+ ├── workflows/ — Processes, deployment steps, runbooks
104
+ └── notes/ — General notes, research, ideas
105
+ \`\`\`
106
+
107
+ ## Auto-Distillation
108
+
109
+ Agent-knowledge automatically distills session insights into \`projects/\` entries:
110
+ - Topics discussed, tools used, files touched
111
+ - All content is scrubbed for secrets before committing
112
+ - Runs after each server startup
113
+
114
+ ## Usage
115
+
116
+ ### Via MCP tools
117
+
118
+ - \`knowledge_list\` / \`knowledge_read\` / \`knowledge_write\` — browse and edit entries
119
+ - \`knowledge_search\` — hybrid semantic + keyword search
120
+ - \`knowledge_sync\` — manual git pull + push
121
+ - \`knowledge_config\` — view or update settings
122
+
123
+ ### File format
124
+
125
+ Markdown with optional YAML frontmatter:
126
+
127
+ \`\`\`markdown
128
+ ---
129
+ title: My Project
130
+ tags: [backend, api]
131
+ updated: 2026-01-01
132
+ ---
133
+
134
+ # My Project
135
+
136
+ Content here.
137
+ \`\`\`
138
+
139
+ ## Security
140
+
141
+ Content is scrubbed before every git push:
142
+ - API keys, tokens, passwords, JWTs, private keys → redacted
143
+ - System noise (XML tags, task notifications) → stripped
144
+ - Absolute user paths → normalized to \`~/\`
145
+ - Final audit blocks writes that still contain sensitive patterns
146
+ `;
147
+ /**
148
+ * Write scaffold files (README, .gitignore, category dirs) into a new
149
+ * knowledge base directory. Skips files that already exist.
150
+ */
151
+ function scaffoldRepo(dir) {
152
+ if (!existsSync(dir)) {
153
+ mkdirSync(dir, { recursive: true });
154
+ }
155
+ for (const cat of CATEGORIES) {
156
+ const catDir = join(dir, cat);
157
+ if (!existsSync(catDir)) {
158
+ mkdirSync(catDir, { recursive: true });
159
+ }
160
+ }
161
+ const readme = join(dir, 'README.md');
162
+ if (!existsSync(readme)) {
163
+ writeFileSync(readme, SCAFFOLD_README, 'utf-8');
164
+ }
165
+ const gitignore = join(dir, '.gitignore');
166
+ if (!existsSync(gitignore)) {
167
+ writeFileSync(gitignore, SCAFFOLD_GITIGNORE, 'utf-8');
168
+ }
169
+ }
170
+ // ── Repo initialization ─────────────────────────────────────────────────────
171
+ /**
172
+ * Ensure the memory directory exists and is a git repo.
173
+ *
174
+ * - If dir missing + gitUrl set → clone, then scaffold missing files
175
+ * - If dir exists but not a git repo + gitUrl set → scaffold + init + push
176
+ * - If dir missing + no gitUrl → scaffold local-only dir
177
+ * - If dir exists + is a git repo → no-op
178
+ */
179
+ export function ensureRepo(dir, gitUrl) {
180
+ const isGitRepo = existsSync(join(dir, '.git'));
181
+ if (isGitRepo) {
182
+ return { success: true, message: 'repo already exists' };
183
+ }
184
+ if (!existsSync(dir) && gitUrl) {
185
+ try {
186
+ execGit(['clone', gitUrl, dir], process.cwd(), 30_000);
187
+ scaffoldRepo(dir);
188
+ try {
189
+ execGit(['add', '-A'], dir, 5_000);
190
+ execGit(['diff', '--cached', '--quiet'], dir, 5_000);
191
+ }
192
+ catch {
193
+ execGit(['commit', '-m', 'add scaffold files'], dir, 5_000);
194
+ execGit(['push', '--quiet'], dir, 15_000);
195
+ }
196
+ return { success: true, message: `cloned ${gitUrl} into ${dir}` };
197
+ }
198
+ catch (err) {
199
+ return { success: false, message: err instanceof Error ? err.message : String(err) };
200
+ }
201
+ }
202
+ scaffoldRepo(dir);
203
+ if (gitUrl) {
204
+ try {
205
+ execGit(['init'], dir, 5_000);
206
+ execGit(['remote', 'add', 'origin', gitUrl], dir, 5_000);
207
+ execGit(['add', '-A'], dir, 5_000);
208
+ try {
209
+ execGit(['commit', '-m', 'init knowledge base'], dir, 5_000);
210
+ }
211
+ catch {
212
+ /* no files to commit is fine */
213
+ }
214
+ try {
215
+ execGit(['push', '-u', 'origin', 'HEAD', '--quiet'], dir, 15_000);
216
+ }
217
+ catch {
218
+ /* remote may have existing content — pull first next time */
219
+ }
220
+ return { success: true, message: `initialized repo with remote ${gitUrl}` };
221
+ }
222
+ catch (err) {
223
+ return { success: false, message: err instanceof Error ? err.message : String(err) };
224
+ }
225
+ }
226
+ return { success: true, message: 'created local-only memory directory (no git URL configured)' };
227
+ }
228
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/knowledge/git.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { KnowledgeEntry } from './store.js';
2
+ export interface SearchOptions {
3
+ category?: string;
4
+ maxResults?: number;
5
+ caseSensitive?: boolean;
6
+ }
7
+ export interface SearchResult {
8
+ entry: KnowledgeEntry;
9
+ score: number;
10
+ excerpt: string;
11
+ }
12
+ /**
13
+ * Search knowledge entries using TF-IDF ranking with regex fallback.
14
+ *
15
+ * Builds a TF-IDF index from all entries, searches by query, and returns
16
+ * ranked results with excerpts. Falls back to regex search if TF-IDF
17
+ * returns no results (useful for exact phrase matches).
18
+ */
19
+ export declare function searchKnowledge(dir: string, query: string, options?: SearchOptions): Array<SearchResult>;
20
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/knowledge/search.ts"],"names":[],"mappings":"AAEA,OAAO,EAA0B,cAAc,EAAE,MAAM,YAAY,CAAC;AAEpE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,cAAc,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,aAAkB,GAC1B,KAAK,CAAC,YAAY,CAAC,CAiErB"}
@@ -0,0 +1,72 @@
1
+ import { TfIdfIndex } from '../search/tfidf.js';
2
+ import { buildExcerpt } from '../search/excerpt.js';
3
+ import { listEntries, readEntry } from './store.js';
4
+ /**
5
+ * Search knowledge entries using TF-IDF ranking with regex fallback.
6
+ *
7
+ * Builds a TF-IDF index from all entries, searches by query, and returns
8
+ * ranked results with excerpts. Falls back to regex search if TF-IDF
9
+ * returns no results (useful for exact phrase matches).
10
+ */
11
+ export function searchKnowledge(dir, query, options = {}) {
12
+ const { category, maxResults = 10, caseSensitive = false } = options;
13
+ // Gather all entries with their content
14
+ const entries = listEntries(dir, category);
15
+ const documents = [];
16
+ for (const entry of entries) {
17
+ try {
18
+ const { content } = readEntry(dir, entry.path);
19
+ documents.push({ entry: { ...entry, content }, content });
20
+ }
21
+ catch {
22
+ continue;
23
+ }
24
+ }
25
+ if (documents.length === 0)
26
+ return [];
27
+ // Build TF-IDF index
28
+ const index = new TfIdfIndex();
29
+ for (const doc of documents) {
30
+ index.addDocument(doc.entry.path, doc.content);
31
+ }
32
+ // Search using TF-IDF
33
+ const tfidfResults = index.search(query, maxResults);
34
+ if (tfidfResults.length > 0) {
35
+ const results = [];
36
+ for (const result of tfidfResults) {
37
+ const doc = documents.find((d) => d.entry.path === result.id);
38
+ if (!doc)
39
+ continue;
40
+ results.push({
41
+ entry: doc.entry,
42
+ score: result.score,
43
+ excerpt: buildExcerpt(doc.content, query, { caseSensitive, contextAfter: 200 }),
44
+ });
45
+ }
46
+ return results;
47
+ }
48
+ // Fallback: regex search for exact phrase matches
49
+ const flags = caseSensitive ? 'g' : 'gi';
50
+ let regex;
51
+ try {
52
+ regex = new RegExp(query, flags);
53
+ }
54
+ catch {
55
+ regex = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), flags);
56
+ }
57
+ const regexResults = [];
58
+ for (const doc of documents) {
59
+ regex.lastIndex = 0; // Reset before test() — global regex is stateful
60
+ if (regex.test(doc.content)) {
61
+ regexResults.push({
62
+ entry: doc.entry,
63
+ score: 1,
64
+ excerpt: buildExcerpt(doc.content, query, { caseSensitive, contextAfter: 200 }),
65
+ });
66
+ if (regexResults.length >= maxResults)
67
+ break;
68
+ }
69
+ }
70
+ return regexResults;
71
+ }
72
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/knowledge/search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ export declare const CATEGORIES: readonly ["projects", "people", "decisions", "workflows", "notes"];
2
+ export type Category = (typeof CATEGORIES)[number];
3
+ /**
4
+ * Sanitize a path component — reject null bytes and path traversal.
5
+ */
6
+ export declare function sanitizePath(input: string): string;
7
+ export interface KnowledgeEntry {
8
+ path: string;
9
+ title: string;
10
+ tags: string[];
11
+ updated: string;
12
+ category: string;
13
+ content?: string;
14
+ }
15
+ /**
16
+ * Parse YAML-like frontmatter delimited by `---`.
17
+ * Extracts title, tags (as array), updated, and any other simple key-value pairs.
18
+ */
19
+ export declare function parseFrontmatter(content: string): {
20
+ meta: Record<string, string | string[]>;
21
+ body: string;
22
+ };
23
+ /**
24
+ * List all knowledge entries, optionally filtered by category and/or tag.
25
+ */
26
+ export declare function listEntries(dir: string, category?: string, tag?: string): KnowledgeEntry[];
27
+ /**
28
+ * Read a specific entry by its relative path.
29
+ * Includes path traversal protection.
30
+ */
31
+ export declare function readEntry(dir: string, entryPath: string): {
32
+ entry: KnowledgeEntry;
33
+ content: string;
34
+ };
35
+ /**
36
+ * Write a knowledge entry to disk.
37
+ * Validates the category, ensures the directory exists, and auto-adds .md extension.
38
+ * Returns the relative path of the written file.
39
+ */
40
+ export declare function writeEntry(dir: string, category: string, filename: string, content: string): string;
41
+ /**
42
+ * Delete a knowledge entry.
43
+ * Includes path traversal protection.
44
+ * Returns true if the file was deleted, false if it didn't exist.
45
+ */
46
+ export declare function deleteEntry(dir: string, entryPath: string): boolean;
47
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/knowledge/store.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,UAAU,oEAAqE,CAAC;AAC7F,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnD;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CASlD;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG;IACjD,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IACxC,IAAI,EAAE,MAAM,CAAC;CACd,CAwBA;AAuBD;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE,CAoC1F;AAED;;;GAGG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,GAChB;IAAE,KAAK,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CA4B5C;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,MAAM,CA0BR;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAenE"}
@@ -0,0 +1,173 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ export const CATEGORIES = ['projects', 'people', 'decisions', 'workflows', 'notes'];
4
+ /**
5
+ * Sanitize a path component — reject null bytes and path traversal.
6
+ */
7
+ export function sanitizePath(input) {
8
+ if (input.includes('\0')) {
9
+ throw new Error('Path contains null bytes');
10
+ }
11
+ const normalized = input.replace(/\\/g, '/');
12
+ if (normalized.includes('..') || normalized.startsWith('/')) {
13
+ throw new Error('Path traversal not allowed');
14
+ }
15
+ return input;
16
+ }
17
+ /**
18
+ * Parse YAML-like frontmatter delimited by `---`.
19
+ * Extracts title, tags (as array), updated, and any other simple key-value pairs.
20
+ */
21
+ export function parseFrontmatter(content) {
22
+ // Normalize \r\n to \n for cross-platform compatibility
23
+ const normalized = content.replace(/\r\n/g, '\n');
24
+ const match = normalized.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
25
+ if (!match)
26
+ return { meta: {}, body: content };
27
+ const meta = {};
28
+ for (const line of match[1].split('\n')) {
29
+ const m = line.match(/^(\w+):\s*(.+)$/);
30
+ if (m) {
31
+ let val = m[2].trim();
32
+ // Parse inline arrays like [tag1, tag2, tag3]
33
+ if (val.startsWith('[') && val.endsWith(']')) {
34
+ val = val
35
+ .slice(1, -1)
36
+ .split(',')
37
+ .map((s) => s.trim())
38
+ .filter((s) => s.length > 0);
39
+ }
40
+ meta[m[1]] = val;
41
+ }
42
+ }
43
+ return { meta, body: match[2] };
44
+ }
45
+ /**
46
+ * Recursively collect all .md files under a directory.
47
+ * Skips directories starting with `.`
48
+ */
49
+ function getAllFiles(dir, base = '') {
50
+ const results = [];
51
+ if (!fs.existsSync(dir))
52
+ return results;
53
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
54
+ const rel = base ? `${base}/${entry.name}` : entry.name;
55
+ if (entry.isDirectory()) {
56
+ if (entry.name.startsWith('.'))
57
+ continue;
58
+ results.push(...getAllFiles(path.join(dir, entry.name), rel));
59
+ }
60
+ else if (entry.name.endsWith('.md')) {
61
+ results.push(rel);
62
+ }
63
+ }
64
+ return results;
65
+ }
66
+ /**
67
+ * List all knowledge entries, optionally filtered by category and/or tag.
68
+ */
69
+ export function listEntries(dir, category, tag) {
70
+ const searchDir = category ? path.join(dir, category) : dir;
71
+ const basePrefix = category || '';
72
+ const files = getAllFiles(searchDir, basePrefix);
73
+ const entries = [];
74
+ for (const file of files) {
75
+ const fullPath = path.join(dir, file);
76
+ try {
77
+ const content = fs.readFileSync(fullPath, 'utf-8');
78
+ const { meta } = parseFrontmatter(content);
79
+ // Filter by tag if specified
80
+ if (tag) {
81
+ const tags = Array.isArray(meta.tags) ? meta.tags : [];
82
+ if (!tags.some((t) => t.toLowerCase() === tag.toLowerCase())) {
83
+ continue;
84
+ }
85
+ }
86
+ // Derive category from the first path segment
87
+ const entryCategory = file.includes('/') ? file.split('/')[0] : '';
88
+ entries.push({
89
+ path: file,
90
+ title: (typeof meta.title === 'string' ? meta.title : '') || file.replace(/\.md$/, ''),
91
+ tags: Array.isArray(meta.tags) ? meta.tags : [],
92
+ updated: (typeof meta.updated === 'string' ? meta.updated : '') || '',
93
+ category: entryCategory,
94
+ });
95
+ }
96
+ catch {
97
+ continue;
98
+ }
99
+ }
100
+ return entries;
101
+ }
102
+ /**
103
+ * Read a specific entry by its relative path.
104
+ * Includes path traversal protection.
105
+ */
106
+ export function readEntry(dir, entryPath) {
107
+ sanitizePath(entryPath);
108
+ const filePath = path.resolve(dir, entryPath);
109
+ // Path traversal protection
110
+ if (!filePath.startsWith(path.resolve(dir))) {
111
+ throw new Error('Path traversal not allowed');
112
+ }
113
+ if (!fs.existsSync(filePath)) {
114
+ throw new Error(`File not found: ${entryPath}`);
115
+ }
116
+ const content = fs.readFileSync(filePath, 'utf-8');
117
+ const { meta, body } = parseFrontmatter(content);
118
+ const entryCategory = entryPath.includes('/') ? entryPath.split('/')[0] : '';
119
+ const entry = {
120
+ path: entryPath,
121
+ title: (typeof meta.title === 'string' ? meta.title : '') || entryPath.replace(/\.md$/, ''),
122
+ tags: Array.isArray(meta.tags) ? meta.tags : [],
123
+ updated: (typeof meta.updated === 'string' ? meta.updated : '') || '',
124
+ category: entryCategory,
125
+ content: body,
126
+ };
127
+ return { entry, content };
128
+ }
129
+ /**
130
+ * Write a knowledge entry to disk.
131
+ * Validates the category, ensures the directory exists, and auto-adds .md extension.
132
+ * Returns the relative path of the written file.
133
+ */
134
+ export function writeEntry(dir, category, filename, content) {
135
+ if (!CATEGORIES.includes(category)) {
136
+ throw new Error(`Invalid category: ${category}. Must be one of: ${CATEGORIES.join(', ')}`);
137
+ }
138
+ // Reject filenames with path separators or traversal attempts
139
+ if (/[/\\]/.test(filename) || filename.includes('..')) {
140
+ throw new Error('Filename must not contain path separators or ".."');
141
+ }
142
+ const safeName = filename.endsWith('.md') ? filename : `${filename}.md`;
143
+ const categoryDir = path.join(dir, category);
144
+ if (!fs.existsSync(categoryDir)) {
145
+ fs.mkdirSync(categoryDir, { recursive: true });
146
+ }
147
+ const filePath = path.resolve(categoryDir, safeName);
148
+ // Path traversal protection
149
+ if (!filePath.startsWith(path.resolve(dir))) {
150
+ throw new Error('Path traversal not allowed');
151
+ }
152
+ fs.writeFileSync(filePath, content, 'utf-8');
153
+ return `${category}/${safeName}`;
154
+ }
155
+ /**
156
+ * Delete a knowledge entry.
157
+ * Includes path traversal protection.
158
+ * Returns true if the file was deleted, false if it didn't exist.
159
+ */
160
+ export function deleteEntry(dir, entryPath) {
161
+ sanitizePath(entryPath);
162
+ const filePath = path.resolve(dir, entryPath);
163
+ // Path traversal protection
164
+ if (!filePath.startsWith(path.resolve(dir))) {
165
+ throw new Error('Path traversal not allowed');
166
+ }
167
+ if (!fs.existsSync(filePath)) {
168
+ return false;
169
+ }
170
+ fs.unlinkSync(filePath);
171
+ return true;
172
+ }
173
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/knowledge/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Shared excerpt-building utility.
3
+ *
4
+ * Extracts a window of text around the first occurrence of `query`,
5
+ * with configurable context on each side.
6
+ */
7
+ export declare function buildExcerpt(text: string, query: string, options?: {
8
+ contextBefore?: number;
9
+ contextAfter?: number;
10
+ caseSensitive?: boolean;
11
+ }): string;
12
+ //# sourceMappingURL=excerpt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"excerpt.d.ts","sourceRoot":"","sources":["../../src/search/excerpt.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GACvF,MAAM,CAwBR"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Shared excerpt-building utility.
3
+ *
4
+ * Extracts a window of text around the first occurrence of `query`,
5
+ * with configurable context on each side.
6
+ */
7
+ export function buildExcerpt(text, query, options = {}) {
8
+ const { contextBefore = 100, contextAfter = 100, caseSensitive = false } = options;
9
+ if (!text)
10
+ return '';
11
+ if (!query)
12
+ return text.substring(0, 300) + (text.length > 300 ? '...' : '');
13
+ let idx;
14
+ try {
15
+ idx = text.search(new RegExp(query, caseSensitive ? '' : 'i'));
16
+ }
17
+ catch {
18
+ const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
19
+ idx = text.search(new RegExp(escaped, caseSensitive ? '' : 'i'));
20
+ }
21
+ if (idx === -1) {
22
+ return text.substring(0, 300) + (text.length > 300 ? '...' : '');
23
+ }
24
+ const start = Math.max(0, idx - contextBefore);
25
+ const end = Math.min(text.length, idx + query.length + contextAfter);
26
+ return ((start > 0 ? '...' : '') + text.substring(start, end).trim() + (end < text.length ? '...' : ''));
27
+ }
28
+ //# sourceMappingURL=excerpt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"excerpt.js","sourceRoot":"","sources":["../../src/search/excerpt.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAY,EACZ,KAAa,EACb,UAAsF,EAAE;IAExF,MAAM,EAAE,aAAa,GAAG,GAAG,EAAE,YAAY,GAAG,GAAG,EAAE,aAAa,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC;IAEnF,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAE7E,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACjE,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;QAC7D,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACnE,CAAC;IAED,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;QACf,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACnE,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,aAAa,CAAC,CAAC;IAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,KAAK,CAAC,MAAM,GAAG,YAAY,CAAC,CAAC;IAErE,OAAO,CACL,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAChG,CAAC;AACJ,CAAC"}
@@ -0,0 +1,15 @@
1
+ export declare function levenshtein(a: string, b: string): number;
2
+ export declare function fuzzyMatch(needle: string, haystack: string, threshold?: number): Array<{
3
+ start: number;
4
+ end: number;
5
+ score: number;
6
+ }>;
7
+ export declare function fuzzySearch(query: string, texts: Array<{
8
+ id: string;
9
+ text: string;
10
+ }>, threshold?: number): Array<{
11
+ id: string;
12
+ score: number;
13
+ excerpt: string;
14
+ }>;
15
+ //# sourceMappingURL=fuzzy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fuzzy.d.ts","sourceRoot":"","sources":["../../src/search/fuzzy.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CA0BxD;AAED,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,MAAY,GACtB,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAgDtD;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EAC1C,SAAS,GAAE,MAAY,GACtB,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAuBvD"}