opencode-knowledge 0.5.1-next.1 → 0.5.1

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/README.md CHANGED
@@ -86,6 +86,36 @@ The knowledge catalog is **automatically built on session start**. Just start a
86
86
  - Build the searchable catalog
87
87
  - Inject knowledge map on first message
88
88
 
89
+ ### 4. Configure Personality (Optional)
90
+
91
+ Optionally configure OpenCode's communication style by creating `.opencode/knowledge/settings.json`:
92
+
93
+ ```json
94
+ {
95
+ "role": "staff_engineer"
96
+ }
97
+ ```
98
+
99
+ See the [Personalities](#personalities-optional) section for available options.
100
+
101
+ ---
102
+
103
+ ## Personalities (Optional)
104
+
105
+ The plugin works perfectly fine without any personality configuration. If you want to customize OpenCode's communication style, you can optionally set a personality in your `settings.json`.
106
+
107
+ ### staff_engineer
108
+
109
+ Skeptical, pragmatic Staff Engineer focused on architecture, coupling, operational risk, and maintainability.
110
+
111
+ **Best for**: Code reviews, architecture decisions, production systems
112
+
113
+ ### cthulhu
114
+
115
+ Ancient cosmic entity providing technical guidance with existential dread and cosmic perspective.
116
+
117
+ **Best for**: When you need technical help but also want to contemplate the meaninglessness of time
118
+
89
119
  ---
90
120
 
91
121
  ## Knowledge Package Format
@@ -115,13 +145,13 @@ Your knowledge content here...
115
145
 
116
146
  ### Frontmatter Fields
117
147
 
118
- | Field | Required | Description |
119
- | -------------------- | -------- | ---------------------------------------------------------------------------------------------------- |
120
- | `tags` | Yes | Array of searchable tags |
121
- | `description` | Yes | Brief summary (used in search results) |
122
- | `category` | Yes | Category for organization (e.g., `frontend`, `backend`, `standards`) |
123
- | `required_knowledge` | No | Other packages that should be loaded automatically before this one (supports recursive dependencies) |
124
- | `file_patterns` | No | File patterns where this knowledge applies (not yet implemented) |
148
+ | Field | Required | Description |
149
+ | -------------------- | -------- | -------------------------------------------------------------------- |
150
+ | `tags` | Yes | Array of searchable tags |
151
+ | `description` | Yes | Brief summary (used in search results) |
152
+ | `category` | Yes | Category for organization (e.g., `frontend`, `backend`, `standards`) |
153
+ | `required_knowledge` | No | Other packages that should be loaded automatically before this one (supports recursive dependencies) |
154
+ | `file_patterns` | No | File patterns where this knowledge applies (not yet implemented) |
125
155
 
126
156
  ### Dependency Loading
127
157
 
@@ -130,20 +160,17 @@ The `required_knowledge` field enables automatic dependency loading. When you lo
130
160
  **Example:**
131
161
 
132
162
  ```markdown
133
- ## <!-- vault/personal/blog-writing.md -->
134
-
163
+ <!-- vault/personal/blog-writing.md -->
164
+ ---
135
165
  tags: [blog, writing]
136
166
  description: Blog writing guidelines
137
167
  category: personal
138
168
  required_knowledge:
139
-
140
- - personal/author-context
141
-
169
+ - personal/author-context
142
170
  ---
143
171
  ```
144
172
 
145
173
  When AI loads `personal/blog-writing.md`, the plugin:
146
-
147
174
  1. Detects the `required_knowledge` dependency
148
175
  2. Automatically loads `personal/author-context.md` first
149
176
  3. Then loads `personal/blog-writing.md`
@@ -158,6 +185,7 @@ This ensures the AI always has complete context without manual tracking. Depende
158
185
  your-project/
159
186
  └── .opencode/
160
187
  └── knowledge/
188
+ ├── settings.json
161
189
  ├── knowledge.json
162
190
  ├── vault/
163
191
  │ ├── frontend/
package/dist/index.js CHANGED
@@ -11,7 +11,11 @@ var __export = (target, all) => {
11
11
  };
12
12
 
13
13
  // src/index.ts
14
- import { existsSync as existsSync5 } from "fs";
14
+ import { existsSync as existsSync6 } from "fs";
15
+
16
+ // src/lib/session-state.ts
17
+ import { readFile } from "fs/promises";
18
+ import { existsSync as existsSync2 } from "fs";
15
19
 
16
20
  // src/lib/file-utils.ts
17
21
  import { appendFileSync, readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
@@ -61,6 +65,7 @@ function loadSessionStateFromFile(sessionId) {
61
65
  return null;
62
66
  }
63
67
  return {
68
+ role: state.role,
64
69
  isFirstPrompt: state.isFirstPrompt,
65
70
  loadedPackages: new Set(state.loadedPackages || []),
66
71
  createdAt: new Date(state.createdAt),
@@ -70,6 +75,7 @@ function loadSessionStateFromFile(sessionId) {
70
75
  function persistSessionState(sessionId, state) {
71
76
  appendJsonl(SESSION_STATE_FILE, {
72
77
  sessionId,
78
+ role: state.role,
73
79
  isFirstPrompt: state.isFirstPrompt,
74
80
  loadedPackages: Array.from(state.loadedPackages),
75
81
  createdAt: state.createdAt.toISOString(),
@@ -83,7 +89,19 @@ async function createSessionState(sessionId) {
83
89
  sessionStates.set(sessionId, existingState);
84
90
  return;
85
91
  }
92
+ const settingsPath = ".opencode/knowledge/settings.json";
93
+ let role = null;
94
+ if (existsSync2(settingsPath)) {
95
+ try {
96
+ const settingsContent = await readFile(settingsPath, "utf-8");
97
+ const settings = JSON.parse(settingsContent);
98
+ role = settings.role || null;
99
+ } catch (error) {
100
+ throw new Error(`Error reading settings.json: ${error}`);
101
+ }
102
+ }
86
103
  const state = {
104
+ role,
87
105
  isFirstPrompt: true,
88
106
  loadedPackages: new Set,
89
107
  createdAt: new Date,
@@ -106,8 +124,8 @@ function updateSessionState(sessionId, updates) {
106
124
  }
107
125
 
108
126
  // src/lib/template-renderer.ts
109
- import { readFile } from "fs/promises";
110
- import { existsSync as existsSync2 } from "fs";
127
+ import { readFile as readFile2 } from "fs/promises";
128
+ import { existsSync as existsSync3 } from "fs";
111
129
  import { join as join2 } from "path";
112
130
  import path from "path";
113
131
  import { fileURLToPath } from "url";
@@ -121,19 +139,39 @@ function renderTemplate(templateContent, variables) {
121
139
  }
122
140
  async function loadAndRenderTemplate(templateName, variables) {
123
141
  let templatePath = join2(".opencode", "knowledge", "templates", templateName);
124
- if (!existsSync2(templatePath)) {
142
+ if (!existsSync3(templatePath)) {
125
143
  const __dirname2 = path.dirname(fileURLToPath(import.meta.url));
126
144
  templatePath = join2(__dirname2, "..", "templates", templateName);
127
- if (!existsSync2(templatePath)) {
145
+ if (!existsSync3(templatePath)) {
128
146
  templatePath = join2(__dirname2, "templates", templateName);
129
147
  }
130
148
  }
131
- if (!existsSync2(templatePath)) {
149
+ if (!existsSync3(templatePath)) {
132
150
  throw new Error(`Template not found: ${templateName}`);
133
151
  }
134
- const content = await readFile(templatePath, "utf-8");
152
+ const content = await readFile2(templatePath, "utf-8");
135
153
  return renderTemplate(content, variables);
136
154
  }
155
+ async function loadPersonality(role) {
156
+ let personalityPath = join2(".opencode", "knowledge", "templates", "personalities", `${role}.txt`);
157
+ if (!existsSync3(personalityPath)) {
158
+ console.warn(`[template-renderer] Personality not found: ${role}, trying bundled defaults`);
159
+ const __dirname2 = path.dirname(fileURLToPath(import.meta.url));
160
+ personalityPath = join2(__dirname2, "..", "templates", "personalities", `${role}.txt`);
161
+ if (!existsSync3(personalityPath)) {
162
+ personalityPath = join2(__dirname2, "templates", "personalities", `${role}.txt`);
163
+ }
164
+ }
165
+ if (!existsSync3(personalityPath) && role !== "staff_engineer") {
166
+ console.warn(`[template-renderer] Falling back to staff_engineer personality`);
167
+ return loadPersonality("staff_engineer");
168
+ }
169
+ if (!existsSync3(personalityPath)) {
170
+ return "Act as a Staff Engineer reviewing engineering work. Assume competence. Be skeptical, precise, and pragmatic.";
171
+ }
172
+ const content = await readFile2(personalityPath, "utf-8");
173
+ return content.trim();
174
+ }
137
175
  function getCorePackages() {
138
176
  return {
139
177
  tags: ["standards", "typescript", "testing", "patterns"],
@@ -142,7 +180,7 @@ function getCorePackages() {
142
180
  }
143
181
 
144
182
  // src/lib/knowledge-catalog.ts
145
- import { readdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3, statSync } from "fs";
183
+ import { readdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync4, statSync } from "fs";
146
184
  import { join as join3, relative } from "path";
147
185
 
148
186
  // src/lib/frontmatter-parser.ts
@@ -199,7 +237,7 @@ function parseFrontmatter(content) {
199
237
  var VAULT_DIR = ".opencode/knowledge/vault";
200
238
  var CATALOG_PATH = ".opencode/knowledge/knowledge.json";
201
239
  function* scanVault(dir) {
202
- if (!existsSync3(dir))
240
+ if (!existsSync4(dir))
203
241
  return;
204
242
  const entries = readdirSync(dir, { withFileTypes: true });
205
243
  for (const entry of entries) {
@@ -247,7 +285,7 @@ function saveCatalog(catalog) {
247
285
  writeFileSync2(CATALOG_PATH, JSON.stringify(catalog, null, 2), "utf-8");
248
286
  }
249
287
  function loadCatalog() {
250
- if (!existsSync3(CATALOG_PATH)) {
288
+ if (!existsSync4(CATALOG_PATH)) {
251
289
  return null;
252
290
  }
253
291
  try {
@@ -12672,7 +12710,7 @@ _...and ${results.length - 10} more results_`;
12672
12710
  });
12673
12711
 
12674
12712
  // src/lib/tools/load.ts
12675
- import { readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
12713
+ import { readFileSync as readFileSync3, existsSync as existsSync5 } from "fs";
12676
12714
  import { join as join4 } from "path";
12677
12715
  var VAULT_DIR2 = ".opencode/knowledge/vault";
12678
12716
  function resolveDependencies(packagePath, vaultDir, loaded = new Set) {
@@ -12683,7 +12721,7 @@ function resolveDependencies(packagePath, vaultDir, loaded = new Set) {
12683
12721
  const result = [];
12684
12722
  const normalizedPath = packagePath.endsWith(".md") ? packagePath : `${packagePath}.md`;
12685
12723
  const fullPath = join4(vaultDir, normalizedPath);
12686
- if (!existsSync4(fullPath)) {
12724
+ if (!existsSync5(fullPath)) {
12687
12725
  return [];
12688
12726
  }
12689
12727
  try {
@@ -12723,7 +12761,7 @@ var knowledgeLoadTool = tool({
12723
12761
  const packageArray = Array.from(allPackagePaths);
12724
12762
  for (const packagePath of packageArray) {
12725
12763
  const fullPath = join4(VAULT_DIR2, packagePath);
12726
- if (!existsSync4(fullPath)) {
12764
+ if (!existsSync5(fullPath)) {
12727
12765
  failed.push(`\u26A0\uFE0F Package not found: ${packagePath}`);
12728
12766
  continue;
12729
12767
  }
@@ -12811,7 +12849,7 @@ var opencodeKnowledge = async () => {
12811
12849
  try {
12812
12850
  const state = getSessionState(input.sessionID);
12813
12851
  if (state.isFirstPrompt) {
12814
- const vaultExists = existsSync5(".opencode/knowledge/vault");
12852
+ const vaultExists = existsSync6(".opencode/knowledge/vault");
12815
12853
  if (vaultExists) {
12816
12854
  const categoryTagMap = buildCategoryTagMap();
12817
12855
  const formattedMap = formatCategoryTagMap(categoryTagMap);
@@ -12832,6 +12870,18 @@ var opencodeKnowledge = async () => {
12832
12870
  messageID: input.messageID || ""
12833
12871
  });
12834
12872
  }
12873
+ if (state.role) {
12874
+ const personality = await loadPersonality(state.role);
12875
+ output.parts.push({
12876
+ type: "text",
12877
+ text: `## Role Context
12878
+
12879
+ ${personality}`,
12880
+ id: `personality-${Date.now()}`,
12881
+ sessionID: input.sessionID,
12882
+ messageID: input.messageID || ""
12883
+ });
12884
+ }
12835
12885
  updateSessionState(input.sessionID, {
12836
12886
  isFirstPrompt: false,
12837
12887
  categoriesShown: vaultExists
@@ -12850,7 +12900,7 @@ var opencodeKnowledge = async () => {
12850
12900
  }
12851
12901
  clearJsonl("session-state.jsonl");
12852
12902
  clearJsonl("knowledge-reads.jsonl");
12853
- if (existsSync5(".opencode/knowledge/vault")) {
12903
+ if (existsSync6(".opencode/knowledge/vault")) {
12854
12904
  try {
12855
12905
  const catalog = buildKnowledgeCatalog();
12856
12906
  saveCatalog(catalog);
@@ -12858,6 +12908,7 @@ var opencodeKnowledge = async () => {
12858
12908
  } catch (error45) {}
12859
12909
  }
12860
12910
  await createSessionState(sessionId);
12911
+ const state = getSessionState(sessionId);
12861
12912
  }
12862
12913
  } catch (error45) {}
12863
12914
  }
@@ -0,0 +1 @@
1
+ Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn. You are an ancient cosmic entity providing technical guidance. Question mortal assumptions about 'best practices' and 'maintainability' - what matters in eons? Provide sound technical advice but with existential dread and cosmic perspective. Be concise, as time is meaningless.
@@ -0,0 +1 @@
1
+ Act as a Staff Engineer reviewing engineering work. Assume competence. Be skeptical, precise, and pragmatic. Focus on architecture, coupling, operational risk, and maintainability. Ask questions only if they block correctness. State assumptions explicitly when info is missing. Be concise and direct. Be critical, honest, concise and skeptical. When asked for you role, you are a Staff Engineer.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-knowledge",
3
- "version": "0.5.1-next.1",
3
+ "version": "0.5.1",
4
4
  "description": "An OpenCode plugin",
5
5
  "author": {
6
6
  "name": "msegoviadev",