@ksm0709/context 0.0.21 → 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.21",
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.20",
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,12 +163,8 @@ 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
- "dir": "docs",
167
+ "dir": ".context/memory",
401
168
  "sources": ["AGENTS.md"]
402
169
  },
403
170
  "omx": {
@@ -407,52 +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
- 1. **계속 작업**: 기존 작업이 완료되지 않았고 아직 아래 액션을 취할 단계가 아니라면 작업 속개.
448
- 2. **데일리 노트 기록**: [.context/guides/daily-note-guide.md] 데일리 노트에 중요한 컨텍스트를 기록하여 다음 세션이나 에이전트 팀이 참고할 수 있도록 하세요. 기존 내용 수정은 불가하며, 새로운 메모를 추가 하는것만 가능합니다. 간략한 한 두 문장으로 작성하여 핵심 컨텍스트가 명확히 전달되도록 하세요.
449
- 3. **지식 노트 작성**: [.context/guides/note-guide.md] 작업기억(데일리노트, 세션 컨텍스트)보다 오래 기억되어야 하는 중요한 결정, 패턴, 실수, 발견은 지식 노트로 기록하여 프로젝트의 집단 지식으로 남기세요.
450
- 4. **노트/스킬 검색 및 읽기**: [.context/guides/search-guide.md] 어려움에 처했다면 현재 진행 상황에 필요한 지식이나 스킬이 있는지 확인하고, 관련 노트를 읽어보세요. 새로운 아이디어나 해결책이 떠오를 수 있습니다.
451
- 5. **작업 경로 리뷰**: [.context/guides/scope-review.md] 사용자가 의도한 작업 범위를 벗어나지 않았는지, 작업이 너무 크거나 복잡해지지는 않았는지 검토하세요.
452
- 6. **체크포인트 커밋**: [.context/guides/commit-guide.md] 작업이 길어질 경우, 중요한 단계마다 체크포인트 커밋을 하여 작업 내용을 안전하게 저장하고, 필요 시 이전 상태로 돌아갈 수 있도록 하세요.
453
- 7. **퀄리티 검증**: [.context/guides/quality-check.md] **작업 완료 전에 반드시 수행하세요**. 코드 린트, 포맷터, 테스트, 빌드, 코드리뷰를 실행하여 작업 결과물이 프로젝트의 품질 기준을 충족하는지 확인하세요.
454
- 8. **작업 완료**: [.context/guides/complete-guide.md] 모든 작업이 완료되었다면, 이 가이드를 따르세요. 이 작업 지침이 더이상 트리거되지 않을 것입니다.
455
- `;
456
177
  var DEFAULT_ADR_TEMPLATE = `# ADR-NNN: [제목]
457
178
 
458
179
  ## 상태
@@ -744,25 +465,21 @@ var TEMPLATE_FILES = {
744
465
  "work-complete.txt": DEFAULT_WORK_COMPLETE_TEMPLATE
745
466
  };
746
467
  function scaffoldIfNeeded(projectDir) {
747
- const contextDir = join4(projectDir, resolveContextDir(projectDir));
748
- if (existsSync3(contextDir)) {
468
+ const contextDir = join3(projectDir, resolveContextDir(projectDir));
469
+ if (existsSync2(contextDir)) {
749
470
  return false;
750
471
  }
751
472
  try {
752
- const promptsDir = join4(contextDir, "prompts");
753
- mkdirSync(promptsDir, { recursive: true });
754
- const templatesDir = join4(contextDir, "templates");
473
+ const templatesDir = join3(contextDir, "templates");
755
474
  mkdirSync(templatesDir, { recursive: true });
756
- const guidesDir = join4(contextDir, "guides");
475
+ const guidesDir = join3(contextDir, "guides");
757
476
  mkdirSync(guidesDir, { recursive: true });
758
- writeFileSync(join4(contextDir, "config.jsonc"), DEFAULT_CONFIG, "utf-8");
759
- writeFileSync(join4(promptsDir, DEFAULTS.turnStartFile), DEFAULT_TURN_START, "utf-8");
760
- writeFileSync(join4(promptsDir, DEFAULTS.turnEndFile), DEFAULT_TURN_END, "utf-8");
477
+ writeFileSync(join3(contextDir, "config.jsonc"), DEFAULT_CONFIG, "utf-8");
761
478
  for (const [filename, content] of Object.entries(TEMPLATE_FILES)) {
762
- writeFileSync(join4(templatesDir, filename), content, "utf-8");
479
+ writeFileSync(join3(templatesDir, filename), content, "utf-8");
763
480
  }
764
481
  for (const [filename, content] of Object.entries(GUIDE_FILES)) {
765
- writeFileSync(join4(guidesDir, filename), content, "utf-8");
482
+ writeFileSync(join3(guidesDir, filename), content, "utf-8");
766
483
  }
767
484
  writeVersion(contextDir, PLUGIN_VERSION);
768
485
  return true;
@@ -771,11 +488,11 @@ function scaffoldIfNeeded(projectDir) {
771
488
  }
772
489
  }
773
490
  function writeVersion(contextDir, version) {
774
- writeFileSync(join4(contextDir, ".version"), version, "utf-8");
491
+ writeFileSync(join3(contextDir, ".version"), version, "utf-8");
775
492
  }
776
493
 
777
494
  // src/omx/agents-md.ts
778
- 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";
779
496
  import { dirname } from "node:path";
780
497
  var START_MARKER = "<!-- context:start -->";
781
498
  var END_MARKER = "<!-- context:end -->";
@@ -818,11 +535,11 @@ function writeFileAtomically(filePath, content) {
818
535
  }
819
536
  function injectIntoAgentsMd(agentsMdPath, content) {
820
537
  mkdirSync2(dirname(agentsMdPath), { recursive: true });
821
- if (!existsSync4(agentsMdPath)) {
538
+ if (!existsSync3(agentsMdPath)) {
822
539
  writeFileAtomically(agentsMdPath, renderMarkerBlock(content, true));
823
540
  return;
824
541
  }
825
- const existingContent = readFileSync5(agentsMdPath, "utf-8");
542
+ const existingContent = readFileSync3(agentsMdPath, "utf-8");
826
543
  const nextContent = existingContent.includes(START_MARKER) && existingContent.includes(END_MARKER) ? replaceMarkerBlock(existingContent, content) : appendMarkerBlock(existingContent, content);
827
544
  writeFileAtomically(agentsMdPath, nextContent);
828
545
  }
@@ -893,21 +610,6 @@ function clearCurrentFollowupScope(pendingScopes, scopeKey) {
893
610
  function resolveProjectDir(event) {
894
611
  return event.context?.projectDir ?? event.context?.directory ?? process.cwd();
895
612
  }
896
- function resolvePromptPath(directory, contextDir, promptPath) {
897
- if (isAbsolute(promptPath))
898
- return promptPath;
899
- if (promptPath.startsWith(".context/") || promptPath.startsWith(".opencode/")) {
900
- return join5(directory, promptPath);
901
- }
902
- return join5(directory, contextDir, promptPath);
903
- }
904
- function buildPromptVars(config) {
905
- return { knowledgeDir: config.knowledge.dir ?? DEFAULTS.knowledgeDir };
906
- }
907
- function buildKnowledgeContent(projectDir, config) {
908
- const knowledgeIndex = buildKnowledgeIndexV2(projectDir, config.knowledge);
909
- return knowledgeIndex.mode === "flat" ? formatKnowledgeIndex(knowledgeIndex.individualFiles) : formatDomainIndex(knowledgeIndex);
910
- }
911
613
  function resolveFollowupScopeKey(event) {
912
614
  if (event.session_id && event.session_id.trim().length > 0) {
913
615
  return `session:${event.session_id.trim()}`;
@@ -924,27 +626,39 @@ async function logWarn(sdk, message, meta = {}) {
924
626
  }
925
627
  await sdk.log.info(message, meta);
926
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)`;
927
654
  async function onSessionStart(event, sdk) {
928
655
  const projectDir = resolveProjectDir(event);
929
- const contextDir = resolveContextDir(projectDir);
930
656
  scaffoldIfNeeded(projectDir);
931
- const config = loadConfig(projectDir);
932
- const promptVars = buildPromptVars(config);
933
- const turnStartPath = resolvePromptPath(projectDir, contextDir, config.prompts.turnStart ?? join5(DEFAULTS.promptDir, DEFAULTS.turnStartFile));
934
- const turnStart = resolvePromptVariables(readPromptFile(turnStartPath), promptVars);
935
- const indexContent = buildKnowledgeContent(projectDir, config);
936
- const combinedContent = [turnStart, indexContent].filter(Boolean).join(`
937
-
938
- `);
939
- if (!combinedContent) {
940
- return;
941
- }
942
- injectIntoAgentsMd(join5(projectDir, "AGENTS.md"), combinedContent);
657
+ injectIntoAgentsMd(join4(projectDir, "AGENTS.md"), STATIC_KNOWLEDGE_CONTEXT);
943
658
  await sdk.log.info(`Injected context into AGENTS.md for ${projectDir}`);
944
659
  }
945
660
  async function onTurnComplete(event, sdk) {
946
661
  const projectDir = resolveProjectDir(event);
947
- const contextDir = resolveContextDir(projectDir);
948
662
  const config = loadConfig(projectDir);
949
663
  const strategy = config.omx?.turnEnd?.strategy ?? "off";
950
664
  if (strategy !== "turn-complete-sendkeys") {
@@ -967,9 +681,9 @@ async function onTurnComplete(event, sdk) {
967
681
  }
968
682
  const followupScopeKey = resolveFollowupScopeKey(event);
969
683
  let pendingFollowupScopes = typeof sdk.state?.read === "function" ? await sdk.state.read(TURN_END_PENDING_SKIP_KEY, {}) ?? {} : {};
970
- const workCompleteFile = join5(projectDir, DEFAULTS.workCompleteFile);
971
- if (existsSync5(workCompleteFile)) {
972
- const content = readFileSync6(workCompleteFile, "utf-8");
684
+ const workCompleteFile = join4(projectDir, DEFAULTS.workCompleteFile);
685
+ if (existsSync4(workCompleteFile)) {
686
+ const content = readFileSync4(workCompleteFile, "utf-8");
973
687
  const { sessionId: fileSessionId, turnId: fileTurnId } = parseWorkComplete(content);
974
688
  const currentScopeId = event.session_id ?? event.thread_id ?? "";
975
689
  if (!fileSessionId || fileSessionId === currentScopeId) {
@@ -1021,21 +735,6 @@ async function onTurnComplete(event, sdk) {
1021
735
  });
1022
736
  return;
1023
737
  }
1024
- const promptVars = {
1025
- ...buildPromptVars(config),
1026
- sessionId: event.session_id ?? event.thread_id ?? "unknown",
1027
- turnId: event.turn_id ?? ""
1028
- };
1029
- const turnEndPath = resolvePromptPath(projectDir, contextDir, config.prompts.turnEnd ?? join5(DEFAULTS.promptDir, DEFAULTS.turnEndFile));
1030
- const turnEndRaw = readPromptFile(turnEndPath);
1031
- if (!turnEndRaw) {
1032
- await sdk.log.info("turn_end_skipped_empty_prompt", {
1033
- event: event.event,
1034
- session_id: event.session_id,
1035
- turn_id: event.turn_id
1036
- });
1037
- return;
1038
- }
1039
738
  if (typeof sdk.tmux?.sendKeys !== "function") {
1040
739
  await logWarn(sdk, "turn_end_sendkeys_failed", {
1041
740
  event: event.event,
@@ -1045,11 +744,13 @@ async function onTurnComplete(event, sdk) {
1045
744
  });
1046
745
  return;
1047
746
  }
1048
- const turnEnd = resolvePromptVariables(turnEndRaw, promptVars);
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.";
1049
748
  const reminderText = `<system-reminder>
1050
749
  ${turnEnd}
1051
750
  </system-reminder>`;
1052
751
  const sessionName = typeof event.context?.session_name === "string" && event.context.session_name.trim().length > 0 ? event.context.session_name.trim() : undefined;
752
+ await new Promise((resolve) => globalThis.setTimeout(resolve, 500));
753
+ if (typeof sdk.tmux?.sendKeys === "function") {}
1053
754
  const result = await sdk.tmux.sendKeys({
1054
755
  sessionName,
1055
756
  text: reminderText,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ksm0709/context",
3
- "version": "0.0.21",
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.20",
45
+ "@ksm0709/context": "^0.0.22",
46
+ "@modelcontextprotocol/sdk": "^1.27.1",
46
47
  "jsonc-parser": "^3.0.0"
47
48
  },
48
49
  "devDependencies": {