@ksm0709/context 0.0.22 → 0.0.23

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.
@@ -1,13 +1,10 @@
1
1
  // src/omx/index.ts
2
- import { existsSync as existsSync5, readFileSync as readFileSync6, unlinkSync } from "node:fs";
3
- import { isAbsolute, join as join5 } from "node:path";
2
+ import { existsSync as existsSync4, readFileSync as readFileSync4, unlinkSync } from "node:fs";
3
+ import { join as join4 } from "node:path";
4
4
 
5
5
  // src/constants.ts
6
6
  var DEFAULTS = {
7
7
  configPath: ".context/config.jsonc",
8
- promptDir: ".context/prompts",
9
- turnStartFile: "turn-start.md",
10
- turnEndFile: "turn-end.md",
11
8
  knowledgeSources: ["AGENTS.md"],
12
9
  templateDir: ".context/templates",
13
10
  indexFilename: "INDEX.md",
@@ -48,10 +45,6 @@ function resolveContextDir(projectDir) {
48
45
  // src/lib/config.ts
49
46
  function getDefaultConfig() {
50
47
  return {
51
- prompts: {
52
- turnStart: join2(DEFAULTS.promptDir, DEFAULTS.turnStartFile),
53
- turnEnd: join2(DEFAULTS.promptDir, DEFAULTS.turnEndFile)
54
- },
55
48
  knowledge: {
56
49
  dir: "docs",
57
50
  sources: [...DEFAULTS.knowledgeSources],
@@ -69,10 +62,6 @@ function getDefaultConfig() {
69
62
  function mergeWithDefaults(partial) {
70
63
  const defaults = getDefaultConfig();
71
64
  return {
72
- prompts: {
73
- turnStart: partial.prompts?.turnStart ?? defaults.prompts.turnStart,
74
- turnEnd: partial.prompts?.turnEnd ?? defaults.prompts.turnEnd
75
- },
76
65
  knowledge: {
77
66
  dir: partial.knowledge?.dir ?? defaults.knowledge.dir,
78
67
  sources: partial.knowledge?.sources ?? defaults.knowledge.sources,
@@ -100,232 +89,13 @@ function loadConfig(projectDir) {
100
89
  }
101
90
  }
102
91
 
103
- // src/lib/knowledge-index.ts
104
- import { readdirSync, readFileSync as readFileSync2, statSync, existsSync as existsSync2 } from "node:fs";
105
- import { join as join3, relative, extname } from "node:path";
106
- function extractSummary(filePath) {
107
- try {
108
- const content = readFileSync2(filePath, "utf-8");
109
- const firstNonEmpty = content.split(`
110
- `).find((line) => line.trim().length > 0);
111
- if (!firstNonEmpty)
112
- return "";
113
- return firstNonEmpty.trim().slice(0, LIMITS.maxSummaryLength);
114
- } catch {
115
- return "";
116
- }
117
- }
118
- function scanDir(dir, projectDir, depth, entries) {
119
- if (depth > LIMITS.maxScanDepth)
120
- return;
121
- if (entries.length >= LIMITS.maxIndexEntries)
122
- return;
123
- try {
124
- const items = readdirSync(dir);
125
- for (const item of items) {
126
- if (entries.length >= LIMITS.maxIndexEntries)
127
- break;
128
- const fullPath = join3(dir, item);
129
- try {
130
- const stat = statSync(fullPath);
131
- if (stat.isDirectory()) {
132
- scanDir(fullPath, projectDir, depth + 1, entries);
133
- } else if (stat.isFile() && extname(item) === ".md") {
134
- entries.push({
135
- filename: relative(projectDir, fullPath),
136
- summary: extractSummary(fullPath)
137
- });
138
- }
139
- } catch {}
140
- }
141
- } catch {}
142
- }
143
- function buildKnowledgeIndex(projectDir, sources) {
144
- const entries = [];
145
- for (const source of sources) {
146
- if (entries.length >= LIMITS.maxIndexEntries)
147
- break;
148
- const fullPath = join3(projectDir, source);
149
- if (!existsSync2(fullPath))
150
- continue;
151
- try {
152
- const stat = statSync(fullPath);
153
- if (stat.isFile() && extname(source) === ".md") {
154
- entries.push({
155
- filename: source,
156
- summary: extractSummary(fullPath)
157
- });
158
- } else if (stat.isDirectory()) {
159
- scanDir(fullPath, projectDir, 1, entries);
160
- }
161
- } catch {}
162
- }
163
- return entries;
164
- }
165
- function formatKnowledgeIndex(entries) {
166
- if (entries.length === 0)
167
- return "";
168
- const lines = ["## Available Knowledge", ""];
169
- for (const entry of entries) {
170
- lines.push(`- ${entry.filename}${entry.summary ? ` — ${entry.summary}` : ""}`);
171
- }
172
- return lines.join(`
173
- `);
174
- }
175
- function countMdFiles(dir, indexFilename) {
176
- try {
177
- const items = readdirSync(dir);
178
- return items.filter((item) => extname(item) === ".md" && item !== indexFilename && statSync(join3(dir, item)).isFile()).length;
179
- } catch {
180
- return 0;
181
- }
182
- }
183
- function scanDomainsRecursive(baseDir, projectDir, indexFilename, currentDepth, maxDepth, results) {
184
- if (currentDepth > maxDepth)
185
- return;
186
- try {
187
- const items = readdirSync(baseDir);
188
- for (const item of items) {
189
- const fullPath = join3(baseDir, item);
190
- try {
191
- if (!statSync(fullPath).isDirectory())
192
- continue;
193
- const indexPath = join3(fullPath, indexFilename);
194
- if (existsSync2(indexPath) && statSync(indexPath).isFile()) {
195
- const rawContent = readFileSync2(indexPath, "utf-8");
196
- const indexContent = rawContent.slice(0, LIMITS.maxIndexFileSize);
197
- results.push({
198
- domain: item,
199
- path: relative(projectDir, fullPath),
200
- indexContent,
201
- noteCount: countMdFiles(fullPath, indexFilename)
202
- });
203
- }
204
- scanDomainsRecursive(fullPath, projectDir, indexFilename, currentDepth + 1, maxDepth, results);
205
- } catch {}
206
- }
207
- } catch {}
208
- }
209
- function scanDomains(projectDir, knowledgeDir, indexFilename, maxDepth) {
210
- const baseDir = join3(projectDir, knowledgeDir);
211
- if (!existsSync2(baseDir))
212
- return [];
213
- const results = [];
214
- scanDomainsRecursive(baseDir, projectDir, indexFilename, 1, maxDepth, results);
215
- return results;
216
- }
217
- function detectKnowledgeMode(projectDir, knowledgeDir, indexFilename, configMode) {
218
- if (configMode !== "auto")
219
- return configMode;
220
- const domains = scanDomains(projectDir, knowledgeDir, indexFilename, 1);
221
- return domains.length > 0 ? "domain" : "flat";
222
- }
223
- function formatDomainIndex(index) {
224
- const hasDomains = index.domains.length > 0;
225
- const hasFiles = index.individualFiles.length > 0;
226
- if (!hasDomains && !hasFiles)
227
- return "";
228
- const lines = ["## Available Knowledge", ""];
229
- if (hasDomains) {
230
- lines.push("### Domains", "");
231
- for (const domain of index.domains) {
232
- lines.push(`#### ${domain.path}/ (${domain.noteCount} notes)`, "");
233
- lines.push(domain.indexContent, "");
234
- }
235
- }
236
- if (hasFiles) {
237
- if (hasDomains) {
238
- lines.push("### Individual Files", "");
239
- }
240
- for (const file of index.individualFiles) {
241
- lines.push(`- ${file.filename}${file.summary ? ` — ${file.summary}` : ""}`);
242
- }
243
- }
244
- return lines.join(`
245
- `);
246
- }
247
- function collectRootFiles(projectDir, knowledgeDir, indexFilename) {
248
- const baseDir = join3(projectDir, knowledgeDir);
249
- if (!existsSync2(baseDir))
250
- return [];
251
- const entries = [];
252
- try {
253
- const items = readdirSync(baseDir);
254
- for (const item of items) {
255
- const fullPath = join3(baseDir, item);
256
- try {
257
- const stat = statSync(fullPath);
258
- if (stat.isFile() && extname(item) === ".md" && item !== indexFilename) {
259
- entries.push({
260
- filename: relative(projectDir, fullPath),
261
- summary: extractSummary(fullPath)
262
- });
263
- }
264
- } catch {}
265
- }
266
- } catch {}
267
- return entries;
268
- }
269
- function buildKnowledgeIndexV2(projectDir, knowledgeConfig) {
270
- const dir = knowledgeConfig.dir ?? "docs";
271
- const indexFilename = knowledgeConfig.indexFilename ?? "INDEX.md";
272
- const maxDepth = knowledgeConfig.maxDomainDepth ?? 2;
273
- const configMode = knowledgeConfig.mode ?? "auto";
274
- const mode = detectKnowledgeMode(projectDir, dir, indexFilename, configMode);
275
- if (mode === "flat") {
276
- const allSources = [dir, ...knowledgeConfig.sources].filter(Boolean);
277
- const entries = buildKnowledgeIndex(projectDir, allSources);
278
- return { mode: "flat", domains: [], individualFiles: entries };
279
- }
280
- const domains = scanDomains(projectDir, dir, indexFilename, maxDepth);
281
- const rootFiles = collectRootFiles(projectDir, dir, indexFilename);
282
- const sourcesEntries = [];
283
- for (const source of knowledgeConfig.sources) {
284
- const fullPath = join3(projectDir, source);
285
- if (!existsSync2(fullPath))
286
- continue;
287
- try {
288
- const stat = statSync(fullPath);
289
- if (stat.isFile() && extname(source) === ".md") {
290
- sourcesEntries.push({
291
- filename: source,
292
- summary: extractSummary(fullPath)
293
- });
294
- }
295
- } catch {}
296
- }
297
- const individualFiles = [...rootFiles, ...sourcesEntries];
298
- return { mode: "domain", domains, individualFiles };
299
- }
300
-
301
- // src/lib/prompt-reader.ts
302
- import { readFileSync as readFileSync3 } from "node:fs";
303
- function readPromptFile(filePath) {
304
- try {
305
- const content = readFileSync3(filePath, "utf-8");
306
- if (content.length > LIMITS.maxPromptFileSize) {
307
- return content.slice(0, LIMITS.maxPromptFileSize);
308
- }
309
- return content;
310
- } catch {
311
- return "";
312
- }
313
- }
314
- function resolvePromptVariables(content, vars) {
315
- const normalized = (vars.knowledgeDir || "docs").replace(/\\/g, "/").replace(/\/+$/, "");
316
- let resolved = content.replaceAll("{{knowledgeDir}}", normalized);
317
- resolved = resolved.replaceAll("{{sessionId}}", vars.sessionId ?? "");
318
- resolved = resolved.replaceAll("{{turnId}}", vars.turnId ?? "");
319
- return resolved;
320
- }
321
-
322
92
  // src/lib/scaffold.ts
323
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "node:fs";
324
- import { join as join4 } from "node:path";
93
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
94
+ import { join as join3 } from "node:path";
325
95
  // package.json
326
96
  var package_default = {
327
97
  name: "@ksm0709/context",
328
- version: "0.0.22",
98
+ version: "0.0.23",
329
99
  author: {
330
100
  name: "TaehoKang",
331
101
  email: "ksm07091@gmail.com"
@@ -355,10 +125,10 @@ var package_default = {
355
125
  access: "public"
356
126
  },
357
127
  scripts: {
358
- build: "bun build ./src/index.ts --outdir dist --target bun && bun build ./src/cli/index.ts --outdir dist/cli --target bun && bun build ./src/omx/index.ts --outdir dist/omx --target node --format esm --external jsonc-parser && mv dist/omx/index.js dist/omx/index.mjs",
128
+ build: "bun build ./src/index.ts --outdir dist --target bun && bun build ./src/mcp.ts --outdir dist --target bun && bun build ./src/cli/index.ts --outdir dist/cli --target bun && bun build ./src/omx/index.ts --outdir dist/omx --target node --format esm --external jsonc-parser && mv dist/omx/index.js dist/omx/index.mjs",
359
129
  test: "vitest run",
360
130
  lint: "eslint src --ext .ts",
361
- prepublishOnly: "bun build ./src/index.ts --outdir dist --target bun && bun build ./src/cli/index.ts --outdir dist/cli --target bun && bun build ./src/omx/index.ts --outdir dist/omx --target node --format esm --external jsonc-parser && mv dist/omx/index.js dist/omx/index.mjs"
131
+ prepublishOnly: "bun build ./src/index.ts --outdir dist --target bun && bun build ./src/mcp.ts --outdir dist --target bun && bun build ./src/cli/index.ts --outdir dist/cli --target bun && bun build ./src/omx/index.ts --outdir dist/omx --target node --format esm --external jsonc-parser && mv dist/omx/index.js dist/omx/index.mjs"
362
132
  },
363
133
  files: [
364
134
  "dist"
@@ -367,7 +137,8 @@ var package_default = {
367
137
  "@opencode-ai/plugin": ">=1.0.0"
368
138
  },
369
139
  dependencies: {
370
- "@ksm0709/context": "^0.0.21",
140
+ "@ksm0709/context": "^0.0.22",
141
+ "@modelcontextprotocol/sdk": "^1.27.1",
371
142
  "jsonc-parser": "^3.0.0"
372
143
  },
373
144
  devDependencies: {
@@ -392,10 +163,6 @@ var PLUGIN_VERSION = package_default.version;
392
163
  var DEFAULT_CONFIG = `{
393
164
  // Context Plugin Configuration
394
165
  // See: https://github.com/ksm0709/context
395
- "prompts": {
396
- "turnStart": "prompts/turn-start.md",
397
- "turnEnd": "prompts/turn-end.md"
398
- },
399
166
  "knowledge": {
400
167
  "dir": ".context/memory",
401
168
  "sources": ["AGENTS.md"]
@@ -407,53 +174,6 @@ var DEFAULT_CONFIG = `{
407
174
  }
408
175
  }
409
176
  }`;
410
- var DEFAULT_TURN_START = `## Knowledge Context
411
-
412
- 이 프로젝트는 **제텔카스텐(Zettelkasten)** 방식으로 지식을 관리합니다.
413
- 세션 간 컨텍스트를 보존하여, 이전 세션의 결정/패턴/실수가 다음 세션에서 재활용됩니다.
414
-
415
- ### 제텔카스텐 핵심 원칙
416
-
417
- 1. **원자성** -- 하나의 노트 = 하나의 주제. 여러 주제를 섞지 마세요.
418
- 2. **연결** -- 모든 노트는 [[wikilink]]로 관련 노트에 연결. 고립된 노트는 발견되지 않습니다.
419
- 3. **자기 언어** -- 복사-붙여넣기가 아닌, 핵심을 이해하고 간결하게 서술하세요.
420
-
421
- ### 작업 전 필수
422
-
423
- - **데일리 노트 확인**: 가장 최근의 데일리 노트(\`{{knowledgeDir}}/daily/YYYY-MM-DD.md\`)를 읽고 이전 세션의 컨텍스트와 미해결 이슈를 파악하세요
424
- - **작업 의도 선언**: 작업 시작 전, 현재 세션의 목표와 작업 의도를 명확히 파악하고 선언하세요 (추후 작업 경로 검증 시 기준이 됩니다)
425
- - 메인 에이전트가 아래 **Available Knowledge** 목록에서 현재 작업과 관련된 문서를 **직접 먼저** 읽으세요
426
- - 도메인 폴더 구조가 있다면 INDEX.md의 요약을 참고하여 필요한 노트만 선택적으로 읽으세요
427
- - 문서 내 [[링크]]를 따라가며 관련 노트를 탐색하세요 -- 링크를 놓치면 중요한 맥락을 잃습니다
428
- - 지식 파일에 기록된 아키텍처 결정, 패턴, 제약사항을 반드시 따르세요
429
- - 읽은 지식을 현재 작업의 설계, 구현, 검증에 직접 반영하세요
430
-
431
- ### 개발 원칙
432
-
433
- - **TDD** (Test-Driven Development): 테스트를 먼저 작성하고(RED), 구현하여 통과시킨 뒤(GREEN), 리팩토링하세요
434
- - **DDD** (Domain-Driven Design): 도메인 개념을 코드 구조에 반영하세요. 타입과 모듈은 비즈니스 도메인을 기준으로 분리하세요
435
- - **테스트 커버리지**: 새로 작성하거나 변경한 코드는 테스트 커버리지 80% 이상을 목표로 하세요. 구현 전에 테스트부터 작성하면 자연스럽게 달성됩니다
436
-
437
- ### 우선순위
438
-
439
- - AGENTS.md의 지시사항이 항상 최우선
440
- - 지식 노트의 결정사항 > 일반적 관행
441
- - 지식 노트에 없는 새로운 결정이나 반복 가치가 있는 발견은 작업 메모나 지식 노트 후보로 기록하세요
442
- `;
443
- var DEFAULT_TURN_END = `## TURN END 작업 지침
444
- 이 지침은 에이전트를 위한 것이며, 사용자에게 직접적으로 노출하지 않습니다.
445
- 아래 메뉴 중 하나를 선택해 진행 상황에 맞게 수행하세요.
446
- **반드시 링크된 가이드를 참고하여 정확히 수행해야 합니다.**
447
-
448
- 1. **계속 작업**: 기존 작업이 완료되지 않았고 아직 아래 액션을 취할 단계가 아니라면 작업 속개.
449
- 2. **데일리 노트 기록**: [.context/guides/daily-note-guide.md] 데일리 노트에 중요한 컨텍스트를 기록하여 다음 세션이나 에이전트 팀이 참고할 수 있도록 하세요. 기존 내용 수정은 불가하며, 새로운 메모를 추가 하는것만 가능합니다. 간략한 한 두 문장으로 작성하여 핵심 컨텍스트가 명확히 전달되도록 하세요.
450
- 3. **지식 노트 작성**: [.context/guides/note-guide.md] 작업기억(데일리노트, 세션 컨텍스트)보다 오래 기억되어야 하는 중요한 결정, 패턴, 실수, 발견은 지식 노트로 기록하여 프로젝트의 집단 지식으로 남기세요.
451
- 4. **노트/스킬 검색 및 읽기**: [.context/guides/search-guide.md] 어려움에 처했다면 현재 진행 상황에 필요한 지식이나 스킬이 있는지 확인하고, 관련 노트를 읽어보세요. 새로운 아이디어나 해결책이 떠오를 수 있습니다.
452
- 5. **작업 경로 리뷰**: [.context/guides/scope-review.md] 사용자가 의도한 작업 범위를 벗어나지 않았는지, 작업이 너무 크거나 복잡해지지는 않았는지 검토하세요.
453
- 6. **체크포인트 커밋**: [.context/guides/commit-guide.md] 작업이 길어질 경우, 중요한 단계마다 체크포인트 커밋을 하여 작업 내용을 안전하게 저장하고, 필요 시 이전 상태로 돌아갈 수 있도록 하세요.
454
- 7. **퀄리티 검증**: [.context/guides/quality-check.md] **작업 완료 전에 반드시 수행하세요**. 코드 린트, 포맷터, 테스트, 빌드, 코드리뷰를 실행하여 작업 결과물이 프로젝트의 품질 기준을 충족하는지 확인하세요.
455
- 8. **작업 완료**: [.context/guides/complete-guide.md] 모든 작업이 완료되었다면, 이 가이드를 따르세요. 이 작업 지침이 더이상 트리거되지 않을 것입니다.
456
- `;
457
177
  var DEFAULT_ADR_TEMPLATE = `# ADR-NNN: [제목]
458
178
 
459
179
  ## 상태
@@ -745,25 +465,21 @@ var TEMPLATE_FILES = {
745
465
  "work-complete.txt": DEFAULT_WORK_COMPLETE_TEMPLATE
746
466
  };
747
467
  function scaffoldIfNeeded(projectDir) {
748
- const contextDir = join4(projectDir, resolveContextDir(projectDir));
749
- if (existsSync3(contextDir)) {
468
+ const contextDir = join3(projectDir, resolveContextDir(projectDir));
469
+ if (existsSync2(contextDir)) {
750
470
  return false;
751
471
  }
752
472
  try {
753
- const promptsDir = join4(contextDir, "prompts");
754
- mkdirSync(promptsDir, { recursive: true });
755
- const templatesDir = join4(contextDir, "templates");
473
+ const templatesDir = join3(contextDir, "templates");
756
474
  mkdirSync(templatesDir, { recursive: true });
757
- const guidesDir = join4(contextDir, "guides");
475
+ const guidesDir = join3(contextDir, "guides");
758
476
  mkdirSync(guidesDir, { recursive: true });
759
- writeFileSync(join4(contextDir, "config.jsonc"), DEFAULT_CONFIG, "utf-8");
760
- writeFileSync(join4(promptsDir, DEFAULTS.turnStartFile), DEFAULT_TURN_START, "utf-8");
761
- writeFileSync(join4(promptsDir, DEFAULTS.turnEndFile), DEFAULT_TURN_END, "utf-8");
477
+ writeFileSync(join3(contextDir, "config.jsonc"), DEFAULT_CONFIG, "utf-8");
762
478
  for (const [filename, content] of Object.entries(TEMPLATE_FILES)) {
763
- writeFileSync(join4(templatesDir, filename), content, "utf-8");
479
+ writeFileSync(join3(templatesDir, filename), content, "utf-8");
764
480
  }
765
481
  for (const [filename, content] of Object.entries(GUIDE_FILES)) {
766
- writeFileSync(join4(guidesDir, filename), content, "utf-8");
482
+ writeFileSync(join3(guidesDir, filename), content, "utf-8");
767
483
  }
768
484
  writeVersion(contextDir, PLUGIN_VERSION);
769
485
  return true;
@@ -772,11 +488,11 @@ function scaffoldIfNeeded(projectDir) {
772
488
  }
773
489
  }
774
490
  function writeVersion(contextDir, version) {
775
- writeFileSync(join4(contextDir, ".version"), version, "utf-8");
491
+ writeFileSync(join3(contextDir, ".version"), version, "utf-8");
776
492
  }
777
493
 
778
494
  // src/omx/agents-md.ts
779
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync5, renameSync, writeFileSync as writeFileSync2 } from "node:fs";
495
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync2 } from "node:fs";
780
496
  import { dirname } from "node:path";
781
497
  var START_MARKER = "<!-- context:start -->";
782
498
  var END_MARKER = "<!-- context:end -->";
@@ -819,11 +535,11 @@ function writeFileAtomically(filePath, content) {
819
535
  }
820
536
  function injectIntoAgentsMd(agentsMdPath, content) {
821
537
  mkdirSync2(dirname(agentsMdPath), { recursive: true });
822
- if (!existsSync4(agentsMdPath)) {
538
+ if (!existsSync3(agentsMdPath)) {
823
539
  writeFileAtomically(agentsMdPath, renderMarkerBlock(content, true));
824
540
  return;
825
541
  }
826
- const existingContent = readFileSync5(agentsMdPath, "utf-8");
542
+ const existingContent = readFileSync3(agentsMdPath, "utf-8");
827
543
  const nextContent = existingContent.includes(START_MARKER) && existingContent.includes(END_MARKER) ? replaceMarkerBlock(existingContent, content) : appendMarkerBlock(existingContent, content);
828
544
  writeFileAtomically(agentsMdPath, nextContent);
829
545
  }
@@ -894,21 +610,6 @@ function clearCurrentFollowupScope(pendingScopes, scopeKey) {
894
610
  function resolveProjectDir(event) {
895
611
  return event.context?.projectDir ?? event.context?.directory ?? process.cwd();
896
612
  }
897
- function resolvePromptPath(directory, contextDir, promptPath) {
898
- if (isAbsolute(promptPath))
899
- return promptPath;
900
- if (promptPath.startsWith(".context/") || promptPath.startsWith(".opencode/")) {
901
- return join5(directory, promptPath);
902
- }
903
- return join5(directory, contextDir, promptPath);
904
- }
905
- function buildPromptVars(config) {
906
- return { knowledgeDir: config.knowledge.dir ?? DEFAULTS.knowledgeDir };
907
- }
908
- function buildKnowledgeContent(projectDir, config) {
909
- const knowledgeIndex = buildKnowledgeIndexV2(projectDir, config.knowledge);
910
- return knowledgeIndex.mode === "flat" ? formatKnowledgeIndex(knowledgeIndex.individualFiles) : formatDomainIndex(knowledgeIndex);
911
- }
912
613
  function resolveFollowupScopeKey(event) {
913
614
  if (event.session_id && event.session_id.trim().length > 0) {
914
615
  return `session:${event.session_id.trim()}`;
@@ -925,27 +626,39 @@ async function logWarn(sdk, message, meta = {}) {
925
626
  }
926
627
  await sdk.log.info(message, meta);
927
628
  }
629
+ var STATIC_KNOWLEDGE_CONTEXT = `## Knowledge Context
630
+
631
+ 이 프로젝트는 **제텔카스텐(Zettelkasten)** 방식으로 지식을 관리합니다.
632
+ 세션 간 컨텍스트를 보존하여, 이전 세션의 결정/패턴/실수가 다음 세션에서 재활용됩니다.
633
+
634
+ ### 제텔카스텐 핵심 원칙
635
+ 1. **원자성** -- 하나의 노트 = 하나의 주제. 여러 주제를 섞지 마세요.
636
+ 2. **연결** -- 모든 노트는 [[wikilink]]로 관련 노트에 연결. 고립된 노트는 발견되지 않습니다.
637
+ 3. **자기 언어** -- 복사-붙여넣기가 아닌, 핵심을 이해하고 간결하게 서술하세요.
638
+
639
+ ### 작업 전 필수
640
+ - **데일리 노트 확인**: 가장 최근의 데일리 노트를 읽고 이전 세션의 컨텍스트와 미해결 이슈를 파악하세요. (Use \`read_daily_note\` tool)
641
+ - **작업 의도 선언**: 작업 시작 전, 현재 세션의 목표와 작업 의도를 명확히 파악하고 선언하세요.
642
+ - **지식 검색**: 작업과 관련된 문서를 **직접 먼저** 검색하고 읽으세요. (Use \`search_knowledge\` and \`read_knowledge\` tools)
643
+ - 지식 파일에 기록된 아키텍처 결정, 패턴, 제약사항을 반드시 따르세요.
644
+
645
+ ### 개발 원칙
646
+ - **TDD** (Test-Driven Development): 테스트를 먼저 작성하고(RED), 구현하여 통과시킨 뒤(GREEN), 리팩토링하세요.
647
+ - **DDD** (Domain-Driven Design): 도메인 개념을 코드 구조에 반영하세요.
648
+ - **테스트 커버리지**: 새로 작성하거나 변경한 코드는 테스트 커버리지 80% 이상을 목표로 하세요.
649
+
650
+ ### 우선순위
651
+ - AGENTS.md의 지시사항이 항상 최우선
652
+ - 지식 노트의 결정사항 > 일반적 관행
653
+ - 지식 노트에 없는 새로운 결정이나 반복 가치가 있는 발견은 작업 메모나 지식 노트 후보로 기록하세요. (Use \`create_knowledge_note\` tool)`;
928
654
  async function onSessionStart(event, sdk) {
929
655
  const projectDir = resolveProjectDir(event);
930
- const contextDir = resolveContextDir(projectDir);
931
656
  scaffoldIfNeeded(projectDir);
932
- const config = loadConfig(projectDir);
933
- const promptVars = buildPromptVars(config);
934
- const turnStartPath = resolvePromptPath(projectDir, contextDir, config.prompts.turnStart ?? join5(DEFAULTS.promptDir, DEFAULTS.turnStartFile));
935
- const turnStart = resolvePromptVariables(readPromptFile(turnStartPath), promptVars);
936
- const indexContent = buildKnowledgeContent(projectDir, config);
937
- const combinedContent = [turnStart, indexContent].filter(Boolean).join(`
938
-
939
- `);
940
- if (!combinedContent) {
941
- return;
942
- }
943
- injectIntoAgentsMd(join5(projectDir, "AGENTS.md"), combinedContent);
657
+ injectIntoAgentsMd(join4(projectDir, "AGENTS.md"), STATIC_KNOWLEDGE_CONTEXT);
944
658
  await sdk.log.info(`Injected context into AGENTS.md for ${projectDir}`);
945
659
  }
946
660
  async function onTurnComplete(event, sdk) {
947
661
  const projectDir = resolveProjectDir(event);
948
- const contextDir = resolveContextDir(projectDir);
949
662
  const config = loadConfig(projectDir);
950
663
  const strategy = config.omx?.turnEnd?.strategy ?? "off";
951
664
  if (strategy !== "turn-complete-sendkeys") {
@@ -968,9 +681,9 @@ async function onTurnComplete(event, sdk) {
968
681
  }
969
682
  const followupScopeKey = resolveFollowupScopeKey(event);
970
683
  let pendingFollowupScopes = typeof sdk.state?.read === "function" ? await sdk.state.read(TURN_END_PENDING_SKIP_KEY, {}) ?? {} : {};
971
- const workCompleteFile = join5(projectDir, DEFAULTS.workCompleteFile);
972
- if (existsSync5(workCompleteFile)) {
973
- const content = readFileSync6(workCompleteFile, "utf-8");
684
+ const workCompleteFile = join4(projectDir, DEFAULTS.workCompleteFile);
685
+ if (existsSync4(workCompleteFile)) {
686
+ const content = readFileSync4(workCompleteFile, "utf-8");
974
687
  const { sessionId: fileSessionId, turnId: fileTurnId } = parseWorkComplete(content);
975
688
  const currentScopeId = event.session_id ?? event.thread_id ?? "";
976
689
  if (!fileSessionId || fileSessionId === currentScopeId) {
@@ -1022,28 +735,6 @@ async function onTurnComplete(event, sdk) {
1022
735
  });
1023
736
  return;
1024
737
  }
1025
- const promptVars = {
1026
- ...buildPromptVars(config),
1027
- sessionId: event.session_id ?? event.thread_id ?? "unknown",
1028
- turnId: event.turn_id ?? ""
1029
- };
1030
- const turnEndPath = resolvePromptPath(projectDir, contextDir, config.prompts.turnEnd ?? join5(DEFAULTS.promptDir, DEFAULTS.turnEndFile));
1031
- let turnEndRaw = readPromptFile(turnEndPath);
1032
- if (!turnEndRaw) {
1033
- await sdk.log.info("turn_end_skipped_empty_prompt", {
1034
- event: event.event,
1035
- session_id: event.session_id,
1036
- turn_id: event.turn_id
1037
- });
1038
- return;
1039
- }
1040
- if (turnEndRaw.includes("<!-- context:start -->") || turnEndRaw.includes("## Knowledge Context")) {
1041
- const defaultTurnEndPath = join5(projectDir, DEFAULTS.promptDir, DEFAULTS.turnEndFile);
1042
- turnEndRaw = readPromptFile(defaultTurnEndPath);
1043
- if (!turnEndRaw) {
1044
- return;
1045
- }
1046
- }
1047
738
  if (typeof sdk.tmux?.sendKeys !== "function") {
1048
739
  await logWarn(sdk, "turn_end_sendkeys_failed", {
1049
740
  event: event.event,
@@ -1053,7 +744,7 @@ async function onTurnComplete(event, sdk) {
1053
744
  });
1054
745
  return;
1055
746
  }
1056
- const turnEnd = resolvePromptVariables(turnEndRaw, promptVars).trim();
747
+ const turnEnd = "TURN END. You MUST call the 'submit_turn_complete' MCP tool to finalize your work and record notes. Do not wait for user input.";
1057
748
  const reminderText = `<system-reminder>
1058
749
  ${turnEnd}
1059
750
  </system-reminder>`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ksm0709/context",
3
- "version": "0.0.22",
3
+ "version": "0.0.23",
4
4
  "author": {
5
5
  "name": "TaehoKang",
6
6
  "email": "ksm07091@gmail.com"
@@ -30,10 +30,10 @@
30
30
  "access": "public"
31
31
  },
32
32
  "scripts": {
33
- "build": "bun build ./src/index.ts --outdir dist --target bun && bun build ./src/cli/index.ts --outdir dist/cli --target bun && bun build ./src/omx/index.ts --outdir dist/omx --target node --format esm --external jsonc-parser && mv dist/omx/index.js dist/omx/index.mjs",
33
+ "build": "bun build ./src/index.ts --outdir dist --target bun && bun build ./src/mcp.ts --outdir dist --target bun && bun build ./src/cli/index.ts --outdir dist/cli --target bun && bun build ./src/omx/index.ts --outdir dist/omx --target node --format esm --external jsonc-parser && mv dist/omx/index.js dist/omx/index.mjs",
34
34
  "test": "vitest run",
35
35
  "lint": "eslint src --ext .ts",
36
- "prepublishOnly": "bun build ./src/index.ts --outdir dist --target bun && bun build ./src/cli/index.ts --outdir dist/cli --target bun && bun build ./src/omx/index.ts --outdir dist/omx --target node --format esm --external jsonc-parser && mv dist/omx/index.js dist/omx/index.mjs"
36
+ "prepublishOnly": "bun build ./src/index.ts --outdir dist --target bun && bun build ./src/mcp.ts --outdir dist --target bun && bun build ./src/cli/index.ts --outdir dist/cli --target bun && bun build ./src/omx/index.ts --outdir dist/omx --target node --format esm --external jsonc-parser && mv dist/omx/index.js dist/omx/index.mjs"
37
37
  },
38
38
  "files": [
39
39
  "dist"
@@ -42,7 +42,8 @@
42
42
  "@opencode-ai/plugin": ">=1.0.0"
43
43
  },
44
44
  "dependencies": {
45
- "@ksm0709/context": "^0.0.21",
45
+ "@ksm0709/context": "^0.0.22",
46
+ "@modelcontextprotocol/sdk": "^1.27.1",
46
47
  "jsonc-parser": "^3.0.0"
47
48
  },
48
49
  "devDependencies": {