opencode-knowledge 0.5.1 → 0.6.0
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,36 +86,6 @@ 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
|
-
|
|
119
89
|
---
|
|
120
90
|
|
|
121
91
|
## Knowledge Package Format
|
|
@@ -145,13 +115,13 @@ Your knowledge content here...
|
|
|
145
115
|
|
|
146
116
|
### Frontmatter Fields
|
|
147
117
|
|
|
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)
|
|
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) |
|
|
155
125
|
|
|
156
126
|
### Dependency Loading
|
|
157
127
|
|
|
@@ -160,17 +130,20 @@ The `required_knowledge` field enables automatic dependency loading. When you lo
|
|
|
160
130
|
**Example:**
|
|
161
131
|
|
|
162
132
|
```markdown
|
|
163
|
-
<!-- vault/personal/blog-writing.md -->
|
|
164
|
-
|
|
133
|
+
## <!-- vault/personal/blog-writing.md -->
|
|
134
|
+
|
|
165
135
|
tags: [blog, writing]
|
|
166
136
|
description: Blog writing guidelines
|
|
167
137
|
category: personal
|
|
168
138
|
required_knowledge:
|
|
169
|
-
|
|
139
|
+
|
|
140
|
+
- personal/author-context
|
|
141
|
+
|
|
170
142
|
---
|
|
171
143
|
```
|
|
172
144
|
|
|
173
145
|
When AI loads `personal/blog-writing.md`, the plugin:
|
|
146
|
+
|
|
174
147
|
1. Detects the `required_knowledge` dependency
|
|
175
148
|
2. Automatically loads `personal/author-context.md` first
|
|
176
149
|
3. Then loads `personal/blog-writing.md`
|
|
@@ -185,7 +158,6 @@ This ensures the AI always has complete context without manual tracking. Depende
|
|
|
185
158
|
your-project/
|
|
186
159
|
└── .opencode/
|
|
187
160
|
└── knowledge/
|
|
188
|
-
├── settings.json
|
|
189
161
|
├── knowledge.json
|
|
190
162
|
├── vault/
|
|
191
163
|
│ ├── frontend/
|
package/dist/index.js
CHANGED
|
@@ -11,11 +11,7 @@ var __export = (target, all) => {
|
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
// src/index.ts
|
|
14
|
-
import { existsSync as
|
|
15
|
-
|
|
16
|
-
// src/lib/session-state.ts
|
|
17
|
-
import { readFile } from "fs/promises";
|
|
18
|
-
import { existsSync as existsSync2 } from "fs";
|
|
14
|
+
import { existsSync as existsSync5 } from "fs";
|
|
19
15
|
|
|
20
16
|
// src/lib/file-utils.ts
|
|
21
17
|
import { appendFileSync, readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
@@ -65,7 +61,6 @@ function loadSessionStateFromFile(sessionId) {
|
|
|
65
61
|
return null;
|
|
66
62
|
}
|
|
67
63
|
return {
|
|
68
|
-
role: state.role,
|
|
69
64
|
isFirstPrompt: state.isFirstPrompt,
|
|
70
65
|
loadedPackages: new Set(state.loadedPackages || []),
|
|
71
66
|
createdAt: new Date(state.createdAt),
|
|
@@ -75,7 +70,6 @@ function loadSessionStateFromFile(sessionId) {
|
|
|
75
70
|
function persistSessionState(sessionId, state) {
|
|
76
71
|
appendJsonl(SESSION_STATE_FILE, {
|
|
77
72
|
sessionId,
|
|
78
|
-
role: state.role,
|
|
79
73
|
isFirstPrompt: state.isFirstPrompt,
|
|
80
74
|
loadedPackages: Array.from(state.loadedPackages),
|
|
81
75
|
createdAt: state.createdAt.toISOString(),
|
|
@@ -89,19 +83,7 @@ async function createSessionState(sessionId) {
|
|
|
89
83
|
sessionStates.set(sessionId, existingState);
|
|
90
84
|
return;
|
|
91
85
|
}
|
|
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
|
-
}
|
|
103
86
|
const state = {
|
|
104
|
-
role,
|
|
105
87
|
isFirstPrompt: true,
|
|
106
88
|
loadedPackages: new Set,
|
|
107
89
|
createdAt: new Date,
|
|
@@ -124,8 +106,8 @@ function updateSessionState(sessionId, updates) {
|
|
|
124
106
|
}
|
|
125
107
|
|
|
126
108
|
// src/lib/template-renderer.ts
|
|
127
|
-
import { readFile
|
|
128
|
-
import { existsSync as
|
|
109
|
+
import { readFile } from "fs/promises";
|
|
110
|
+
import { existsSync as existsSync2 } from "fs";
|
|
129
111
|
import { join as join2 } from "path";
|
|
130
112
|
import path from "path";
|
|
131
113
|
import { fileURLToPath } from "url";
|
|
@@ -139,39 +121,19 @@ function renderTemplate(templateContent, variables) {
|
|
|
139
121
|
}
|
|
140
122
|
async function loadAndRenderTemplate(templateName, variables) {
|
|
141
123
|
let templatePath = join2(".opencode", "knowledge", "templates", templateName);
|
|
142
|
-
if (!
|
|
124
|
+
if (!existsSync2(templatePath)) {
|
|
143
125
|
const __dirname2 = path.dirname(fileURLToPath(import.meta.url));
|
|
144
126
|
templatePath = join2(__dirname2, "..", "templates", templateName);
|
|
145
|
-
if (!
|
|
127
|
+
if (!existsSync2(templatePath)) {
|
|
146
128
|
templatePath = join2(__dirname2, "templates", templateName);
|
|
147
129
|
}
|
|
148
130
|
}
|
|
149
|
-
if (!
|
|
131
|
+
if (!existsSync2(templatePath)) {
|
|
150
132
|
throw new Error(`Template not found: ${templateName}`);
|
|
151
133
|
}
|
|
152
|
-
const content = await
|
|
134
|
+
const content = await readFile(templatePath, "utf-8");
|
|
153
135
|
return renderTemplate(content, variables);
|
|
154
136
|
}
|
|
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
|
-
}
|
|
175
137
|
function getCorePackages() {
|
|
176
138
|
return {
|
|
177
139
|
tags: ["standards", "typescript", "testing", "patterns"],
|
|
@@ -180,7 +142,7 @@ function getCorePackages() {
|
|
|
180
142
|
}
|
|
181
143
|
|
|
182
144
|
// src/lib/knowledge-catalog.ts
|
|
183
|
-
import { readdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as
|
|
145
|
+
import { readdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3, statSync } from "fs";
|
|
184
146
|
import { join as join3, relative } from "path";
|
|
185
147
|
|
|
186
148
|
// src/lib/frontmatter-parser.ts
|
|
@@ -237,7 +199,7 @@ function parseFrontmatter(content) {
|
|
|
237
199
|
var VAULT_DIR = ".opencode/knowledge/vault";
|
|
238
200
|
var CATALOG_PATH = ".opencode/knowledge/knowledge.json";
|
|
239
201
|
function* scanVault(dir) {
|
|
240
|
-
if (!
|
|
202
|
+
if (!existsSync3(dir))
|
|
241
203
|
return;
|
|
242
204
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
243
205
|
for (const entry of entries) {
|
|
@@ -285,7 +247,7 @@ function saveCatalog(catalog) {
|
|
|
285
247
|
writeFileSync2(CATALOG_PATH, JSON.stringify(catalog, null, 2), "utf-8");
|
|
286
248
|
}
|
|
287
249
|
function loadCatalog() {
|
|
288
|
-
if (!
|
|
250
|
+
if (!existsSync3(CATALOG_PATH)) {
|
|
289
251
|
return null;
|
|
290
252
|
}
|
|
291
253
|
try {
|
|
@@ -12710,7 +12672,7 @@ _...and ${results.length - 10} more results_`;
|
|
|
12710
12672
|
});
|
|
12711
12673
|
|
|
12712
12674
|
// src/lib/tools/load.ts
|
|
12713
|
-
import { readFileSync as readFileSync3, existsSync as
|
|
12675
|
+
import { readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
|
|
12714
12676
|
import { join as join4 } from "path";
|
|
12715
12677
|
var VAULT_DIR2 = ".opencode/knowledge/vault";
|
|
12716
12678
|
function resolveDependencies(packagePath, vaultDir, loaded = new Set) {
|
|
@@ -12721,7 +12683,7 @@ function resolveDependencies(packagePath, vaultDir, loaded = new Set) {
|
|
|
12721
12683
|
const result = [];
|
|
12722
12684
|
const normalizedPath = packagePath.endsWith(".md") ? packagePath : `${packagePath}.md`;
|
|
12723
12685
|
const fullPath = join4(vaultDir, normalizedPath);
|
|
12724
|
-
if (!
|
|
12686
|
+
if (!existsSync4(fullPath)) {
|
|
12725
12687
|
return [];
|
|
12726
12688
|
}
|
|
12727
12689
|
try {
|
|
@@ -12761,7 +12723,7 @@ var knowledgeLoadTool = tool({
|
|
|
12761
12723
|
const packageArray = Array.from(allPackagePaths);
|
|
12762
12724
|
for (const packagePath of packageArray) {
|
|
12763
12725
|
const fullPath = join4(VAULT_DIR2, packagePath);
|
|
12764
|
-
if (!
|
|
12726
|
+
if (!existsSync4(fullPath)) {
|
|
12765
12727
|
failed.push(`\u26A0\uFE0F Package not found: ${packagePath}`);
|
|
12766
12728
|
continue;
|
|
12767
12729
|
}
|
|
@@ -12849,7 +12811,7 @@ var opencodeKnowledge = async () => {
|
|
|
12849
12811
|
try {
|
|
12850
12812
|
const state = getSessionState(input.sessionID);
|
|
12851
12813
|
if (state.isFirstPrompt) {
|
|
12852
|
-
const vaultExists =
|
|
12814
|
+
const vaultExists = existsSync5(".opencode/knowledge/vault");
|
|
12853
12815
|
if (vaultExists) {
|
|
12854
12816
|
const categoryTagMap = buildCategoryTagMap();
|
|
12855
12817
|
const formattedMap = formatCategoryTagMap(categoryTagMap);
|
|
@@ -12870,18 +12832,6 @@ var opencodeKnowledge = async () => {
|
|
|
12870
12832
|
messageID: input.messageID || ""
|
|
12871
12833
|
});
|
|
12872
12834
|
}
|
|
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
|
-
}
|
|
12885
12835
|
updateSessionState(input.sessionID, {
|
|
12886
12836
|
isFirstPrompt: false,
|
|
12887
12837
|
categoriesShown: vaultExists
|
|
@@ -12900,7 +12850,7 @@ ${personality}`,
|
|
|
12900
12850
|
}
|
|
12901
12851
|
clearJsonl("session-state.jsonl");
|
|
12902
12852
|
clearJsonl("knowledge-reads.jsonl");
|
|
12903
|
-
if (
|
|
12853
|
+
if (existsSync5(".opencode/knowledge/vault")) {
|
|
12904
12854
|
try {
|
|
12905
12855
|
const catalog = buildKnowledgeCatalog();
|
|
12906
12856
|
saveCatalog(catalog);
|
|
@@ -12908,7 +12858,6 @@ ${personality}`,
|
|
|
12908
12858
|
} catch (error45) {}
|
|
12909
12859
|
}
|
|
12910
12860
|
await createSessionState(sessionId);
|
|
12911
|
-
const state = getSessionState(sessionId);
|
|
12912
12861
|
}
|
|
12913
12862
|
} catch (error45) {}
|
|
12914
12863
|
}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
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.
|
|
@@ -1 +0,0 @@
|
|
|
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.
|