@ksm0709/context 0.0.33 → 0.0.35

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/mcp.js CHANGED
@@ -32638,8 +32638,462 @@ class StdioServerTransport {
32638
32638
  }
32639
32639
 
32640
32640
  // src/lib/mcp-server.ts
32641
+ import * as fs2 from "fs/promises";
32642
+ import * as path2 from "path";
32643
+
32644
+ // src/lib/knowledge-search.ts
32641
32645
  import * as fs from "fs/promises";
32642
32646
  import * as path from "path";
32647
+ var SEARCH_DIRECTORIES = ["docs", ".context"];
32648
+ var FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
32649
+ var HEADING_REGEX = /^#\s+(.+)$/m;
32650
+ var WIKI_LINK_REGEX = /\[\[([^[\]]+?)\]\]/g;
32651
+ var SNIPPET_LENGTH = 160;
32652
+ var KNOWLEDGE_READ_MAX_LENGTH = 32 * 1024;
32653
+ var RELATED_NOTES_SECTION_MAX_LENGTH = 6 * 1024;
32654
+ var STOP_WORDS = new Set([
32655
+ "a",
32656
+ "an",
32657
+ "and",
32658
+ "are",
32659
+ "as",
32660
+ "at",
32661
+ "be",
32662
+ "but",
32663
+ "by",
32664
+ "for",
32665
+ "from",
32666
+ "how",
32667
+ "i",
32668
+ "in",
32669
+ "is",
32670
+ "it",
32671
+ "of",
32672
+ "on",
32673
+ "or",
32674
+ "that",
32675
+ "the",
32676
+ "this",
32677
+ "to",
32678
+ "what",
32679
+ "when",
32680
+ "where",
32681
+ "which",
32682
+ "with",
32683
+ "\uAC00",
32684
+ "\uB294",
32685
+ "\uB3C4",
32686
+ "\uB97C",
32687
+ "\uC5D0",
32688
+ "\uC640",
32689
+ "\uC744",
32690
+ "\uC758",
32691
+ "\uC774",
32692
+ "\uC880",
32693
+ "\uD560",
32694
+ "\uB54C"
32695
+ ]);
32696
+ function normalizeKnowledgePath(filePath) {
32697
+ const normalizedPath = path.normalize(filePath);
32698
+ if (normalizedPath.startsWith("..") || path.isAbsolute(normalizedPath)) {
32699
+ throw new Error("Invalid path: Directory traversal is not allowed");
32700
+ }
32701
+ const relativePath = toPosixPath(normalizedPath);
32702
+ if (!SEARCH_DIRECTORIES.some((dir) => relativePath.startsWith(`${dir}/`))) {
32703
+ throw new Error("Invalid path: Only files in docs/ or .context/ are allowed");
32704
+ }
32705
+ return relativePath;
32706
+ }
32707
+ async function loadKnowledgeNotes(rootDir) {
32708
+ const notes = [];
32709
+ for (const dir of SEARCH_DIRECTORIES) {
32710
+ const fullDirPath = path.resolve(rootDir, dir);
32711
+ try {
32712
+ const files = await fs.readdir(fullDirPath, { recursive: true });
32713
+ for (const file2 of files) {
32714
+ if (typeof file2 !== "string" || !file2.endsWith(".md")) {
32715
+ continue;
32716
+ }
32717
+ const fullPath = path.join(fullDirPath, file2);
32718
+ const content = await fs.readFile(fullPath, "utf-8");
32719
+ const relativePath = toPosixPath(path.relative(rootDir, fullPath));
32720
+ notes.push(parseKnowledgeNote(relativePath, content));
32721
+ }
32722
+ } catch (error45) {
32723
+ if (error45.code !== "ENOENT") {
32724
+ throw error45;
32725
+ }
32726
+ }
32727
+ }
32728
+ return notes.sort((left, right) => left.file.localeCompare(right.file));
32729
+ }
32730
+ function buildSearchKnowledgeResponse(results) {
32731
+ if (results.length === 0) {
32732
+ return "No matches found.";
32733
+ }
32734
+ return [
32735
+ ...results.map((result, index) => [
32736
+ `Result ${index + 1}`,
32737
+ `Path: ${result.file}`,
32738
+ `Title: ${result.title}`,
32739
+ `Description: ${result.description || "(none)"}`,
32740
+ `Tags: ${result.tags.length > 0 ? result.tags.join(", ") : "(none)"}`,
32741
+ `Score: ${result.score}`,
32742
+ `Match reasons: ${result.matchReasons.length > 0 ? result.matchReasons.join(", ") : "(general overlap)"}`,
32743
+ `Snippet: ${result.snippet}`
32744
+ ].join(`
32745
+ `)),
32746
+ "Open a relevant note with read_knowledge to inspect the full content and linked-note metadata."
32747
+ ].join(`
32748
+
32749
+ `);
32750
+ }
32751
+ function formatRelatedNotesSection(relatedNotes) {
32752
+ if (relatedNotes.resolved.length === 0 && relatedNotes.unresolved.length === 0) {
32753
+ return "";
32754
+ }
32755
+ const lines = ["## Related Notes", ""];
32756
+ if (relatedNotes.resolved.length > 0) {
32757
+ for (const note of relatedNotes.resolved) {
32758
+ lines.push(`- Title: ${note.title}`);
32759
+ lines.push(` Path: ${note.file}`);
32760
+ lines.push(` Description: ${note.description || "(none)"}`);
32761
+ lines.push(` Tags: ${note.tags.length > 0 ? note.tags.join(", ") : "(none)"}`);
32762
+ }
32763
+ lines.push("");
32764
+ }
32765
+ if (relatedNotes.unresolved.length > 0) {
32766
+ lines.push(`Unresolved links: ${relatedNotes.unresolved.join(", ")}`);
32767
+ lines.push("");
32768
+ }
32769
+ lines.push("If one of these related notes looks relevant, open it with `read_knowledge` to continue exploring.");
32770
+ return lines.join(`
32771
+ `).trim();
32772
+ }
32773
+ function buildReadKnowledgeResponse(content, relatedNotesSection) {
32774
+ if (!relatedNotesSection) {
32775
+ return content.length > KNOWLEDGE_READ_MAX_LENGTH ? `${content.substring(0, KNOWLEDGE_READ_MAX_LENGTH)}
32776
+
32777
+ ... (content truncated due to size limit)` : content;
32778
+ }
32779
+ const truncatedRelatedSection = relatedNotesSection.length > RELATED_NOTES_SECTION_MAX_LENGTH ? `${relatedNotesSection.slice(0, RELATED_NOTES_SECTION_MAX_LENGTH)}
32780
+ ... (related notes truncated)` : relatedNotesSection;
32781
+ const reservedLength = truncatedRelatedSection.length + 2;
32782
+ const mainBudget = Math.max(0, KNOWLEDGE_READ_MAX_LENGTH - reservedLength);
32783
+ const truncatedMainContent = content.length > mainBudget ? `${content.substring(0, mainBudget)}
32784
+
32785
+ ... (content truncated due to size limit)` : content;
32786
+ return `${truncatedMainContent}
32787
+
32788
+ ${truncatedRelatedSection}`;
32789
+ }
32790
+ function parseKnowledgeNote(file2, content) {
32791
+ const { body, metadata } = parseFrontmatter(content);
32792
+ const title = resolveTitle(file2, body, metadata);
32793
+ const description = resolveDescription(body, metadata);
32794
+ const tags = resolveTags(metadata);
32795
+ return {
32796
+ body,
32797
+ content,
32798
+ description,
32799
+ file: file2,
32800
+ links: extractWikiLinks(content),
32801
+ pathStem: normalizeSearchText(stripExtension(file2)),
32802
+ tags,
32803
+ title
32804
+ };
32805
+ }
32806
+ function searchKnowledgeNotes(notes, query, limit) {
32807
+ const normalizedQuery = normalizeSearchText(query);
32808
+ const queryTokens = tokenizeQuery(query);
32809
+ if (normalizedQuery.length === 0 && queryTokens.length === 0) {
32810
+ return [];
32811
+ }
32812
+ return notes.map((note) => scoreKnowledgeNote(note, normalizedQuery, queryTokens)).filter((match) => match.score > 0).sort((left, right) => {
32813
+ if (right.score !== left.score) {
32814
+ return right.score - left.score;
32815
+ }
32816
+ return left.file.localeCompare(right.file);
32817
+ }).slice(0, limit);
32818
+ }
32819
+ function resolveRelatedKnowledgeLinks(notes, currentFile, links) {
32820
+ const lookup = new Map;
32821
+ for (const note of notes) {
32822
+ const keys = [normalizeSearchText(note.title), note.pathStem];
32823
+ for (const key of keys) {
32824
+ if (!key || lookup.has(key)) {
32825
+ continue;
32826
+ }
32827
+ lookup.set(key, note);
32828
+ }
32829
+ }
32830
+ const resolved = [];
32831
+ const unresolved = [];
32832
+ const seenFiles = new Set;
32833
+ for (const rawLink of links) {
32834
+ const normalizedLink = normalizeSearchText(rawLink);
32835
+ const note = lookup.get(normalizedLink);
32836
+ if (!note || note.file === currentFile || seenFiles.has(note.file)) {
32837
+ if (!note) {
32838
+ unresolved.push(rawLink);
32839
+ }
32840
+ continue;
32841
+ }
32842
+ resolved.push(note);
32843
+ seenFiles.add(note.file);
32844
+ }
32845
+ return { resolved, unresolved };
32846
+ }
32847
+ function scoreKnowledgeNote(note, normalizedQuery, queryTokens) {
32848
+ const matchReasons = [];
32849
+ let score = 0;
32850
+ score += scoreTextField({
32851
+ exactPhraseWeight: 80,
32852
+ label: "title",
32853
+ matchReasons,
32854
+ queryTokens,
32855
+ text: note.title,
32856
+ tokenWeight: 24,
32857
+ normalizedQuery
32858
+ });
32859
+ score += scoreTags(note.tags, queryTokens, matchReasons);
32860
+ score += scoreTextField({
32861
+ exactPhraseWeight: 40,
32862
+ label: "description",
32863
+ matchReasons,
32864
+ queryTokens,
32865
+ text: note.description,
32866
+ tokenWeight: 14,
32867
+ normalizedQuery
32868
+ });
32869
+ score += scoreTextField({
32870
+ exactPhraseWeight: 20,
32871
+ label: "body",
32872
+ matchReasons,
32873
+ queryTokens,
32874
+ text: note.body,
32875
+ tokenWeight: 6,
32876
+ normalizedQuery
32877
+ });
32878
+ score += scoreTextField({
32879
+ exactPhraseWeight: 12,
32880
+ label: "path",
32881
+ matchReasons,
32882
+ queryTokens,
32883
+ text: note.file,
32884
+ tokenWeight: 4,
32885
+ normalizedQuery
32886
+ });
32887
+ return {
32888
+ description: note.description,
32889
+ file: note.file,
32890
+ matchReasons,
32891
+ score,
32892
+ snippet: buildSnippet(note, queryTokens),
32893
+ tags: note.tags,
32894
+ title: note.title
32895
+ };
32896
+ }
32897
+ function scoreTextField(params) {
32898
+ const normalizedText = normalizeSearchText(params.text);
32899
+ if (!normalizedText) {
32900
+ return 0;
32901
+ }
32902
+ const tokenSet = new Set(tokenizeQuery(params.text));
32903
+ const matchedTokenCount = params.queryTokens.filter((token) => tokenSet.has(token)).length;
32904
+ let score = matchedTokenCount * params.tokenWeight;
32905
+ if (matchedTokenCount > 0) {
32906
+ params.matchReasons.push(`${params.label}:${matchedTokenCount}`);
32907
+ }
32908
+ if (params.normalizedQuery && normalizedText.includes(params.normalizedQuery)) {
32909
+ score += params.exactPhraseWeight;
32910
+ params.matchReasons.push(`${params.label}:phrase`);
32911
+ }
32912
+ return score;
32913
+ }
32914
+ function scoreTags(tags, queryTokens, matchReasons) {
32915
+ const normalizedTags = tags.map((tag) => normalizeSearchText(tag)).filter(Boolean);
32916
+ let score = 0;
32917
+ let matched = 0;
32918
+ for (const token of queryTokens) {
32919
+ const hasMatch = normalizedTags.some((tag) => tag === token || tag.includes(token));
32920
+ if (!hasMatch) {
32921
+ continue;
32922
+ }
32923
+ matched += 1;
32924
+ score += 20;
32925
+ }
32926
+ if (matched > 0) {
32927
+ matchReasons.push(`tags:${matched}`);
32928
+ }
32929
+ return score;
32930
+ }
32931
+ function buildSnippet(note, queryTokens) {
32932
+ const preferredSources = [note.description, note.body, note.title];
32933
+ for (const source of preferredSources) {
32934
+ const snippet = buildSnippetFromText(source, queryTokens);
32935
+ if (snippet) {
32936
+ return snippet;
32937
+ }
32938
+ }
32939
+ return truncateInlineText(note.title || note.file, SNIPPET_LENGTH);
32940
+ }
32941
+ function buildSnippetFromText(text, queryTokens) {
32942
+ const inlineText = text.replace(/\s+/g, " ").trim();
32943
+ if (!inlineText) {
32944
+ return "";
32945
+ }
32946
+ const lowerText = inlineText.toLowerCase();
32947
+ let firstIndex = Number.POSITIVE_INFINITY;
32948
+ for (const token of queryTokens) {
32949
+ const matchIndex = lowerText.indexOf(token.toLowerCase());
32950
+ if (matchIndex !== -1 && matchIndex < firstIndex) {
32951
+ firstIndex = matchIndex;
32952
+ }
32953
+ }
32954
+ if (!Number.isFinite(firstIndex)) {
32955
+ return truncateInlineText(inlineText, SNIPPET_LENGTH);
32956
+ }
32957
+ const start = Math.max(0, firstIndex - Math.floor(SNIPPET_LENGTH / 3));
32958
+ const end = Math.min(inlineText.length, start + SNIPPET_LENGTH);
32959
+ let snippet = inlineText.slice(start, end);
32960
+ if (start > 0) {
32961
+ snippet = `...${snippet}`;
32962
+ }
32963
+ if (end < inlineText.length) {
32964
+ snippet = `${snippet}...`;
32965
+ }
32966
+ return snippet;
32967
+ }
32968
+ function truncateInlineText(text, maxLength) {
32969
+ if (text.length <= maxLength) {
32970
+ return text;
32971
+ }
32972
+ return `${text.slice(0, maxLength - 3)}...`;
32973
+ }
32974
+ function parseFrontmatter(content) {
32975
+ const match = FRONTMATTER_REGEX.exec(content);
32976
+ if (!match) {
32977
+ return {
32978
+ body: content,
32979
+ metadata: {}
32980
+ };
32981
+ }
32982
+ return {
32983
+ body: content.slice(match[0].length),
32984
+ metadata: parseFrontmatterBlock(match[1])
32985
+ };
32986
+ }
32987
+ function parseFrontmatterBlock(block) {
32988
+ const metadata = {};
32989
+ const lines = block.split(/\r?\n/);
32990
+ let currentArrayKey = null;
32991
+ for (const rawLine of lines) {
32992
+ const line = rawLine.trimEnd();
32993
+ const trimmed = line.trim();
32994
+ if (!trimmed || trimmed.startsWith("#")) {
32995
+ continue;
32996
+ }
32997
+ if (currentArrayKey && /^\s*-\s+/.test(line)) {
32998
+ const arrayValue = normalizeScalarValue(trimmed.replace(/^-+\s*/, ""));
32999
+ const existing = metadata[currentArrayKey];
33000
+ if (Array.isArray(existing)) {
33001
+ existing.push(arrayValue);
33002
+ } else {
33003
+ metadata[currentArrayKey] = [arrayValue];
33004
+ }
33005
+ continue;
33006
+ }
33007
+ currentArrayKey = null;
33008
+ const separatorIndex = line.indexOf(":");
33009
+ if (separatorIndex === -1) {
33010
+ continue;
33011
+ }
33012
+ const key = line.slice(0, separatorIndex).trim().toLowerCase();
33013
+ const value = line.slice(separatorIndex + 1).trim();
33014
+ if (!value) {
33015
+ currentArrayKey = key;
33016
+ metadata[key] = [];
33017
+ continue;
33018
+ }
33019
+ if (value.startsWith("[") && value.endsWith("]")) {
33020
+ metadata[key] = value.slice(1, -1).split(",").map((item) => normalizeScalarValue(item)).filter(Boolean);
33021
+ continue;
33022
+ }
33023
+ metadata[key] = normalizeScalarValue(value);
33024
+ }
33025
+ return metadata;
33026
+ }
33027
+ function resolveTitle(file2, body, metadata) {
33028
+ const frontmatterTitle = getMetadataString(metadata, "title");
33029
+ if (frontmatterTitle) {
33030
+ return frontmatterTitle;
33031
+ }
33032
+ const headingMatch = HEADING_REGEX.exec(body);
33033
+ if (headingMatch) {
33034
+ return headingMatch[1].trim();
33035
+ }
33036
+ return humanizeSlug(path.basename(file2, ".md"));
33037
+ }
33038
+ function resolveDescription(body, metadata) {
33039
+ const frontmatterDescription = getMetadataString(metadata, "description") ?? getMetadataString(metadata, "summary");
33040
+ if (frontmatterDescription) {
33041
+ return frontmatterDescription;
33042
+ }
33043
+ const lines = body.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
33044
+ const paragraph = lines.find((line) => !line.startsWith("- ") && !line.startsWith("* "));
33045
+ return paragraph ? truncateInlineText(paragraph, 200) : "";
33046
+ }
33047
+ function resolveTags(metadata) {
33048
+ const tags = metadata.tags;
33049
+ if (Array.isArray(tags)) {
33050
+ return tags.map((tag) => tag.trim()).filter(Boolean);
33051
+ }
33052
+ if (typeof tags === "string") {
33053
+ return tags.split(",").map((tag) => tag.trim()).filter(Boolean);
33054
+ }
33055
+ return [];
33056
+ }
33057
+ function getMetadataString(metadata, key) {
33058
+ const value = metadata[key];
33059
+ if (typeof value === "string" && value.trim()) {
33060
+ return value.trim();
33061
+ }
33062
+ return null;
33063
+ }
33064
+ function normalizeScalarValue(value) {
33065
+ return value.replace(/^['"]|['"]$/g, "").trim();
33066
+ }
33067
+ function extractWikiLinks(content) {
33068
+ const links = new Set;
33069
+ for (const match of content.matchAll(WIKI_LINK_REGEX)) {
33070
+ const rawTarget = match[1].split("|")[0].split("#")[0].trim();
33071
+ if (rawTarget) {
33072
+ links.add(rawTarget);
33073
+ }
33074
+ }
33075
+ return [...links];
33076
+ }
33077
+ function tokenizeQuery(text) {
33078
+ const rawTokens = text.toLowerCase().split(/[^\p{L}\p{N}]+/u).map((token) => token.trim()).filter(Boolean);
33079
+ const filteredTokens = rawTokens.filter((token) => !STOP_WORDS.has(token));
33080
+ return [...new Set(filteredTokens.length > 0 ? filteredTokens : rawTokens)];
33081
+ }
33082
+ function normalizeSearchText(text) {
33083
+ return text.toLowerCase().replace(/\s+/g, " ").trim();
33084
+ }
33085
+ function toPosixPath(filePath) {
33086
+ return filePath.replace(/\\/g, "/");
33087
+ }
33088
+ function humanizeSlug(slug) {
33089
+ const words = slug.split(/[-_]+/).filter(Boolean).map((word) => word.length > 0 ? word[0].toUpperCase() + word.slice(1) : word);
33090
+ return words.join(" ") || slug;
33091
+ }
33092
+ function stripExtension(file2) {
33093
+ return file2.endsWith(".md") ? file2.slice(0, -3) : file2;
33094
+ }
33095
+
33096
+ // src/lib/mcp-server.ts
32643
33097
  function startMcpServer() {
32644
33098
  const server = new McpServer({
32645
33099
  name: "context-mcp-server",
@@ -32648,62 +33102,20 @@ function startMcpServer() {
32648
33102
  capabilities: {}
32649
33103
  });
32650
33104
  server.registerTool("search_knowledge", {
32651
- description: "Search .md files in docs/ and .context/ directories for a keyword or regex",
33105
+ description: "Search knowledge notes in docs/ and .context/ using weighted metadata/body matching and ranked results",
32652
33106
  inputSchema: {
32653
- query: exports_external.string().describe("The keyword or regex to search for"),
33107
+ query: exports_external.string().describe("The search query to match against titles, descriptions, tags, and note content"),
32654
33108
  limit: exports_external.number().optional().describe("Maximum number of results to return (default: 50)")
32655
33109
  }
32656
33110
  }, async ({ query, limit = 50 }) => {
32657
- const searchDirs = ["docs", ".context"];
32658
- const results = [];
32659
- const maxResults = limit;
32660
- const snippetLength = 100;
32661
33111
  try {
32662
- const regex = new RegExp(query, "i");
32663
- for (const dir of searchDirs) {
32664
- const fullDirPath = path.resolve(process.cwd(), dir);
32665
- try {
32666
- const files = await fs.readdir(fullDirPath, { recursive: true });
32667
- for (const file2 of files) {
32668
- if (typeof file2 === "string" && file2.endsWith(".md")) {
32669
- const filePath = path.join(fullDirPath, file2);
32670
- const content = await fs.readFile(filePath, "utf-8");
32671
- const match = regex.exec(content);
32672
- if (match) {
32673
- const start = Math.max(0, match.index - snippetLength / 2);
32674
- const end = Math.min(content.length, match.index + match[0].length + snippetLength / 2);
32675
- let snippet = content.substring(start, end).replace(/\n/g, " ");
32676
- if (start > 0)
32677
- snippet = "..." + snippet;
32678
- if (end < content.length)
32679
- snippet = snippet + "...";
32680
- results.push({
32681
- file: path.relative(process.cwd(), filePath),
32682
- snippet
32683
- });
32684
- if (results.length >= maxResults) {
32685
- break;
32686
- }
32687
- }
32688
- }
32689
- }
32690
- } catch (err) {
32691
- if (err.code !== "ENOENT") {
32692
- console.error(`Error reading directory ${dir}:`, err);
32693
- }
32694
- }
32695
- if (results.length >= maxResults) {
32696
- break;
32697
- }
32698
- }
33112
+ const notes = await loadKnowledgeNotes(process.cwd());
33113
+ const results = searchKnowledgeNotes(notes, query, limit);
32699
33114
  return {
32700
33115
  content: [
32701
33116
  {
32702
33117
  type: "text",
32703
- text: results.length > 0 ? results.map((r) => `File: ${r.file}
32704
- Snippet: ${r.snippet}`).join(`
32705
-
32706
- `) : "No matches found."
33118
+ text: buildSearchKnowledgeResponse(results)
32707
33119
  }
32708
33120
  ]
32709
33121
  };
@@ -32720,25 +33132,19 @@ Snippet: ${r.snippet}`).join(`
32720
33132
  }
32721
33133
  });
32722
33134
  server.registerTool("read_knowledge", {
32723
- description: "Read the content of a specific .md file in docs/ or .context/ directories",
33135
+ description: "Read a specific knowledge note and append linked-note metadata to help agents explore related notes",
32724
33136
  inputSchema: {
32725
33137
  path: exports_external.string().describe("The relative path to the file (e.g., docs/architecture.md)")
32726
33138
  }
32727
33139
  }, async ({ path: filePath }) => {
32728
33140
  try {
32729
- const normalizedPath = path.normalize(filePath);
32730
- if (normalizedPath.startsWith("..") || path.isAbsolute(normalizedPath)) {
32731
- throw new Error("Invalid path: Directory traversal is not allowed");
32732
- }
32733
- if (!normalizedPath.startsWith("docs/") && !normalizedPath.startsWith(".context/")) {
32734
- throw new Error("Invalid path: Only files in docs/ or .context/ are allowed");
32735
- }
32736
- const fullPath = path.resolve(process.cwd(), normalizedPath);
32737
- const content = await fs.readFile(fullPath, "utf-8");
32738
- const MAX_LENGTH = 32 * 1024;
32739
- const truncatedContent = content.length > MAX_LENGTH ? content.substring(0, MAX_LENGTH) + `
32740
-
32741
- ... (content truncated due to size limit)` : content;
33141
+ const normalizedPath = normalizeKnowledgePath(filePath);
33142
+ const fullPath = path2.resolve(process.cwd(), normalizedPath);
33143
+ const notes = await loadKnowledgeNotes(process.cwd());
33144
+ const note = notes.find((entry) => entry.file === normalizedPath);
33145
+ const content = note?.content ?? await fs2.readFile(fullPath, "utf-8");
33146
+ const relatedNotesSection = note ? formatRelatedNotesSection(resolveRelatedKnowledgeLinks(notes, note.file, note.links)) : "";
33147
+ const truncatedContent = buildReadKnowledgeResponse(content, relatedNotesSection);
32742
33148
  return {
32743
33149
  content: [
32744
33150
  {
@@ -32775,15 +33181,15 @@ Snippet: ${r.snippet}`).join(`
32775
33181
  const seconds = String(date6.getSeconds()).padStart(2, "0");
32776
33182
  const dateString = `${year}-${month}-${day}`;
32777
33183
  const timestamp = `[${year}-${month}-${day} ${hours}:${minutes}:${seconds}]`;
32778
- const dirPath = path.resolve(process.cwd(), ".context/memory/daily");
32779
- const filePath = path.join(dirPath, `${dateString}.md`);
32780
- await fs.mkdir(dirPath, { recursive: true });
33184
+ const dirPath = path2.resolve(process.cwd(), ".context/memory/daily");
33185
+ const filePath = path2.join(dirPath, `${dateString}.md`);
33186
+ await fs2.mkdir(dirPath, { recursive: true });
32781
33187
  let textToAppend = content;
32782
33188
  if (!content.startsWith(`[${year}-${month}-${day}`)) {
32783
33189
  textToAppend = `${timestamp} ${content}`;
32784
33190
  }
32785
33191
  try {
32786
- const existingContent = await fs.readFile(filePath, "utf-8");
33192
+ const existingContent = await fs2.readFile(filePath, "utf-8");
32787
33193
  if (existingContent.length > 0 && !existingContent.endsWith(`
32788
33194
  `)) {
32789
33195
  textToAppend = `
@@ -32795,7 +33201,7 @@ Snippet: ${r.snippet}`).join(`
32795
33201
  textToAppend += `
32796
33202
  `;
32797
33203
  }
32798
- await fs.appendFile(filePath, textToAppend, "utf-8");
33204
+ await fs2.appendFile(filePath, textToAppend, "utf-8");
32799
33205
  return {
32800
33206
  content: [
32801
33207
  {
@@ -32834,10 +33240,10 @@ Snippet: ${r.snippet}`).join(`
32834
33240
  const month = String(date6.getMonth() + 1).padStart(2, "0");
32835
33241
  const day = String(date6.getDate()).padStart(2, "0");
32836
33242
  const dateString = `${year}-${month}-${day}`;
32837
- const filePath = path.resolve(process.cwd(), ".context/memory/daily", `${dateString}.md`);
33243
+ const filePath = path2.resolve(process.cwd(), ".context/memory/daily", `${dateString}.md`);
32838
33244
  let fileContent;
32839
33245
  try {
32840
- fileContent = await fs.readFile(filePath, "utf-8");
33246
+ fileContent = await fs2.readFile(filePath, "utf-8");
32841
33247
  } catch (err) {
32842
33248
  if (err.code === "ENOENT") {
32843
33249
  return {
@@ -32888,15 +33294,15 @@ Snippet: ${r.snippet}`).join(`
32888
33294
  }, async ({ title, content, tags, linked_notes, template }) => {
32889
33295
  try {
32890
33296
  const filename = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "") + ".md";
32891
- const dirPath = path.resolve(process.cwd(), ".context/memory");
32892
- const filePath = path.join(dirPath, filename);
32893
- await fs.mkdir(dirPath, { recursive: true });
33297
+ const dirPath = path2.resolve(process.cwd(), ".context/memory");
33298
+ const filePath = path2.join(dirPath, filename);
33299
+ await fs2.mkdir(dirPath, { recursive: true });
32894
33300
  const date6 = new Date().toISOString().split("T")[0];
32895
33301
  let fileContent = "";
32896
33302
  if (template) {
32897
- const templatePath = path.resolve(process.cwd(), `.context/templates/${template}.md`);
33303
+ const templatePath = path2.resolve(process.cwd(), `.context/templates/${template}.md`);
32898
33304
  try {
32899
- fileContent = await fs.readFile(templatePath, "utf-8");
33305
+ fileContent = await fs2.readFile(templatePath, "utf-8");
32900
33306
  fileContent = fileContent.replace(/\[\uC81C\uBAA9\]/g, title);
32901
33307
  fileContent += `
32902
33308
 
@@ -32937,7 +33343,7 @@ ${tags.map((t) => ` - ${t}`).join(`
32937
33343
  `) + `
32938
33344
  `;
32939
33345
  }
32940
- await fs.writeFile(filePath, fileContent, "utf-8");
33346
+ await fs2.writeFile(filePath, fileContent, "utf-8");
32941
33347
  return {
32942
33348
  content: [
32943
33349
  {
@@ -32967,19 +33373,19 @@ ${tags.map((t) => ` - ${t}`).join(`
32967
33373
  }
32968
33374
  }, async ({ path: filePath, content, mode }) => {
32969
33375
  try {
32970
- const normalizedPath = path.normalize(filePath);
32971
- if (normalizedPath.startsWith("..") || path.isAbsolute(normalizedPath)) {
33376
+ const normalizedPath = path2.normalize(filePath);
33377
+ if (normalizedPath.startsWith("..") || path2.isAbsolute(normalizedPath)) {
32972
33378
  throw new Error("Invalid path: Directory traversal is not allowed");
32973
33379
  }
32974
33380
  if (!normalizedPath.startsWith("docs/") && !normalizedPath.startsWith(".context/")) {
32975
33381
  throw new Error("Invalid path: Only files in docs/ or .context/ are allowed");
32976
33382
  }
32977
- const fullPath = path.resolve(process.cwd(), normalizedPath);
32978
- await fs.mkdir(path.dirname(fullPath), { recursive: true });
33383
+ const fullPath = path2.resolve(process.cwd(), normalizedPath);
33384
+ await fs2.mkdir(path2.dirname(fullPath), { recursive: true });
32979
33385
  if (mode === "append") {
32980
33386
  let textToAppend = content;
32981
33387
  try {
32982
- const existingContent = await fs.readFile(fullPath, "utf-8");
33388
+ const existingContent = await fs2.readFile(fullPath, "utf-8");
32983
33389
  if (existingContent.length > 0 && !existingContent.endsWith(`
32984
33390
  `)) {
32985
33391
  textToAppend = `
@@ -32991,9 +33397,9 @@ ${tags.map((t) => ` - ${t}`).join(`
32991
33397
  textToAppend += `
32992
33398
  `;
32993
33399
  }
32994
- await fs.appendFile(fullPath, textToAppend, "utf-8");
33400
+ await fs2.appendFile(fullPath, textToAppend, "utf-8");
32995
33401
  } else {
32996
- await fs.writeFile(fullPath, content, "utf-8");
33402
+ await fs2.writeFile(fullPath, content, "utf-8");
32997
33403
  }
32998
33404
  return {
32999
33405
  content: [
@@ -33061,12 +33467,12 @@ ${tags.map((t) => ` - ${t}`).join(`
33061
33467
  };
33062
33468
  }
33063
33469
  try {
33064
- const dirPath = path.resolve(process.cwd(), ".context");
33065
- const filePath = path.join(dirPath, ".work-complete");
33066
- await fs.mkdir(dirPath, { recursive: true });
33470
+ const dirPath = path2.resolve(process.cwd(), ".context");
33471
+ const filePath = path2.join(dirPath, ".work-complete");
33472
+ await fs2.mkdir(dirPath, { recursive: true });
33067
33473
  const content = `timestamp=${Date.now()}
33068
33474
  `;
33069
- await fs.writeFile(filePath, content, "utf-8");
33475
+ await fs2.writeFile(filePath, content, "utf-8");
33070
33476
  return {
33071
33477
  content: [
33072
33478
  {