@mojoaar/opencode-mimir 0.20.0 → 0.22.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/dist/index.js CHANGED
@@ -12,7 +12,8 @@ export const NUDGE_HEADER = '## Memory check (Mimir)\n\n';
12
12
  */
13
13
  const NUDGE_EVERY = 20;
14
14
  const NUDGE = 'If this session settled a decision, a convention or a gotcha that is not in Mimir yet, ' +
15
- 'call memory_write now at one clear path, before the detail is compacted away.';
15
+ 'call memory_write now at one clear path, or hand the conversation over with mimir_learn, ' +
16
+ 'before the detail is compacted away.';
16
17
  /**
17
18
  * One injection point and two tools. The hook is what puts the project's
18
19
  * digest in front of the model before the first turn, so a session starts
package/dist/tools.js CHANGED
@@ -12,6 +12,8 @@ export async function registerTools(ctx, config, repo) {
12
12
  return ctx.tool.transform((editor) => {
13
13
  editor.add(digestTool(repo));
14
14
  editor.add(rememberTool(repo));
15
+ editor.add(searchTool(repo));
16
+ editor.add(learnTool(repo));
15
17
  });
16
18
  }
17
19
  const IDLE = { dispose: async () => { } };
@@ -70,7 +72,10 @@ function rememberTool(repo) {
70
72
  properties: {
71
73
  path: { type: 'string', description: 'Slash-separated memory path, e.g. deploy/rollback' },
72
74
  body: { type: 'string', description: 'What to remember, written so it reads without the conversation' },
73
- kind: { type: 'string', enum: ['note', 'fact', 'decision', 'gotcha', 'snippet'] },
75
+ kind: {
76
+ type: 'string',
77
+ enum: ['note', 'fact', 'decision', 'gotcha', 'procedure', 'snippet', 'import'],
78
+ },
74
79
  },
75
80
  required: ['path', 'body'],
76
81
  additionalProperties: false,
@@ -84,3 +89,57 @@ function rememberTool(repo) {
84
89
  },
85
90
  };
86
91
  }
92
+ function searchTool(repo) {
93
+ return {
94
+ name: 'mimir_search',
95
+ description: 'Search what this project has already settled in Mimir. Returns paths, titles and summaries; read a path with the MCP server if you need the body.',
96
+ input: {
97
+ type: 'object',
98
+ properties: {
99
+ query: { type: 'string', description: 'What to look for, in plain words' },
100
+ path_prefix: { type: 'string', description: 'Scope to a directory, e.g. auth' },
101
+ },
102
+ required: ['query'],
103
+ additionalProperties: false,
104
+ },
105
+ async execute(input) {
106
+ const result = await callTool('memory_search', { query: input?.query, path_prefix: input?.path_prefix, repo: repo ?? undefined }, { timeoutMs: 8000 });
107
+ if (!result.ok)
108
+ return { content: `Mimir is unavailable (${result.error}). Carry on without it.` };
109
+ const hits = result.result;
110
+ if (!Array.isArray(hits) || hits.length === 0)
111
+ return { content: 'Nothing matches that in Mimir.' };
112
+ return {
113
+ content: hits
114
+ .map((hit) => `${hit.path}\n${hit.summary ?? hit.title}`)
115
+ .join('\n\n'),
116
+ };
117
+ },
118
+ };
119
+ }
120
+ function learnTool(repo) {
121
+ return {
122
+ name: 'mimir_learn',
123
+ description: 'Hand the part of this conversation worth keeping to Mimir. The project model writes at most three memories, in the background, so this answers at once.',
124
+ input: {
125
+ type: 'object',
126
+ properties: {
127
+ transcript: { type: 'string', description: 'The conversation excerpt, verbatim. Up to 32 KB.' },
128
+ hint: { type: 'string', description: 'What to look for, e.g. "the deploy rollback"' },
129
+ },
130
+ required: ['transcript'],
131
+ additionalProperties: false,
132
+ },
133
+ async execute(input) {
134
+ const result = await callTool('memory_learn', { transcript: input?.transcript, hint: input?.hint, repo: repo ?? undefined }, { timeoutMs: 8000 });
135
+ if (!result.ok)
136
+ return { content: `Could not hand that over (${result.error}).` };
137
+ const queued = result.result;
138
+ return {
139
+ content: queued.queued
140
+ ? 'Queued. The model writes the memories in the background.'
141
+ : 'Mimir took no job: its queue is not running.',
142
+ };
143
+ },
144
+ };
145
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mojoaar/opencode-mimir",
3
- "version": "0.20.0",
3
+ "version": "0.22.0",
4
4
  "description": "Mimir long-term memory for OpenCode sessions",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/index.ts CHANGED
@@ -15,7 +15,8 @@ export const NUDGE_HEADER = '## Memory check (Mimir)\n\n'
15
15
  const NUDGE_EVERY = 20
16
16
  const NUDGE =
17
17
  'If this session settled a decision, a convention or a gotcha that is not in Mimir yet, ' +
18
- 'call memory_write now at one clear path, before the detail is compacted away.'
18
+ 'call memory_write now at one clear path, or hand the conversation over with mimir_learn, ' +
19
+ 'before the detail is compacted away.'
19
20
 
20
21
  /**
21
22
  * One injection point and two tools. The hook is what puts the project's
package/src/tools.ts CHANGED
@@ -36,6 +36,8 @@ export async function registerTools(
36
36
  return ctx.tool.transform((editor) => {
37
37
  editor.add(digestTool(repo))
38
38
  editor.add(rememberTool(repo))
39
+ editor.add(searchTool(repo))
40
+ editor.add(learnTool(repo))
39
41
  })
40
42
  }
41
43
 
@@ -104,7 +106,10 @@ function rememberTool(repo: string | null): ToolDefinition {
104
106
  properties: {
105
107
  path: { type: 'string', description: 'Slash-separated memory path, e.g. deploy/rollback' },
106
108
  body: { type: 'string', description: 'What to remember, written so it reads without the conversation' },
107
- kind: { type: 'string', enum: ['note', 'fact', 'decision', 'gotcha', 'snippet'] },
109
+ kind: {
110
+ type: 'string',
111
+ enum: ['note', 'fact', 'decision', 'gotcha', 'procedure', 'snippet', 'import'],
112
+ },
108
113
  },
109
114
  required: ['path', 'body'],
110
115
  additionalProperties: false,
@@ -121,3 +126,66 @@ function rememberTool(repo: string | null): ToolDefinition {
121
126
  },
122
127
  }
123
128
  }
129
+
130
+ function searchTool(repo: string | null): ToolDefinition {
131
+ return {
132
+ name: 'mimir_search',
133
+ description:
134
+ 'Search what this project has already settled in Mimir. Returns paths, titles and summaries; read a path with the MCP server if you need the body.',
135
+ input: {
136
+ type: 'object',
137
+ properties: {
138
+ query: { type: 'string', description: 'What to look for, in plain words' },
139
+ path_prefix: { type: 'string', description: 'Scope to a directory, e.g. auth' },
140
+ },
141
+ required: ['query'],
142
+ additionalProperties: false,
143
+ },
144
+ async execute(input) {
145
+ const result = await callTool(
146
+ 'memory_search',
147
+ { query: input?.query, path_prefix: input?.path_prefix, repo: repo ?? undefined },
148
+ { timeoutMs: 8000 },
149
+ )
150
+ if (!result.ok) return { content: `Mimir is unavailable (${result.error}). Carry on without it.` }
151
+ const hits = result.result as { path: string; title: string; summary: string | null }[]
152
+ if (!Array.isArray(hits) || hits.length === 0) return { content: 'Nothing matches that in Mimir.' }
153
+ return {
154
+ content: hits
155
+ .map((hit) => `${hit.path}\n${hit.summary ?? hit.title}`)
156
+ .join('\n\n'),
157
+ }
158
+ },
159
+ }
160
+ }
161
+
162
+ function learnTool(repo: string | null): ToolDefinition {
163
+ return {
164
+ name: 'mimir_learn',
165
+ description:
166
+ 'Hand the part of this conversation worth keeping to Mimir. The project model writes at most three memories, in the background, so this answers at once.',
167
+ input: {
168
+ type: 'object',
169
+ properties: {
170
+ transcript: { type: 'string', description: 'The conversation excerpt, verbatim. Up to 32 KB.' },
171
+ hint: { type: 'string', description: 'What to look for, e.g. "the deploy rollback"' },
172
+ },
173
+ required: ['transcript'],
174
+ additionalProperties: false,
175
+ },
176
+ async execute(input) {
177
+ const result = await callTool(
178
+ 'memory_learn',
179
+ { transcript: input?.transcript, hint: input?.hint, repo: repo ?? undefined },
180
+ { timeoutMs: 8000 },
181
+ )
182
+ if (!result.ok) return { content: `Could not hand that over (${result.error}).` }
183
+ const queued = result.result as { queued?: boolean }
184
+ return {
185
+ content: queued.queued
186
+ ? 'Queued. The model writes the memories in the background.'
187
+ : 'Mimir took no job: its queue is not running.',
188
+ }
189
+ },
190
+ }
191
+ }