@ksm0709/context 0.0.5 → 0.0.7

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/index.js CHANGED
@@ -818,14 +818,17 @@ var DEFAULTS = {
818
818
  turnEndFile: "turn-end.md",
819
819
  knowledgeSources: ["AGENTS.md"],
820
820
  knowledgeDir: "docs",
821
- templateDir: ".opencode/context/templates"
821
+ templateDir: ".opencode/context/templates",
822
+ indexFilename: "INDEX.md",
823
+ maxDomainDepth: 2
822
824
  };
823
825
  var LIMITS = {
824
826
  maxPromptFileSize: 64 * 1024,
825
827
  maxIndexEntries: 100,
826
828
  maxTotalInjectionSize: 128 * 1024,
827
829
  maxScanDepth: 3,
828
- maxSummaryLength: 100
830
+ maxSummaryLength: 100,
831
+ maxIndexFileSize: 32 * 1024
829
832
  };
830
833
 
831
834
  // src/lib/config.ts
@@ -837,7 +840,10 @@ function getDefaultConfig() {
837
840
  },
838
841
  knowledge: {
839
842
  dir: DEFAULTS.knowledgeDir,
840
- sources: [...DEFAULTS.knowledgeSources]
843
+ sources: [...DEFAULTS.knowledgeSources],
844
+ mode: "auto",
845
+ indexFilename: DEFAULTS.indexFilename,
846
+ maxDomainDepth: DEFAULTS.maxDomainDepth
841
847
  }
842
848
  };
843
849
  }
@@ -850,7 +856,10 @@ function mergeWithDefaults(partial) {
850
856
  },
851
857
  knowledge: {
852
858
  dir: partial.knowledge?.dir ?? defaults.knowledge.dir,
853
- sources: partial.knowledge?.sources ?? defaults.knowledge.sources
859
+ sources: partial.knowledge?.sources ?? defaults.knowledge.sources,
860
+ mode: partial.knowledge?.mode ?? defaults.knowledge.mode,
861
+ indexFilename: partial.knowledge?.indexFilename ?? defaults.knowledge.indexFilename,
862
+ maxDomainDepth: partial.knowledge?.maxDomainDepth ?? defaults.knowledge.maxDomainDepth
854
863
  }
855
864
  };
856
865
  }
@@ -939,6 +948,131 @@ function formatKnowledgeIndex(entries) {
939
948
  return lines.join(`
940
949
  `);
941
950
  }
