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
|
-
|
|
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
|
|
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
|
|
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 (!
|
|
142
|
+
if (!existsSync3(templatePath)) {
|
|
125
143
|
const __dirname2 = path.dirname(fileURLToPath(import.meta.url));
|
|
126
144
|
templatePath = join2(__dirname2, "..", "templates", templateName);
|
|
127
|
-
if (!
|
|
145
|
+
if (!existsSync3(templatePath)) {
|
|
128
146
|
templatePath = join2(__dirname2, "templates", templateName);
|
|
129
147
|
}
|
|
130
148
|
}
|
|
131
|
-
if (!
|
|
149
|
+
if (!existsSync3(templatePath)) {
|
|
132
150
|
throw new Error(`Template not found: ${templateName}`);
|
|
133
151
|
}
|
|
134
|
-
const content = await
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
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 (
|
|
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.
|