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.
- package/CHANGELOG.md +77 -0
- package/LICENSE +21 -0
- package/README.md +191 -0
- package/dist/dashboard.d.ts +4 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +434 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/embeddings/claude.d.ts +21 -0
- package/dist/embeddings/claude.d.ts.map +1 -0
- package/dist/embeddings/claude.js +84 -0
- package/dist/embeddings/claude.js.map +1 -0
- package/dist/embeddings/factory.d.ts +9 -0
- package/dist/embeddings/factory.d.ts.map +1 -0
- package/dist/embeddings/factory.js +60 -0
- package/dist/embeddings/factory.js.map +1 -0
- package/dist/embeddings/gemini.d.ts +21 -0
- package/dist/embeddings/gemini.d.ts.map +1 -0
- package/dist/embeddings/gemini.js +86 -0
- package/dist/embeddings/gemini.js.map +1 -0
- package/dist/embeddings/index.d.ts +4 -0
- package/dist/embeddings/index.d.ts.map +1 -0
- package/dist/embeddings/index.js +3 -0
- package/dist/embeddings/index.js.map +1 -0
- package/dist/embeddings/local.d.ts +20 -0
- package/dist/embeddings/local.d.ts.map +1 -0
- package/dist/embeddings/local.js +73 -0
- package/dist/embeddings/local.js.map +1 -0
- package/dist/embeddings/openai.d.ts +20 -0
- package/dist/embeddings/openai.d.ts.map +1 -0
- package/dist/embeddings/openai.js +84 -0
- package/dist/embeddings/openai.js.map +1 -0
- package/dist/embeddings/types.d.ts +39 -0
- package/dist/embeddings/types.d.ts.map +1 -0
- package/dist/embeddings/types.js +12 -0
- package/dist/embeddings/types.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/knowledge/distill.d.ts +27 -0
- package/dist/knowledge/distill.d.ts.map +1 -0
- package/dist/knowledge/distill.js +407 -0
- package/dist/knowledge/distill.js.map +1 -0
- package/dist/knowledge/git.d.ts +30 -0
- package/dist/knowledge/git.d.ts.map +1 -0
- package/dist/knowledge/git.js +228 -0
- package/dist/knowledge/git.js.map +1 -0
- package/dist/knowledge/search.d.ts +20 -0
- package/dist/knowledge/search.d.ts.map +1 -0
- package/dist/knowledge/search.js +72 -0
- package/dist/knowledge/search.js.map +1 -0
- package/dist/knowledge/store.d.ts +47 -0
- package/dist/knowledge/store.d.ts.map +1 -0
- package/dist/knowledge/store.js +173 -0
- package/dist/knowledge/store.js.map +1 -0
- package/dist/search/excerpt.d.ts +12 -0
- package/dist/search/excerpt.d.ts.map +1 -0
- package/dist/search/excerpt.js +28 -0
- package/dist/search/excerpt.js.map +1 -0
- package/dist/search/fuzzy.d.ts +15 -0
- package/dist/search/fuzzy.d.ts.map +1 -0
- package/dist/search/fuzzy.js +81 -0
- package/dist/search/fuzzy.js.map +1 -0
- package/dist/search/tfidf.d.ts +19 -0
- package/dist/search/tfidf.d.ts.map +1 -0
- package/dist/search/tfidf.js +200 -0
- package/dist/search/tfidf.js.map +1 -0
- package/dist/search/types.d.ts +19 -0
- package/dist/search/types.d.ts.map +1 -0
- package/dist/search/types.js +2 -0
- package/dist/search/types.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +518 -0
- package/dist/server.js.map +1 -0
- package/dist/sessions/indexer.d.ts +15 -0
- package/dist/sessions/indexer.d.ts.map +1 -0
- package/dist/sessions/indexer.js +182 -0
- package/dist/sessions/indexer.js.map +1 -0
- package/dist/sessions/parser.d.ts +58 -0
- package/dist/sessions/parser.d.ts.map +1 -0
- package/dist/sessions/parser.js +142 -0
- package/dist/sessions/parser.js.map +1 -0
- package/dist/sessions/scopes.d.ts +16 -0
- package/dist/sessions/scopes.d.ts.map +1 -0
- package/dist/sessions/scopes.js +153 -0
- package/dist/sessions/scopes.js.map +1 -0
- package/dist/sessions/search.d.ts +26 -0
- package/dist/sessions/search.d.ts.map +1 -0
- package/dist/sessions/search.js +256 -0
- package/dist/sessions/search.js.map +1 -0
- package/dist/sessions/summary.d.ts +28 -0
- package/dist/sessions/summary.d.ts.map +1 -0
- package/dist/sessions/summary.js +135 -0
- package/dist/sessions/summary.js.map +1 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +109 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/app.js +1029 -0
- package/dist/ui/index.html +373 -0
- package/dist/ui/styles.css +1508 -0
- package/dist/ui/ui/app.js +811 -0
- package/dist/ui/ui/index.html +300 -0
- package/dist/ui/ui/styles.css +1154 -0
- package/dist/validate.d.ts +21 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +86 -0
- package/dist/validate.js.map +1 -0
- package/dist/vectorstore/chunker.d.ts +48 -0
- package/dist/vectorstore/chunker.d.ts.map +1 -0
- package/dist/vectorstore/chunker.js +165 -0
- package/dist/vectorstore/chunker.js.map +1 -0
- package/dist/vectorstore/index.d.ts +5 -0
- package/dist/vectorstore/index.d.ts.map +1 -0
- package/dist/vectorstore/index.js +3 -0
- package/dist/vectorstore/index.js.map +1 -0
- package/dist/vectorstore/store.d.ts +139 -0
- package/dist/vectorstore/store.d.ts.map +1 -0
- package/dist/vectorstore/store.js +500 -0
- package/dist/vectorstore/store.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/docs/ARCHITECTURE.md +244 -0
- package/docs/DASHBOARD.md +133 -0
- package/docs/SETUP.md +178 -0
- package/package.json +92 -0
- 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":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAC1D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAO5B,MAAM,UAAU,GAAG,CAAC,UAAU,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;AAE7E;;GAEG;AACH,SAAS,OAAO,CAAC,IAAc,EAAE,GAAW,EAAE,OAAe;IAC3D,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE;YACvC,GAAG;YACH,KAAK,EAAE,MAAM;YACb,OAAO;SACR,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAChD,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,CAAC,GAAG,GAA4C,CAAC;QACvD,MAAM,GAAG,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAC/E,MAAM,IAAI,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,YAAY,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;IACnE,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,GAAW;IACvC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;QACrE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,IAAI,YAAY,EAAE,CAAC;IAC5D,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAE,GAAa,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IAClG,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,GAAW,EAAE,SAAkB;IAC3D,MAAM,OAAO,GAAG,SAAS,IAAI,uBAAuB,CAAC;IACrD,IAAI,CAAC;QACH,OAAO,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;QAEnC,0CAA0C;QAC1C,IAAI,CAAC;YACH,OAAO,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;YACrD,0CAA0C;QAC5C,CAAC;QAAC,MAAM,CAAC;YACP,8DAA8D;YAC9D,OAAO,CAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;QACjD,CAAC;QAED,OAAO,CAAC,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;QAC1C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,qBAAqB,EAAE,CAAC;IAC3D,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAE,GAAa,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IAClG,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,GAAW;IACvC,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;IAChC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACxB,CAAC;AAED,+EAA+E;AAE/E,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;CAwB1B,CAAC;AAEF,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqDvB,CAAC;AAEF;;;GAGG;AACH,SAAS,YAAY,CAAC,GAAW;IAC/B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC9B,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACxB,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IACtC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACxB,aAAa,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,CAAC,CAAC;IAClD,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IAC1C,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,aAAa,CAAC,SAAS,EAAE,kBAAkB,EAAE,OAAO,CAAC,CAAC;IACxD,CAAC;AACH,CAAC;AAED,+EAA+E;AAE/E;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,MAAe;IACrD,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;IAEhD,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,qBAAqB,EAAE,CAAC;IAC3D,CAAC;IAED,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,MAAM,EAAE,CAAC;QAC/B,IAAI,CAAC;YACH,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;YACvD,YAAY,CAAC,GAAG,CAAC,CAAC;YAClB,IAAI,CAAC;gBACH,OAAO,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;gBACnC,OAAO,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;YACvD,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,CAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,oBAAoB,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;gBAC5D,OAAO,CAAC,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;YAC5C,CAAC;YACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,MAAM,SAAS,GAAG,EAAE,EAAE,CAAC;QACpE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACvF,CAAC;IACH,CAAC;IAED,YAAY,CAAC,GAAG,CAAC,CAAC;IAElB,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,CAAC;YACH,OAAO,CAAC,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;YAC9B,OAAO,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;YACzD,OAAO,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;YACnC,IAAI,CAAC;gBACH,OAAO,CAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,qBAAqB,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;YAC/D,CAAC;YAAC,MAAM,CAAC;gBACP,gCAAgC;YAClC,CAAC;YACD,IAAI,CAAC;gBACH,OAAO,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;YACpE,CAAC;YAAC,MAAM,CAAC;gBACP,6DAA6D;YAC/D,CAAC;YACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,gCAAgC,MAAM,EAAE,EAAE,CAAC;QAC9E,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACvF,CAAC;IACH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,6DAA6D,EAAE,CAAC;AACnG,CAAC"}
|
|
@@ -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"}
|