@ksm0709/context 0.0.35 → 0.0.36

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/cli/index.js CHANGED
@@ -27,7 +27,7 @@ function resolveContextDir(projectDir) {
27
27
  // package.json
28
28
  var package_default = {
29
29
  name: "@ksm0709/context",
30
- version: "0.0.35",
30
+ version: "0.0.36",
31
31
  author: {
32
32
  name: "TaehoKang",
33
33
  email: "ksm07091@gmail.com"
@@ -325,6 +325,8 @@ var DEFAULT_DAILY_NOTE_GUIDE = `# \uB370\uC77C\uB9AC \uB178\uD2B8 \uAE30\uB85D \
325
325
  var DEFAULT_NOTE_GUIDE = `# \uC9C0\uC2DD \uB178\uD2B8 \uC791\uC131 \uBC0F \uAD00\uB9AC \uAC00\uC774\uB4DC
326
326
 
327
327
  - [ ] **\uB178\uD2B8 \uC0DD\uC131**: \`context_mcp_create_knowledge_note\` \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD558\uC5EC \uC0DD\uC131\uD558\uC138\uC694.
328
+ - [ ] \uD15C\uD50C\uB9BF \uBAA8\uB4DC\uC5D0\uC11C\uB294 \uBA3C\uC800 \`.context/templates/<template>.md\`\uB97C \uC77D\uACE0, **\uC644\uC131\uB41C markdown \uC804\uCCB4**\uB97C \`content\`\uB85C \uC804\uB2EC\uD558\uC138\uC694.
329
+ - [ ] \uD15C\uD50C\uB9BF \uBAA8\uB4DC\uC5D0\uC11C\uB294 \`tags\`/\`linked_notes\`\uB97C \uB530\uB85C \uB118\uAE30\uC9C0 \uB9C8\uC138\uC694. \uAD00\uB828 \uB178\uD2B8\uC640 \uBA54\uD0C0\uB370\uC774\uD130\uB294 markdown \uBCF8\uBB38\uC5D0 \uC9C1\uC811 \uC791\uC131\uD574\uC57C \uD569\uB2C8\uB2E4.
328
330
  - [ ] \uC81C\uD154\uCE74\uC2A4\uD150(Zettelkasten) 3\uB300 \uC6D0\uCE59 \uC900\uC218:
329
331
  - [ ] \uC6D0\uC790\uC131: \uD55C \uB178\uD2B8\uB2F9 \uD55C \uC8FC\uC81C
330
332
  - [ ] \uC5F0\uACB0: \uACE0\uB9BD\uB41C \uB178\uD2B8 \uBC29\uC9C0
package/dist/index.js CHANGED
@@ -25,7 +25,7 @@ import { join as join2 } from "path";
25
25
  // package.json
26
26
  var package_default = {
27
27
  name: "@ksm0709/context",
28
- version: "0.0.35",
28
+ version: "0.0.36",
29
29
  author: {
30
30
  name: "TaehoKang",
31
31
  email: "ksm07091@gmail.com"
@@ -323,6 +323,8 @@ var DEFAULT_DAILY_NOTE_GUIDE = `# \uB370\uC77C\uB9AC \uB178\uD2B8 \uAE30\uB85D \
323
323
  var DEFAULT_NOTE_GUIDE = `# \uC9C0\uC2DD \uB178\uD2B8 \uC791\uC131 \uBC0F \uAD00\uB9AC \uAC00\uC774\uB4DC
324
324
 
325
325
  - [ ] **\uB178\uD2B8 \uC0DD\uC131**: \`context_mcp_create_knowledge_note\` \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD558\uC5EC \uC0DD\uC131\uD558\uC138\uC694.
326
+ - [ ] \uD15C\uD50C\uB9BF \uBAA8\uB4DC\uC5D0\uC11C\uB294 \uBA3C\uC800 \`.context/templates/<template>.md\`\uB97C \uC77D\uACE0, **\uC644\uC131\uB41C markdown \uC804\uCCB4**\uB97C \`content\`\uB85C \uC804\uB2EC\uD558\uC138\uC694.
327
+ - [ ] \uD15C\uD50C\uB9BF \uBAA8\uB4DC\uC5D0\uC11C\uB294 \`tags\`/\`linked_notes\`\uB97C \uB530\uB85C \uB118\uAE30\uC9C0 \uB9C8\uC138\uC694. \uAD00\uB828 \uB178\uD2B8\uC640 \uBA54\uD0C0\uB370\uC774\uD130\uB294 markdown \uBCF8\uBB38\uC5D0 \uC9C1\uC811 \uC791\uC131\uD574\uC57C \uD569\uB2C8\uB2E4.
326
328
  - [ ] \uC81C\uD154\uCE74\uC2A4\uD150(Zettelkasten) 3\uB300 \uC6D0\uCE59 \uC900\uC218:
327
329
  - [ ] \uC6D0\uC790\uC131: \uD55C \uB178\uD2B8\uB2F9 \uD55C \uC8FC\uC81C
328
330
  - [ ] \uC5F0\uACB0: \uACE0\uB9BD\uB41C \uB178\uD2B8 \uBC29\uC9C0
package/dist/mcp.js CHANGED
@@ -33093,6 +33093,100 @@ function stripExtension(file2) {
33093
33093
  return file2.endsWith(".md") ? file2.slice(0, -3) : file2;
33094
33094
  }
33095
33095
 
33096
+ // src/lib/knowledge-note-template-validation.ts
33097
+ var HEADING_REGEX2 = /^(#{1,3})\s+(.+)$/;
33098
+ var ADDITIONAL_FORBIDDEN_SNIPPETS = ["[\uC81C\uBAA9]", "[\uAC04\uB2E8\uD55C \uC124\uBA85]", "TODO"];
33099
+ var RELATED_NOTES_TITLES = ["\uAD00\uB828 \uB178\uD2B8", "Related Notes"];
33100
+ function escapeRegExp(value) {
33101
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
33102
+ }
33103
+ function parseMarkdownHeadings(content) {
33104
+ const lines = content.split(`
33105
+ `);
33106
+ return lines.flatMap((line, index) => {
33107
+ const match = line.match(HEADING_REGEX2);
33108
+ if (!match) {
33109
+ return [];
33110
+ }
33111
+ return [
33112
+ {
33113
+ level: match[1].length,
33114
+ line: index,
33115
+ raw: line.trim(),
33116
+ text: match[2].trim()
33117
+ }
33118
+ ];
33119
+ });
33120
+ }
33121
+ function buildHeadingPattern(templateText) {
33122
+ const escaped = escapeRegExp(templateText).replace(/\\\[[^\]]+\\\]/g, "(.+)");
33123
+ return new RegExp(`^${escaped}$`);
33124
+ }
33125
+ function headingMatches(template, candidate) {
33126
+ if (template.level !== candidate.level) {
33127
+ return false;
33128
+ }
33129
+ return buildHeadingPattern(template.text).test(candidate.text);
33130
+ }
33131
+ function collectForbiddenSnippets(templateContent) {
33132
+ const snippets = new Set(ADDITIONAL_FORBIDDEN_SNIPPETS);
33133
+ for (const line of templateContent.split(`
33134
+ `)) {
33135
+ const trimmed = line.trim();
33136
+ if (!trimmed) {
33137
+ continue;
33138
+ }
33139
+ const headingMatch = trimmed.match(HEADING_REGEX2);
33140
+ if (headingMatch) {
33141
+ if (trimmed.includes("[") || trimmed.includes("...") || trimmed.includes("TODO")) {
33142
+ snippets.add(trimmed);
33143
+ }
33144
+ continue;
33145
+ }
33146
+ snippets.add(trimmed);
33147
+ }
33148
+ return [...snippets];
33149
+ }
33150
+ function isRelatedNotesHeading(heading) {
33151
+ return RELATED_NOTES_TITLES.includes(heading.text);
33152
+ }
33153
+ function validateTemplatedKnowledgeNoteContent(templateContent, content) {
33154
+ const templateHeadings = parseMarkdownHeadings(templateContent);
33155
+ const contentHeadings = parseMarkdownHeadings(content);
33156
+ const contentLines = content.split(`
33157
+ `);
33158
+ const errors3 = [];
33159
+ const matchedHeadings = [];
33160
+ let searchStart = 0;
33161
+ for (const templateHeading of templateHeadings) {
33162
+ const matchIndex = contentHeadings.findIndex((candidate, index) => index >= searchStart && headingMatches(templateHeading, candidate));
33163
+ if (matchIndex === -1) {
33164
+ errors3.push(`Missing required heading: ${templateHeading.raw}`);
33165
+ continue;
33166
+ }
33167
+ matchedHeadings.push(contentHeadings[matchIndex]);
33168
+ searchStart = matchIndex + 1;
33169
+ }
33170
+ for (const forbiddenSnippet of collectForbiddenSnippets(templateContent)) {
33171
+ if (content.includes(forbiddenSnippet)) {
33172
+ errors3.push(`Template placeholder was not replaced: ${forbiddenSnippet}`);
33173
+ }
33174
+ }
33175
+ matchedHeadings.forEach((heading, index) => {
33176
+ const nextHeadingLine = matchedHeadings[index + 1]?.line ?? contentLines.length;
33177
+ const sectionBody = contentLines.slice(heading.line + 1, nextHeadingLine).join(`
33178
+ `).trim();
33179
+ if (heading.level > 1 && !sectionBody) {
33180
+ errors3.push(`Section is empty: ${heading.raw}`);
33181
+ return;
33182
+ }
33183
+ if (isRelatedNotesHeading(heading) && !/\[\[[^[\]]+\]\]/.test(sectionBody)) {
33184
+ errors3.push(`Related notes section must include at least one wikilink: ${heading.raw}`);
33185
+ }
33186
+ });
33187
+ return { errors: errors3 };
33188
+ }
33189
+
33096
33190
  // src/lib/mcp-server.ts
33097
33191
  function startMcpServer() {
33098
33192
  const server = new McpServer({
@@ -33283,13 +33377,13 @@ function startMcpServer() {
33283
33377
  }
33284
33378
  });
33285
33379
  server.registerTool("create_knowledge_note", {
33286
- description: "Create a new Zettelkasten knowledge note with frontmatter and wikilinks. You can optionally use a template by providing the `template` parameter. Available templates: adr (Architecture Decision Records), pattern (Design patterns), bug (Bug reports and analysis), gotcha (Pitfalls and gotchas), decision (General decisions), context (General context and background), runbook (Procedures and runbooks), insight (Insights and learnings).",
33380
+ description: "Create a new Zettelkasten knowledge note with frontmatter and wikilinks. When you provide `template`, first read `.context/templates/<template>.md` and pass fully completed markdown in `content`. Available templates: adr (Architecture Decision Records), pattern (Design patterns), bug (Bug reports and analysis), gotcha (Pitfalls and gotchas), decision (General decisions), context (General context and background), runbook (Procedures and runbooks), insight (Insights and learnings).",
33287
33381
  inputSchema: {
33288
33382
  title: exports_external.string().describe("The title of the note"),
33289
- content: exports_external.string().describe("The main content of the note"),
33290
- tags: exports_external.array(exports_external.string()).optional().describe("Optional tags for the note"),
33291
- linked_notes: exports_external.array(exports_external.string()).optional().describe("Optional list of related note titles to link to"),
33292
- template: exports_external.enum(["adr", "pattern", "bug", "gotcha", "decision", "context", "runbook", "insight"]).optional().describe("Optional template to use for the note")
33383
+ content: exports_external.string().describe("The main content of the note. When `template` is set, this must be the complete markdown document that already follows the template."),
33384
+ tags: exports_external.array(exports_external.string()).optional().describe("Optional tags for the note. Not supported when `template` is set."),
33385
+ linked_notes: exports_external.array(exports_external.string()).optional().describe("Optional list of related note titles to link to. Not supported when `template` is set; include related notes directly in the markdown content instead."),
33386
+ template: exports_external.enum(["adr", "pattern", "bug", "gotcha", "decision", "context", "runbook", "insight"]).optional().describe("Optional template to validate against. Read the template file first and pass fully completed markdown in `content`.")
33293
33387
  }
33294
33388
  }, async ({ title, content, tags, linked_notes, template }) => {
33295
33389
  try {
@@ -33300,18 +33394,48 @@ function startMcpServer() {
33300
33394
  const date6 = new Date().toISOString().split("T")[0];
33301
33395
  let fileContent = "";
33302
33396
  if (template) {
33397
+ if (tags && tags.length > 0) {
33398
+ return {
33399
+ content: [
33400
+ {
33401
+ type: "text",
33402
+ text: "Error creating knowledge note: `tags` is not supported in template mode. Read the template and include any frontmatter or metadata directly in the markdown content."
33403
+ }
33404
+ ],
33405
+ isError: true
33406
+ };
33407
+ }
33408
+ if (linked_notes && linked_notes.length > 0) {
33409
+ return {
33410
+ content: [
33411
+ {
33412
+ type: "text",
33413
+ text: "Error creating knowledge note: `linked_notes` is not supported in template mode. Read the template and include related notes directly in the markdown content."
33414
+ }
33415
+ ],
33416
+ isError: true
33417
+ };
33418
+ }
33303
33419
  const templatePath = path2.resolve(process.cwd(), `.context/templates/${template}.md`);
33304
- try {
33305
- fileContent = await fs2.readFile(templatePath, "utf-8");
33306
- fileContent = fileContent.replace(/\[\uC81C\uBAA9\]/g, title);
33307
- fileContent += `
33308
-
33309
- ${content}`;
33310
- } catch (err) {
33311
- fileContent = `Error loading template: ${err instanceof Error ? err.message : String(err)}
33312
-
33313
- ${content}`;
33420
+ const templateContent = await fs2.readFile(templatePath, "utf-8");
33421
+ const validation = validateTemplatedKnowledgeNoteContent(templateContent, content);
33422
+ if (validation.errors.length > 0) {
33423
+ return {
33424
+ content: [
33425
+ {
33426
+ type: "text",
33427
+ text: `Error creating knowledge note: template content is invalid.
33428
+ - ${validation.errors.join(`
33429
+ - `)}
33430
+ Read the template and provide the fully completed markdown document in \`content\`.`
33431
+ }
33432
+ ],
33433
+ isError: true
33434
+ };
33314
33435
  }