951
+ function countMdFiles(dir, indexFilename) {
952
+ try {
953
+ const items = readdirSync(dir);
954
+ return items.filter((item) => extname(item) === ".md" && item !== indexFilename && statSync(join2(dir, item)).isFile()).length;
955
+ } catch {
956
+ return 0;
957
+ }
958
+ }
959
+ function scanDomainsRecursive(baseDir, projectDir, indexFilename, currentDepth, maxDepth, results) {
960
+ if (currentDepth > maxDepth)
961
+ return;
962
+ try {
963
+ const items = readdirSync(baseDir);
964
+ for (const item of items) {
965
+ const fullPath = join2(baseDir, item);
966
+ try {
967
+ if (!statSync(fullPath).isDirectory())
968
+ continue;
969
+ const indexPath = join2(fullPath, indexFilename);
970
+ if (existsSync(indexPath) && statSync(indexPath).isFile()) {
971
+ const rawContent = readFileSync2(indexPath, "utf-8");
972
+ const indexContent = rawContent.slice(0, LIMITS.maxIndexFileSize);
973
+ results.push({
974
+ domain: item,
975
+ path: relative(projectDir, fullPath),
976
+ indexContent,
977
+ noteCount: countMdFiles(fullPath, indexFilename)
978
+ });
979
+ }
980
+ scanDomainsRecursive(fullPath, projectDir, indexFilename, currentDepth + 1, maxDepth, results);
981
+ } catch {}
982
+ }
983
+ } catch {}
984
+ }
985
+ function scanDomains(projectDir, knowledgeDir, indexFilename, maxDepth) {
986
+ const baseDir = join2(projectDir, knowledgeDir);
987
+ if (!existsSync(baseDir))
988
+ return [];
989
+ const results = [];
990
+ scanDomainsRecursive(baseDir, projectDir, indexFilename, 1, maxDepth, results);
991
+ return results;
992
+ }
993
+ function detectKnowledgeMode(projectDir, knowledgeDir, indexFilename, configMode) {
994
+ if (configMode !== "auto")
995
+ return configMode;
996
+ const domains = scanDomains(projectDir, knowledgeDir, indexFilename, 1);
997
+ return domains.length > 0 ? "domain" : "flat";
998
+ }
999
+ function formatDomainIndex(index) {
1000
+ const hasDomains = index.domains.length > 0;
1001
+ const hasFiles = index.individualFiles.length > 0;
1002
+ if (!hasDomains && !hasFiles)
1003
+ return "";
1004
+ const lines = ["## Available Knowledge", ""];
1005
+ if (hasDomains) {
1006
+ lines.push("### Domains", "");
1007
+ for (const domain of index.domains) {
1008
+ lines.push(`#### ${domain.path}/ (${domain.noteCount} notes)`, "");
1009
+ lines.push(domain.indexContent, "");
1010
+ }
1011
+ }
1012
+ if (hasFiles) {
1013
+ if (hasDomains) {
1014
+ lines.push("### Individual Files", "");
1015
+ }
1016
+ for (const file of index.individualFiles) {
1017
+ lines.push(`- ${file.filename}${file.summary ? ` \u2014 ${file.summary}` : ""}`);
1018
+ }
1019
+ }
1020
+ return lines.join(`
1021
+ `);
1022
+ }
1023
+ function collectRootFiles(projectDir, knowledgeDir, indexFilename) {
1024
+ const baseDir = join2(projectDir, knowledgeDir);
1025
+ if (!existsSync(baseDir))
1026
+ return [];
1027
+ const entries = [];
1028
+ try {
1029
+ const items = readdirSync(baseDir);
1030
+ for (const item of items) {
1031
+ const fullPath = join2(baseDir, item);
1032
+ try {
1033
+ const stat = statSync(fullPath);
1034
+ if (stat.isFile() && extname(item) === ".md" && item !== indexFilename) {
1035
+ entries.push({
1036
+ filename: relative(projectDir, fullPath),
1037
+ summary: extractSummary(fullPath)
1038
+ });
1039
+ }
1040
+ } catch {}
1041
+ }
1042
+ } catch {}
1043
+ return entries;
1044
+ }
1045
+ function buildKnowledgeIndexV2(projectDir, knowledgeConfig) {
1046
+ const dir = knowledgeConfig.dir ?? "docs";
1047
+ const indexFilename = knowledgeConfig.indexFilename ?? "INDEX.md";
1048
+ const maxDepth = knowledgeConfig.maxDomainDepth ?? 2;
1049
+ const configMode = knowledgeConfig.mode ?? "auto";
1050
+ const mode = detectKnowledgeMode(projectDir, dir, indexFilename, configMode);
1051
+ if (mode === "flat") {
1052
+ const allSources = [dir, ...knowledgeConfig.sources].filter(Boolean);
1053
+ const entries = buildKnowledgeIndex(projectDir, allSources);
1054
+ return { mode: "flat", domains: [], individualFiles: entries };
1055
+ }
1056
+ const domains = scanDomains(projectDir, dir, indexFilename, maxDepth);
1057
+ const rootFiles = collectRootFiles(projectDir, dir, indexFilename);
1058
+ const sourcesEntries = [];
1059
+ for (const source of knowledgeConfig.sources) {
1060
+ const fullPath = join2(projectDir, source);
1061
+ if (!existsSync(fullPath))
1062
+ continue;
1063
+ try {
1064
+ const stat = statSync(fullPath);
1065
+ if (stat.isFile() && extname(source) === ".md") {
1066
+ sourcesEntries.push({
1067
+ filename: source,
1068
+ summary: extractSummary(fullPath)
1069
+ });
1070
+ }
1071
+ } catch {}
1072
+ }
1073
+ const individualFiles = [...rootFiles, ...sourcesEntries];
1074
+ return { mode: "domain", domains, individualFiles };
1075
+ }
942
1076
 
943
1077
  // src/lib/prompt-reader.ts
944
1078
  import { readFileSync as readFileSync3 } from "fs";
@@ -957,6 +1091,11 @@ function readPromptFile(filePath) {
957
1091
  // src/lib/scaffold.ts
958
1092
  import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
959
1093
  import { join as join3 } from "path";
1094
+
1095
+ // src/version.ts
1096
+ var PLUGIN_VERSION = "0.0.6";
1097
+
1098
+ // src/lib/scaffold.ts
960
1099
  var DEFAULT_CONFIG = `{
961
1100
  // Context Plugin Configuration
962
1101
  // See: https://github.com/ksm0709/context
@@ -983,9 +1122,16 @@ var DEFAULT_TURN_START = `## Knowledge Context
983
1122
  ### \uC791\uC5C5 \uC804 \uD544\uC218
984
1123
 
985
1124
  - \uC544\uB798 **Available Knowledge** \uBAA9\uB85D\uC5D0\uC11C \uD604\uC7AC \uC791\uC5C5\uACFC \uAD00\uB828\uB41C \uBB38\uC11C\uB97C **\uBA3C\uC800** \uC77D\uC73C\uC138\uC694
1125
+ - \uB3C4\uBA54\uC778 \uD3F4\uB354 \uAD6C\uC870\uAC00 \uC788\uB2E4\uBA74 INDEX.md\uC758 \uC694\uC57D\uC744 \uCC38\uACE0\uD558\uC5EC \uD544\uC694\uD55C \uB178\uD2B8\uB9CC \uC120\uD0DD\uC801\uC73C\uB85C \uC77D\uC73C\uC138\uC694
986
1126
  - \uBB38\uC11C \uB0B4 [[\uB9C1\uD06C]]\uB97C \uB530\uB77C\uAC00\uBA70 \uAD00\uB828 \uB178\uD2B8\uB97C \uD0D0\uC0C9\uD558\uC138\uC694 -- \uB9C1\uD06C\uB97C \uB193\uCE58\uBA74 \uC911\uC694\uD55C \uB9E5\uB77D\uC744 \uC783\uC2B5\uB2C8\uB2E4
987
1127
  - \uC9C0\uC2DD \uD30C\uC77C\uC5D0 \uAE30\uB85D\uB41C \uC544\uD0A4\uD14D\uCC98 \uACB0\uC815, \uD328\uD134, \uC81C\uC57D\uC0AC\uD56D\uC744 \uBC18\uB4DC\uC2DC \uB530\uB974\uC138\uC694
988
1128
 
1129
+ ### \uAC1C\uBC1C \uC6D0\uCE59
1130
+
1131
+ - **TDD** (Test-Driven Development): \uD14C\uC2A4\uD2B8\uB97C \uBA3C\uC800 \uC791\uC131\uD558\uACE0(RED), \uAD6C\uD604\uD558\uC5EC \uD1B5\uACFC\uC2DC\uD0A8 \uB4A4(GREEN), \uB9AC\uD329\uD1A0\uB9C1\uD558\uC138\uC694
1132
+ - **DDD** (Domain-Driven Design): \uB3C4\uBA54\uC778 \uAC1C\uB150\uC744 \uCF54\uB4DC \uAD6C\uC870\uC5D0 \uBC18\uC601\uD558\uC138\uC694. \uD0C0\uC785\uACFC \uBAA8\uB4C8\uC740 \uBE44\uC988\uB2C8\uC2A4 \uB3C4\uBA54\uC778\uC744 \uAE30\uC900\uC73C\uB85C \uBD84\uB9AC\uD558\uC138\uC694
1133
+ - **\uD14C\uC2A4\uD2B8 \uCEE4\uBC84\uB9AC\uC9C0**: \uC0C8\uB85C \uC791\uC131\uD558\uAC70\uB098 \uBCC0\uACBD\uD55C \uCF54\uB4DC\uB294 \uD14C\uC2A4\uD2B8 \uCEE4\uBC84\uB9AC\uC9C0 80% \uC774\uC0C1\uC744 \uBAA9\uD45C\uB85C \uD558\uC138\uC694. \uAD6C\uD604 \uC804\uC5D0 \uD14C\uC2A4\uD2B8\uBD80\uD130 \uC791\uC131\uD558\uBA74 \uC790\uC5F0\uC2A4\uB7FD\uAC8C \uB2EC\uC131\uB429\uB2C8\uB2E4
1134
+
989
1135
  ### \uC6B0\uC120\uC21C\uC704
990
1136
 
991
1137
  - AGENTS.md\uC758 \uC9C0\uC2DC\uC0AC\uD56D\uC774 \uD56D\uC0C1 \uCD5C\uC6B0\uC120
@@ -999,8 +1145,10 @@ var DEFAULT_TURN_END = `## \uC791\uC5C5 \uB9C8\uBB34\uB9AC \uCCB4\uD06C\uB9AC\uC
999
1145
  ### \uD004\uB9AC\uD2F0 \uBCF4\uC7A5
1000
1146
 
1001
1147
  - [ ] \uBCC0\uACBD\uD55C \uCF54\uB4DC\uC5D0 \uB300\uD574 lint \uC2E4\uD589
1002
- - [ ] \uD0C0\uC785 \uC5D0\uB7EC \uD655\uC778
1148
+ - [ ] \uBCC0\uACBD\uD55C \uCF54\uB4DC\uC5D0 \uB300\uD574 formatter \uC2E4\uD589 (lint\uC640 \uBCC4\uAC1C)
1003
1149
  - [ ] \uAE30\uC874 \uD14C\uC2A4\uD2B8 \uD1B5\uACFC \uD655\uC778
1150
+ - [ ] \uBCC0\uACBD \uBC94\uC704 \uD655\uC778: \uC694\uCCAD\uACFC \uBB34\uAD00\uD55C \uD30C\uC77C\uC744 \uAC74\uB4DC\uB9AC\uC9C0 \uC54A\uC558\uB294\uAC00?
1151
+ - [ ] \uC0C8\uB85C \uC791\uC131\uD558\uAC70\uB098 \uBCC0\uACBD\uD55C \uCF54\uB4DC\uC758 \uD14C\uC2A4\uD2B8 \uCEE4\uBC84\uB9AC\uC9C0 80% \uC774\uC0C1 \uB2EC\uC131
1004
1152
 
1005
1153
  ### \uC9C0\uC2DD \uC815\uB9AC (Zettelkasten)
1006
1154
 
@@ -1020,13 +1168,14 @@ var DEFAULT_TURN_END = `## \uC791\uC5C5 \uB9C8\uBB34\uB9AC \uCCB4\uD06C\uB9AC\uC
1020
1168
  - [ ] \uC704 \uC0C1\uD669\uC5D0 \uD574\uB2F9\uD558\uB294 \uBC1C\uACAC\uC774 \uC788\uC5C8\uB2E4\uBA74 \uB178\uD2B8\uB97C \uC791\uC131\uD588\uB294\uAC00?
1021
1169
  - [ ] \uAD00\uB828 \uAE30\uC874 \uB178\uD2B8\uC5D0 [[\uB9C1\uD06C]]\uB97C \uCD94\uAC00\uD588\uB294\uAC00?
1022
1170
  - [ ] \uAE30\uC874 \uB178\uD2B8\uC758 \uB0B4\uC6A9\uC774 \uBCC0\uACBD\uC0AC\uD56D\uACFC \uBD88\uC77C\uCE58\uD558\uBA74 \uC5C5\uB370\uC774\uD2B8\uD588\uB294\uAC00?
1171
+ - [ ] \uB178\uD2B8\uB97C \uB3C4\uBA54\uC778 \uD3F4\uB354\uC5D0 \uC800\uC7A5\uD588\uB2E4\uBA74 \uD574\uB2F9 INDEX.md\uC5D0 \uCD94\uAC00\uD588\uB294\uAC00?
1023
1172
 
1024
1173
  #### \uB178\uD2B8 \uC791\uC131 \uADDC\uCE59
1025
1174
 
1026
1175
  - \uCCAB \uC904: \uBA85\uD655\uD55C \uC81C\uBAA9 (\`# Title\`)
1027
1176
  - \uD575\uC2EC \uB0B4\uC6A9\uC744 \uC790\uC2E0\uC758 \uC5B8\uC5B4\uB85C \uAC04\uACB0\uD558\uAC8C \uC11C\uC220
1028
1177
  - \uAD00\uB828 \uB178\uD2B8\uB97C \`[[relative/path/file.md]]\` \uD615\uD0DC\uC758 wikilink\uB85C \uC5F0\uACB0
1029
- - knowledge \uB514\uB809\uD1A0\uB9AC (\uAE30\uBCF8: \`docs/\`)\uC5D0 \uC800\uC7A5
1178
+ - knowledge \uB514\uB809\uD1A0\uB9AC (\uAE30\uBCF8: \`docs/\`)\uC5D0 \uC800\uC7A5. \uB3C4\uBA54\uC778 \uD3F4\uB354\uAC00 \uC788\uB2E4\uBA74 \uC801\uC808\uD55C \uB3C4\uBA54\uC778\uC5D0 \uC800\uC7A5
1030
1179
  `;
1031
1180
  var DEFAULT_ADR_TEMPLATE = `# ADR-NNN: [\uC81C\uBAA9]
1032
1181
 
@@ -1220,6 +1369,20 @@ var DEFAULT_INSIGHT_TEMPLATE = `# Insight: [\uBC1C\uACAC \uC81C\uBAA9]
1220
1369
 
1221
1370
  - [[\uAD00\uB828-insight.md]] / [[\uC601\uD5A5\uBC1B\uB294-\uD328\uD134.md]] / [[\uAD00\uB828-ADR.md]]
1222
1371
  `;
1372
+ var DEFAULT_INDEX_TEMPLATE = `# [Domain] Domain
1373
+
1374
+ Overview: [1-2 sentence description of this domain]
1375
+
1376
+ ## Notes
1377
+
1378
+ | File | Summary | Read When... |
1379
+ |------|---------|--------------|
1380
+ | [[example.md]] | Example summary | Working on X |
1381
+
1382
+ ## Related Domains
1383
+
1384
+ - [[../other-domain/INDEX.md]] -- Description
1385
+ `;
1223
1386
  var TEMPLATE_FILES = {
1224
1387
  "adr.md": DEFAULT_ADR_TEMPLATE,
1225
1388
  "pattern.md": DEFAULT_PATTERN_TEMPLATE,
@@ -1228,7 +1391,8 @@ var TEMPLATE_FILES = {
1228
1391
  "decision.md": DEFAULT_DECISION_TEMPLATE,
1229
1392
  "context.md": DEFAULT_CONTEXT_TEMPLATE,
1230
1393
  "runbook.md": DEFAULT_RUNBOOK_TEMPLATE,
1231
- "insight.md": DEFAULT_INSIGHT_TEMPLATE
1394
+ "insight.md": DEFAULT_INSIGHT_TEMPLATE,
1395
+ "index.md": DEFAULT_INDEX_TEMPLATE
1232
1396
  };
1233
1397
  function scaffoldIfNeeded(projectDir) {
1234
1398
  const contextDir = join3(projectDir, ".opencode", "context");
@@ -1246,6 +1410,7 @@ function scaffoldIfNeeded(projectDir) {
1246
1410
  for (const [filename, content] of Object.entries(TEMPLATE_FILES)) {
1247
1411
  writeFileSync(join3(templatesDir, filename), content, "utf-8");
1248
1412
  }
1413
+ writeVersion(contextDir, PLUGIN_VERSION);
1249
1414
  return true;
1250
1415
  } catch {
1251
1416
  return false;
@@ -1273,6 +1438,39 @@ function updateScaffold(projectDir) {
1273
1438
  writeFileSync(filePath, content, "utf-8");
1274
1439
  updated.push(path);
1275
1440
  }
1441
+ writeVersion(contextDir, PLUGIN_VERSION);
1442
+ return updated;
1443
+ }
1444
+ function getStoredVersion(projectDir) {
1445
+ try {
1446
+ return readFileSync4(join3(projectDir, ".opencode", "context", ".version"), "utf-8").trim();
1447
+ } catch {
1448
+ return null;
1449
+ }
1450
+ }
1451
+ function writeVersion(contextDir, version) {
1452
+ writeFileSync(join3(contextDir, ".version"), version, "utf-8");
1453
+ }
1454
+ function autoUpdateTemplates(projectDir) {
1455
+ const contextDir = join3(projectDir, ".opencode", "context");
1456
+ if (!existsSync2(contextDir))
1457
+ return [];
1458
+ const stored = getStoredVersion(projectDir);
1459
+ if (stored === PLUGIN_VERSION)
1460
+ return [];
1461
+ mkdirSync(join3(contextDir, "templates"), { recursive: true });
1462
+ const updated = [];
1463
+ for (const [filename, content] of Object.entries(TEMPLATE_FILES)) {
1464
+ const filePath = join3(contextDir, "templates", filename);
1465
+ try {
1466
+ const existing = readFileSync4(filePath, "utf-8");
1467
+ if (existing === content)
1468
+ continue;
1469
+ } catch {}
1470
+ writeFileSync(filePath, content, "utf-8");
1471
+ updated.push(`templates/${filename}`);
1472
+ }
1473
+ writeVersion(contextDir, PLUGIN_VERSION);
1276
1474
  return updated;
1277
1475
  }
1278
1476
 
@@ -1287,6 +1485,17 @@ var plugin = async ({ directory, client }) => {
1287
1485
  message: "Scaffold created at .opencode/context/"
1288
1486
  }
1289
1487
  });
1488
+ } else {
1489
+ const autoUpdated = autoUpdateTemplates(directory);
1490
+ if (autoUpdated.length > 0) {
1491
+ await client.app.log({
1492
+ body: {
1493
+ service: "context",
1494
+ level: "info",
1495
+ message: `Auto-updated ${autoUpdated.length} template(s): ${autoUpdated.join(", ")}`
1496
+ }
1497
+ });
1498
+ }
1290
1499
  }
1291
1500
  const config = loadConfig(directory);
1292
1501
  return {
@@ -1301,14 +1510,17 @@ var plugin = async ({ directory, client }) => {
1301
1510
  if (input.command !== "context-update")
1302
1511
  return;
1303
1512
  const updated = updateScaffold(directory);
1304
- if (updated.length === 0) {
1305
- output.parts = [{ type: "text", text: "All scaffold files are already up to date." }];
1306
- } else {
1307
- const lines = updated.map((f) => `- ${f}`).join(`
1308
- `);
1309
- output.parts = [{ type: "text", text: `Updated ${updated.length} file(s):
1310
- ${lines}` }];
1311
- }
1513
+ const text = updated.length === 0 ? "All scaffold files are already up to date." : `Updated ${updated.length} file(s):
1514
+ ${updated.map((f) => `- ${f}`).join(`
1515
+ `)}`;
1516
+ const now = Date.now();
1517
+ output.parts.splice(0, output.parts.length, {
1518
+ id: `context-update-${now}`,
1519
+ sessionID: input.sessionID,
1520
+ messageID: `context-update-msg-${now}`,
1521
+ type: "text",
1522
+ text
1523
+ });
1312
1524
  },
1313
1525
  "experimental.chat.messages.transform": async (_input, output) => {
1314
1526
  if (output.messages.length === 0)
@@ -1318,9 +1530,8 @@ ${lines}` }];
1318
1530
  return;
1319
1531
  const turnStartPath = join4(directory, config.prompts.turnStart ?? join4(DEFAULTS.promptDir, DEFAULTS.turnStartFile));
1320
1532
  const turnStart = readPromptFile(turnStartPath);
1321
- const knowledgeSources = [config.knowledge.dir, ...config.knowledge.sources].filter((s) => Boolean(s));
1322
- const entries = buildKnowledgeIndex(directory, knowledgeSources);
1323
- const indexContent = formatKnowledgeIndex(entries);
1533
+ const knowledgeIndex = buildKnowledgeIndexV2(directory, config.knowledge);
1534
+ const indexContent = knowledgeIndex.mode === "flat" ? formatKnowledgeIndex(knowledgeIndex.individualFiles) : formatDomainIndex(knowledgeIndex);
1324
1535
  const combinedContent = [turnStart, indexContent].filter(Boolean).join(`
1325
1536
 
1326
1537
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ksm0709/context",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Intent tools for Bun",
5
5
  "author": {
6
6
  "name": "TaehoKang",
package/src/version.ts ADDED
@@ -0,0 +1 @@
1
+ export const PLUGIN_VERSION = '0.0.6';