33436
+ fileContent = content.endsWith(`
33437
+ `) ? content : `${content}
33438
+ `;
33315
33439
  } else {
33316
33440
  fileContent = `---
33317
33441
  `;
@@ -20,7 +20,7 @@ function resolveContextDir(projectDir) {
20
20
  // package.json
21
21
  var package_default = {
22
22
  name: "@ksm0709/context",
23
- version: "0.0.35",
23
+ version: "0.0.36",
24
24
  author: {
25
25
  name: "TaehoKang",
26
26
  email: "ksm07091@gmail.com"
@@ -318,6 +318,8 @@ var DEFAULT_DAILY_NOTE_GUIDE = `# \uB370\uC77C\uB9AC \uB178\uD2B8 \uAE30\uB85D \
318
318
  var DEFAULT_NOTE_GUIDE = `# \uC9C0\uC2DD \uB178\uD2B8 \uC791\uC131 \uBC0F \uAD00\uB9AC \uAC00\uC774\uB4DC
319
319
 
320
320
  - [ ] **\uB178\uD2B8 \uC0DD\uC131**: \`context_mcp_create_knowledge_note\` \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD558\uC5EC \uC0DD\uC131\uD558\uC138\uC694.
321
+ - [ ] \uD15C\uD50C\uB9BF \uBAA8\uB4DC\uC5D0\uC11C\uB294 \uBA3C\uC800 \`.context/templates/<template>.md\`\uB97C \uC77D\uACE0, **\uC644\uC131\uB41C markdown \uC804\uCCB4**\uB97C \`content\`\uB85C \uC804\uB2EC\uD558\uC138\uC694.
322
+ - [ ] \uD15C\uD50C\uB9BF \uBAA8\uB4DC\uC5D0\uC11C\uB294 \`tags\`/\`linked_notes\`\uB97C \uB530\uB85C \uB118\uAE30\uC9C0 \uB9C8\uC138\uC694. \uAD00\uB828 \uB178\uD2B8\uC640 \uBA54\uD0C0\uB370\uC774\uD130\uB294 markdown \uBCF8\uBB38\uC5D0 \uC9C1\uC811 \uC791\uC131\uD574\uC57C \uD569\uB2C8\uB2E4.
321
323
  - [ ] \uC81C\uD154\uCE74\uC2A4\uD150(Zettelkasten) 3\uB300 \uC6D0\uCE59 \uC900\uC218:
322
324
  - [ ] \uC6D0\uC790\uC131: \uD55C \uB178\uD2B8\uB2F9 \uD55C \uC8FC\uC81C
323
325
  - [ ] \uC5F0\uACB0: \uACE0\uB9BD\uB41C \uB178\uD2B8 \uBC29\uC9C0
@@ -105,7 +105,7 @@ import { join as join3 } from "node:path";
105
105
  // package.json
106
106
  var package_default = {
107
107
  name: "@ksm0709/context",
108
- version: "0.0.35",
108
+ version: "0.0.36",
109
109
  author: {
110
110
  name: "TaehoKang",
111
111
  email: "ksm07091@gmail.com"
@@ -403,6 +403,8 @@ var DEFAULT_DAILY_NOTE_GUIDE = `# 데일리 노트 기록 가이드
403
403
  var DEFAULT_NOTE_GUIDE = `# 지식 노트 작성 및 관리 가이드
404
404
 
405
405
  - [ ] **노트 생성**: \`context_mcp_create_knowledge_note\` 도구를 사용하여 생성하세요.
406
+ - [ ] 템플릿 모드에서는 먼저 \`.context/templates/<template>.md\`를 읽고, **완성된 markdown 전체**를 \`content\`로 전달하세요.
407
+ - [ ] 템플릿 모드에서는 \`tags\`/\`linked_notes\`를 따로 넘기지 마세요. 관련 노트와 메타데이터는 markdown 본문에 직접 작성해야 합니다.
406
408
  - [ ] 제텔카스텐(Zettelkasten) 3대 원칙 준수:
407
409
  - [ ] 원자성: 한 노트당 한 주제
408
410
  - [ ] 연결: 고립된 노트 방지
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ksm0709/context",
3
- "version": "0.0.35",
3
+ "version": "0.0.36",
4
4
  "author": {
5
5
  "name": "TaehoKang",
6
6
  "email": "ksm07091@gmail.com"