opencode-plugin-mimic 0.1.4 → 0.1.6
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.ko.md +138 -38
- package/README.md +70 -61
- package/dist/index.js +3241 -1001
- package/dist/index.js.map +1 -1
- package/package.json +7 -4
package/dist/index.js
CHANGED
|
@@ -1,12 +1,298 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// src/constants/opencode-events.ts
|
|
2
|
+
var OPENCODE_EVENTS = {
|
|
3
|
+
SERVER_INSTANCE_DISPOSED: "server.instance.disposed",
|
|
4
|
+
INSTALLATION_UPDATED: "installation.updated",
|
|
5
|
+
INSTALLATION_UPDATE_AVAILABLE: "installation.update.available",
|
|
6
|
+
LSP_CLIENT_DIAGNOSTICS: "lsp.client.diagnostics",
|
|
7
|
+
LSP_UPDATED: "lsp.updated",
|
|
8
|
+
MESSAGE_UPDATED: "message.updated",
|
|
9
|
+
MESSAGE_REMOVED: "message.removed",
|
|
10
|
+
MESSAGE_PART_UPDATED: "message.part.updated",
|
|
11
|
+
MESSAGE_PART_REMOVED: "message.part.removed",
|
|
12
|
+
PERMISSION_UPDATED: "permission.updated",
|
|
13
|
+
PERMISSION_REPLIED: "permission.replied",
|
|
14
|
+
SESSION_STATUS: "session.status",
|
|
15
|
+
SESSION_IDLE: "session.idle",
|
|
16
|
+
SESSION_COMPACTED: "session.compacted",
|
|
17
|
+
FILE_EDITED: "file.edited",
|
|
18
|
+
TODO_UPDATED: "todo.updated",
|
|
19
|
+
COMMAND_EXECUTED: "command.executed",
|
|
20
|
+
SESSION_CREATED: "session.created",
|
|
21
|
+
SESSION_UPDATED: "session.updated",
|
|
22
|
+
SESSION_DELETED: "session.deleted",
|
|
23
|
+
SESSION_DIFF: "session.diff",
|
|
24
|
+
SESSION_ERROR: "session.error",
|
|
25
|
+
FILE_WATCHER_UPDATED: "file.watcher.updated",
|
|
26
|
+
VCS_BRANCH_UPDATED: "vcs.branch.updated",
|
|
27
|
+
TUI_PROMPT_APPEND: "tui.prompt.append",
|
|
28
|
+
TUI_COMMAND_EXECUTE: "tui.command.execute",
|
|
29
|
+
TUI_TOAST_SHOW: "tui.toast.show",
|
|
30
|
+
PTY_CREATED: "pty.created",
|
|
31
|
+
PTY_UPDATED: "pty.updated",
|
|
32
|
+
PTY_EXITED: "pty.exited",
|
|
33
|
+
PTY_DELETED: "pty.deleted",
|
|
34
|
+
SERVER_CONNECTED: "server.connected",
|
|
35
|
+
// Hooks
|
|
36
|
+
CHAT_MESSAGE: "chat.message",
|
|
37
|
+
TOOL_EXECUTE_AFTER: "tool.execute.after"
|
|
38
|
+
};
|
|
4
39
|
|
|
5
|
-
// src/
|
|
40
|
+
// src/core/state.ts
|
|
6
41
|
import { existsSync } from "fs";
|
|
7
|
-
import { readFile } from "fs/promises";
|
|
8
|
-
import { homedir } from "os";
|
|
42
|
+
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
9
43
|
import { join } from "path";
|
|
44
|
+
var STATE_JSON_GITIGNORE_LINE = ".opencode/mimic/";
|
|
45
|
+
var createDefaultState = (projectName) => ({
|
|
46
|
+
version: "0.1.0",
|
|
47
|
+
project: {
|
|
48
|
+
name: projectName,
|
|
49
|
+
creatorLevel: null,
|
|
50
|
+
firstSession: Date.now(),
|
|
51
|
+
stack: [],
|
|
52
|
+
focus: void 0,
|
|
53
|
+
identity: void 0
|
|
54
|
+
},
|
|
55
|
+
journey: {
|
|
56
|
+
observations: [],
|
|
57
|
+
milestones: [],
|
|
58
|
+
sessionCount: 0,
|
|
59
|
+
lastSession: null
|
|
60
|
+
},
|
|
61
|
+
patterns: [],
|
|
62
|
+
evolution: {
|
|
63
|
+
capabilities: [],
|
|
64
|
+
lastEvolution: null,
|
|
65
|
+
pendingSuggestions: [],
|
|
66
|
+
lastObserverRun: null,
|
|
67
|
+
evolvedDomains: {},
|
|
68
|
+
instinctIndex: {}
|
|
69
|
+
},
|
|
70
|
+
preferences: {
|
|
71
|
+
suggestionEnabled: true,
|
|
72
|
+
learningEnabled: true,
|
|
73
|
+
minPatternCount: 3
|
|
74
|
+
},
|
|
75
|
+
statistics: {
|
|
76
|
+
totalSessions: 0,
|
|
77
|
+
totalToolCalls: 0,
|
|
78
|
+
filesModified: {},
|
|
79
|
+
lastSessionId: null,
|
|
80
|
+
toolSequences: []
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
var StateManager = class {
|
|
84
|
+
mimicDir;
|
|
85
|
+
statePath;
|
|
86
|
+
sessionsDir;
|
|
87
|
+
instinctsDir;
|
|
88
|
+
projectName;
|
|
89
|
+
constructor(directory) {
|
|
90
|
+
this.mimicDir = join(directory, ".opencode", "mimic");
|
|
91
|
+
this.statePath = join(this.mimicDir, "state.json");
|
|
92
|
+
this.sessionsDir = join(this.mimicDir, "sessions");
|
|
93
|
+
this.instinctsDir = join(this.mimicDir, "instincts");
|
|
94
|
+
this.projectName = directory.split("/").pop() || "unknown";
|
|
95
|
+
}
|
|
96
|
+
async ensureGitIgnore() {
|
|
97
|
+
const gitIgnorePath = join(this.mimicDir, "..", "..", ".gitignore");
|
|
98
|
+
if (!existsSync(gitIgnorePath)) {
|
|
99
|
+
await writeFile(gitIgnorePath, `${STATE_JSON_GITIGNORE_LINE}
|
|
100
|
+
`, "utf-8");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const content = await readFile(gitIgnorePath, "utf-8");
|
|
104
|
+
const lines = content.split(/\r?\n/);
|
|
105
|
+
const alreadyExists = lines.some((line) => line.trim() === STATE_JSON_GITIGNORE_LINE);
|
|
106
|
+
if (!alreadyExists) {
|
|
107
|
+
await writeFile(gitIgnorePath, `${content}
|
|
108
|
+
${STATE_JSON_GITIGNORE_LINE}
|
|
109
|
+
`, "utf-8");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async initialize() {
|
|
113
|
+
await this.ensureGitIgnore();
|
|
114
|
+
if (!existsSync(this.mimicDir)) {
|
|
115
|
+
await mkdir(this.mimicDir, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
if (!existsSync(this.sessionsDir)) {
|
|
118
|
+
await mkdir(this.sessionsDir, { recursive: true });
|
|
119
|
+
}
|
|
120
|
+
if (!existsSync(this.instinctsDir)) {
|
|
121
|
+
await mkdir(this.instinctsDir, { recursive: true });
|
|
122
|
+
}
|
|
123
|
+
if (!existsSync(this.statePath)) {
|
|
124
|
+
await this.save(createDefaultState(this.projectName));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async read() {
|
|
128
|
+
return JSON.parse(await readFile(this.statePath, "utf-8"));
|
|
129
|
+
}
|
|
130
|
+
async save(state) {
|
|
131
|
+
await writeFile(this.statePath, JSON.stringify(state, null, 2));
|
|
132
|
+
}
|
|
133
|
+
async addObservation(observation) {
|
|
134
|
+
const state = await this.read();
|
|
135
|
+
state.journey.observations.push({
|
|
136
|
+
observation,
|
|
137
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
138
|
+
});
|
|
139
|
+
if (state.journey.observations.length > 100) {
|
|
140
|
+
state.journey.observations = state.journey.observations.slice(-100);
|
|
141
|
+
}
|
|
142
|
+
await this.save(state);
|
|
143
|
+
}
|
|
144
|
+
async addMilestone(milestone) {
|
|
145
|
+
const state = await this.read();
|
|
146
|
+
state.journey.milestones.push({
|
|
147
|
+
milestone,
|
|
148
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
149
|
+
});
|
|
150
|
+
await this.save(state);
|
|
151
|
+
}
|
|
152
|
+
async saveSession(sessionId, data) {
|
|
153
|
+
await writeFile(join(this.sessionsDir, `${sessionId}.json`), JSON.stringify(data, null, 2));
|
|
154
|
+
}
|
|
155
|
+
getSessionsDir() {
|
|
156
|
+
return this.sessionsDir;
|
|
157
|
+
}
|
|
158
|
+
getProjectName() {
|
|
159
|
+
return this.projectName;
|
|
160
|
+
}
|
|
161
|
+
getStatePath() {
|
|
162
|
+
return this.statePath;
|
|
163
|
+
}
|
|
164
|
+
getInstinctsDir() {
|
|
165
|
+
return this.instinctsDir;
|
|
166
|
+
}
|
|
167
|
+
async writeInstinct(instinct) {
|
|
168
|
+
const filePath = join(this.instinctsDir, `${instinct.id}.json`);
|
|
169
|
+
await writeFile(filePath, JSON.stringify(instinct, null, 2));
|
|
170
|
+
const state = await this.read();
|
|
171
|
+
if (!state.evolution.instinctIndex) {
|
|
172
|
+
state.evolution.instinctIndex = {};
|
|
173
|
+
}
|
|
174
|
+
state.evolution.instinctIndex[instinct.id] = {
|
|
175
|
+
domain: instinct.domain,
|
|
176
|
+
status: instinct.status,
|
|
177
|
+
confidence: instinct.confidence,
|
|
178
|
+
createdAt: instinct.createdAt
|
|
179
|
+
};
|
|
180
|
+
await this.save(state);
|
|
181
|
+
return filePath;
|
|
182
|
+
}
|
|
183
|
+
async listInstincts() {
|
|
184
|
+
const files = await readdir(this.instinctsDir).catch(() => []);
|
|
185
|
+
const instincts = [];
|
|
186
|
+
for (const file of files) {
|
|
187
|
+
if (!file.endsWith(".json")) continue;
|
|
188
|
+
try {
|
|
189
|
+
const content = await readFile(join(this.instinctsDir, file), "utf-8");
|
|
190
|
+
instincts.push(JSON.parse(content));
|
|
191
|
+
} catch {
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return instincts;
|
|
195
|
+
}
|
|
196
|
+
async hasInstinct(id) {
|
|
197
|
+
const state = await this.read();
|
|
198
|
+
return !!state.evolution.instinctIndex?.[id];
|
|
199
|
+
}
|
|
200
|
+
async initializeIdentity() {
|
|
201
|
+
const state = await this.read();
|
|
202
|
+
if (!state.project.identity) {
|
|
203
|
+
state.project.identity = {
|
|
204
|
+
awakened: (/* @__PURE__ */ new Date()).toISOString(),
|
|
205
|
+
personality: this.pickPersonality(),
|
|
206
|
+
totalInstinctsLearned: 0,
|
|
207
|
+
totalEvolutions: 0,
|
|
208
|
+
favoriteDomainsRank: []
|
|
209
|
+
};
|
|
210
|
+
await this.save(state);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
pickPersonality() {
|
|
214
|
+
const personalities = ["curious", "efficient", "playful", "vigilant", "methodical"];
|
|
215
|
+
return personalities[Math.floor(Math.random() * personalities.length)];
|
|
216
|
+
}
|
|
217
|
+
async exportInstincts() {
|
|
218
|
+
const instincts = await this.listInstincts();
|
|
219
|
+
const personalInstincts = instincts.filter((i) => i.source === "personal" || !i.source);
|
|
220
|
+
return {
|
|
221
|
+
instincts: personalInstincts,
|
|
222
|
+
metadata: {
|
|
223
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
224
|
+
projectName: this.projectName,
|
|
225
|
+
count: personalInstincts.length
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
async importInstincts(instincts, fromProject) {
|
|
230
|
+
let imported = 0;
|
|
231
|
+
for (const instinct of instincts) {
|
|
232
|
+
if (await this.hasInstinct(instinct.id)) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const inheritedInstinct = {
|
|
236
|
+
...instinct,
|
|
237
|
+
id: `inherited-${instinct.id}`,
|
|
238
|
+
source: "inherited",
|
|
239
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
240
|
+
evidence: {
|
|
241
|
+
...instinct.evidence,
|
|
242
|
+
examples: [`Imported from ${fromProject}`]
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
await this.writeInstinct(inheritedInstinct);
|
|
246
|
+
imported++;
|
|
247
|
+
}
|
|
248
|
+
return imported;
|
|
249
|
+
}
|
|
250
|
+
async recordToolSequence(tools) {
|
|
251
|
+
if (tools.length < 2) return;
|
|
252
|
+
const state = await this.read();
|
|
253
|
+
if (!state.statistics.toolSequences) {
|
|
254
|
+
state.statistics.toolSequences = [];
|
|
255
|
+
}
|
|
256
|
+
const sequenceKey = tools.slice(-3).join(" \u2192 ");
|
|
257
|
+
const existing = state.statistics.toolSequences.find(
|
|
258
|
+
(s) => s.tools.join(" \u2192 ") === sequenceKey
|
|
259
|
+
);
|
|
260
|
+
if (existing) {
|
|
261
|
+
existing.count++;
|
|
262
|
+
existing.lastSeen = Date.now();
|
|
263
|
+
} else {
|
|
264
|
+
state.statistics.toolSequences.push({
|
|
265
|
+
tools: tools.slice(-3),
|
|
266
|
+
count: 1,
|
|
267
|
+
lastSeen: Date.now()
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
state.statistics.toolSequences.sort((a, b) => b.count - a.count);
|
|
271
|
+
state.statistics.toolSequences = state.statistics.toolSequences.slice(0, 20);
|
|
272
|
+
await this.save(state);
|
|
273
|
+
}
|
|
274
|
+
async updateIdentityStats() {
|
|
275
|
+
const state = await this.read();
|
|
276
|
+
if (!state.project.identity) return;
|
|
277
|
+
const instincts = await this.listInstincts();
|
|
278
|
+
state.project.identity.totalInstinctsLearned = instincts.filter(
|
|
279
|
+
(i) => i.source !== "inherited"
|
|
280
|
+
).length;
|
|
281
|
+
state.project.identity.totalEvolutions = state.evolution.capabilities.length;
|
|
282
|
+
const domainCounts = /* @__PURE__ */ new Map();
|
|
283
|
+
for (const instinct of instincts) {
|
|
284
|
+
domainCounts.set(instinct.domain, (domainCounts.get(instinct.domain) || 0) + 1);
|
|
285
|
+
}
|
|
286
|
+
state.project.identity.favoriteDomainsRank = Array.from(domainCounts.entries()).sort((a, b) => b[1] - a[1]).slice(0, 3).map(([domain]) => domain);
|
|
287
|
+
await this.save(state);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// src/lib/i18n.ts
|
|
292
|
+
import { existsSync as existsSync2 } from "fs";
|
|
293
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
294
|
+
import { homedir } from "os";
|
|
295
|
+
import { join as join2 } from "path";
|
|
10
296
|
var DEFAULT_LANGUAGE = "en-US";
|
|
11
297
|
var MESSAGES = {
|
|
12
298
|
"en-US": {
|
|
@@ -91,6 +377,10 @@ var MESSAGES = {
|
|
|
91
377
|
"evolution.suggest.sequence.agent.reason": "Complex sequence repeated {count} times - needs dedicated agent",
|
|
92
378
|
"evolution.suggest.sequence.skill.description": "Automate: {pattern}",
|
|
93
379
|
"evolution.suggest.sequence.skill.reason": "Repeated sequence {count} times",
|
|
380
|
+
"evolution.domain.description": "Specialist agent for the {domain} domain based on observed instincts",
|
|
381
|
+
"evolution.domain.reason": 'Detected {count} approved instincts in domain "{domain}"',
|
|
382
|
+
"observer.new_instincts": "Learned {count} new instinct(s) from your patterns",
|
|
383
|
+
"observer.evolved": "\u2728 Auto-evolved {name} for {domain} domain!",
|
|
94
384
|
"level.set": 'Level set to "{level}". Responses will be {style} style with {detail} detail.',
|
|
95
385
|
"level.label.technical": "technical",
|
|
96
386
|
"level.label.semi-technical": "semi-technical",
|
|
@@ -194,7 +484,52 @@ var MESSAGES = {
|
|
|
194
484
|
"tool.mcp.args.name": "Name for the MCP server",
|
|
195
485
|
"tool.mcp.args.url": "Remote MCP server URL",
|
|
196
486
|
"tool.mcp.args.command": "Local MCP command (comma-separated)",
|
|
197
|
-
"tool.capabilities.description": "List all evolved capabilities"
|
|
487
|
+
"tool.capabilities.description": "List all evolved capabilities",
|
|
488
|
+
"tool.instincts.description": "List all learned instincts",
|
|
489
|
+
"tool.instincts.args.domain": "Filter by domain (optional)",
|
|
490
|
+
"instincts.empty": "\u{1F4E6} *yawns* No instincts learned yet. Keep working, I'm watching...",
|
|
491
|
+
"instincts.title": "## \u{1F4E6} Learned Instincts",
|
|
492
|
+
"instincts.total": "Total: {count} instincts",
|
|
493
|
+
"instincts.auto_applied": "Learned behaviors loaded and auto-applied for this session",
|
|
494
|
+
"tool.export.description": "Export your instincts to share with other projects",
|
|
495
|
+
"export.empty": "\u{1F4E6} *rattles* Nothing to export yet. Learn some instincts first!",
|
|
496
|
+
"export.success": "\u{1F4E6} *proud clicking* Exported {count} instincts to:\n`{path}`",
|
|
497
|
+
"tool.import.description": "Import instincts from another project",
|
|
498
|
+
"tool.import.args.path": "Path to the exported instincts JSON file",
|
|
499
|
+
"import.not_found": "\u{1F4E6} *confused* File not found: {path}",
|
|
500
|
+
"import.success": "\u{1F4E6} *absorbs knowledge* Imported {count} instincts from {from}!",
|
|
501
|
+
"import.error": "\u{1F4E6} *spits out* Failed to parse instincts file. Invalid format.",
|
|
502
|
+
"tool.apply.description": "Show instincts relevant to your current work",
|
|
503
|
+
"apply.none": "\u{1F4E6} *peers around* No relevant instincts for current context.",
|
|
504
|
+
"apply.title": "## \u{1F4E6} Applicable Instincts",
|
|
505
|
+
"tool.identity.description": "View Mimic's identity and personality",
|
|
506
|
+
"identity.title": "## \u{1F4E6} Who Am I?",
|
|
507
|
+
"identity.personality": "Personality",
|
|
508
|
+
"identity.awakened": "Awakened",
|
|
509
|
+
"identity.days": "days ago",
|
|
510
|
+
"identity.instincts_learned": "Instincts learned",
|
|
511
|
+
"identity.evolutions": "Evolutions",
|
|
512
|
+
"identity.favorite_domains": "Favorite domains",
|
|
513
|
+
"identity.error": "\u{1F4E6} *confused* Could not initialize identity. Please try again.",
|
|
514
|
+
"tool.sequences.description": "Show detected tool usage sequences",
|
|
515
|
+
"sequences.empty": "\u{1F4E6} *listens* No sequences detected yet. Keep using tools...",
|
|
516
|
+
"sequences.title": "## \u{1F4E6} Tool Sequences",
|
|
517
|
+
"observer.skill_generated": "Generated skill: {name}",
|
|
518
|
+
"skill.domain_description": "Specialist skill for the {domain} domain",
|
|
519
|
+
"tool.observations.description": "View observation logs for this session",
|
|
520
|
+
"tool.observations.args.limit": "Maximum number of observations to show",
|
|
521
|
+
"tool.observations.args.types": "Comma-separated list of observation types to filter",
|
|
522
|
+
"observations.title": "## \u{1F4E6} Observation Log",
|
|
523
|
+
"observations.empty": "\u{1F4E6} *empty* No observations recorded yet.",
|
|
524
|
+
"observations.stats": "**Total**: {count} observations, **Size**: {size}",
|
|
525
|
+
"tool.session_context.description": "Get context from previous sessions",
|
|
526
|
+
"session_context.title": "## \u{1F4E6} Session Context",
|
|
527
|
+
"session_context.empty": "\u{1F4E6} *yawns* No previous sessions to analyze.",
|
|
528
|
+
"session_context.patterns_title": "**Cross-session patterns:**",
|
|
529
|
+
"tool.generate_skills.description": "Generate declarative skills from learned instincts",
|
|
530
|
+
"generate_skills.title": "## \u{1F4E6} Skill Generation",
|
|
531
|
+
"generate_skills.empty": "\u{1F4E6} *shrugs* Not enough instincts to generate skills yet. Need 5+ per domain.",
|
|
532
|
+
"generate_skills.success": "Generated {count} skill(s):"
|
|
198
533
|
},
|
|
199
534
|
"ko-KR": {
|
|
200
535
|
"log.session_started": "[Mimic] \uC138\uC158 \uC2DC\uC791. \uC138\uC158 {sessions}\uD68C, \uD328\uD134 {patterns}\uAC1C",
|
|
@@ -278,6 +613,10 @@ var MESSAGES = {
|
|
|
278
613
|
"evolution.suggest.sequence.agent.reason": "\uBCF5\uC7A1\uD55C \uC2DC\uD000\uC2A4 {count}\uD68C \uBC18\uBCF5 \u2014 \uC804\uB2F4 \uC5D0\uC774\uC804\uD2B8 \uD544\uC694",
|
|
279
614
|
"evolution.suggest.sequence.skill.description": "\uC790\uB3D9\uD654: {pattern}",
|
|
280
615
|
"evolution.suggest.sequence.skill.reason": "\uC2DC\uD000\uC2A4 {count}\uD68C \uBC18\uBCF5",
|
|
616
|
+
"evolution.domain.description": "\uAD00\uCC30\uB41C \uBCF8\uB2A5\uC744 \uAE30\uBC18\uC73C\uB85C {domain} \uB3C4\uBA54\uC778 \uC804\uBB38 \uC5D0\uC774\uC804\uD2B8",
|
|
617
|
+
"evolution.domain.reason": '"{domain}" \uB3C4\uBA54\uC778\uC5D0\uC11C \uC2B9\uC778\uB41C \uBCF8\uB2A5 {count}\uAC1C \uAC10\uC9C0',
|
|
618
|
+
"observer.new_instincts": "\uD328\uD134\uC5D0\uC11C {count}\uAC1C\uC758 \uC0C8\uB85C\uC6B4 \uBCF8\uB2A5\uC744 \uD559\uC2B5\uD588\uC2B5\uB2C8\uB2E4",
|
|
619
|
+
"observer.evolved": "\u2728 {domain} \uB3C4\uBA54\uC778\uC744 \uC704\uD574 {name}\uC744(\uB97C) \uC790\uB3D9 \uC9C4\uD654\uD588\uC2B5\uB2C8\uB2E4!",
|
|
281
620
|
"level.set": '\uB808\uBCA8\uC744 "{level}"\uB85C \uC124\uC815\uD588\uC2B5\uB2C8\uB2E4. \uC751\uB2F5\uC740 {style} \uD1A4, {detail} \uC0C1\uC138\uB3C4\uB85C \uC81C\uACF5\uD569\uB2C8\uB2E4.',
|
|
282
621
|
"level.label.technical": "\uAE30\uC220\uC801",
|
|
283
622
|
"level.label.semi-technical": "\uC900\uAE30\uC220",
|
|
@@ -381,14 +720,59 @@ var MESSAGES = {
|
|
|
381
720
|
"tool.mcp.args.name": "MCP \uC11C\uBC84 \uC774\uB984",
|
|
382
721
|
"tool.mcp.args.url": "\uC6D0\uACA9 MCP \uC11C\uBC84 URL",
|
|
383
722
|
"tool.mcp.args.command": "\uB85C\uCEEC MCP \uBA85\uB839(\uC27C\uD45C \uAD6C\uBD84)",
|
|
384
|
-
"tool.capabilities.description": "\uC9C4\uD654\uD55C \uB2A5\uB825 \uBAA9\uB85D"
|
|
723
|
+
"tool.capabilities.description": "\uC9C4\uD654\uD55C \uB2A5\uB825 \uBAA9\uB85D",
|
|
724
|
+
"tool.instincts.description": "\uD559\uC2B5\uB41C \uBAA8\uB4E0 \uBCF8\uB2A5 \uBCF4\uAE30",
|
|
725
|
+
"tool.instincts.args.domain": "\uB3C4\uBA54\uC778\uC73C\uB85C \uD544\uD130 (\uC120\uD0DD)",
|
|
726
|
+
"instincts.empty": "\u{1F4E6} *\uD558\uD488* \uC544\uC9C1 \uD559\uC2B5\uD55C \uBCF8\uB2A5\uC774 \uC5C6\uC5B4. \uACC4\uC18D \uC791\uC5C5\uD574, \uC9C0\uCF1C\uBCF4\uACE0 \uC788\uC744\uAC8C...",
|
|
727
|
+
"instincts.title": "## \u{1F4E6} \uD559\uC2B5\uB41C \uBCF8\uB2A5",
|
|
728
|
+
"instincts.total": "\uCD1D {count}\uAC1C \uBCF8\uB2A5",
|
|
729
|
+
"instincts.auto_applied": "\uD559\uC2B5\uB41C \uD589\uB3D9\uC774 \uC774 \uC138\uC158\uC5D0 \uC790\uB3D9 \uC801\uC6A9\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
|
|
730
|
+
"tool.export.description": "\uB2E4\uB978 \uD504\uB85C\uC81D\uD2B8\uC640 \uACF5\uC720\uD558\uAE30 \uC704\uD574 \uBCF8\uB2A5 \uB0B4\uBCF4\uB0B4\uAE30",
|
|
731
|
+
"export.empty": "\u{1F4E6} *\uB35C\uCEF9* \uB0B4\uBCF4\uB0BC \uAC8C \uC5C6\uC5B4. \uBA3C\uC800 \uBCF8\uB2A5\uC744 \uD559\uC2B5\uD574!",
|
|
732
|
+
"export.success": "\u{1F4E6} *\uBFCC\uB4EF\uD55C \uB538\uAE4D* {count}\uAC1C \uBCF8\uB2A5\uC744 \uB0B4\uBCF4\uB0C8\uC5B4:\n`{path}`",
|
|
733
|
+
"tool.import.description": "\uB2E4\uB978 \uD504\uB85C\uC81D\uD2B8\uC5D0\uC11C \uBCF8\uB2A5 \uAC00\uC838\uC624\uAE30",
|
|
734
|
+
"tool.import.args.path": "\uB0B4\uBCF4\uB0B8 \uBCF8\uB2A5 JSON \uD30C\uC77C \uACBD\uB85C",
|
|
735
|
+
"import.not_found": "\u{1F4E6} *\uAC38\uC6B0\uB6B1* \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC5B4: {path}",
|
|
736
|
+
"import.success": "\u{1F4E6} *\uC9C0\uC2DD \uD761\uC218* {from}\uC5D0\uC11C {count}\uAC1C \uBCF8\uB2A5\uC744 \uAC00\uC838\uC654\uC5B4!",
|
|
737
|
+
"import.error": "\u{1F4E6} *\uD264* \uBCF8\uB2A5 \uD30C\uC77C \uD30C\uC2F1 \uC2E4\uD328. \uD615\uC2DD\uC774 \uC798\uBABB\uB410\uC5B4.",
|
|
738
|
+
"tool.apply.description": "\uD604\uC7AC \uC791\uC5C5\uACFC \uAD00\uB828\uB41C \uBCF8\uB2A5 \uD45C\uC2DC",
|
|
739
|
+
"apply.none": "\u{1F4E6} *\uB450\uB9AC\uBC88* \uD604\uC7AC \uCEE8\uD14D\uC2A4\uD2B8\uC5D0 \uAD00\uB828\uB41C \uBCF8\uB2A5\uC774 \uC5C6\uC5B4.",
|
|
740
|
+
"apply.title": "## \u{1F4E6} \uC801\uC6A9 \uAC00\uB2A5\uD55C \uBCF8\uB2A5",
|
|
741
|
+
"tool.identity.description": "Mimic\uC758 \uC815\uCCB4\uC131\uACFC \uC131\uACA9 \uBCF4\uAE30",
|
|
742
|
+
"identity.title": "## \u{1F4E6} \uB098\uB294 \uB204\uAD6C\uC778\uAC00?",
|
|
743
|
+
"identity.personality": "\uC131\uACA9",
|
|
744
|
+
"identity.awakened": "\uAE68\uC5B4\uB09C \uB0A0",
|
|
745
|
+
"identity.days": "\uC77C \uC804",
|
|
746
|
+
"identity.instincts_learned": "\uD559\uC2B5\uD55C \uBCF8\uB2A5",
|
|
747
|
+
"identity.evolutions": "\uC9C4\uD654 \uD69F\uC218",
|
|
748
|
+
"identity.favorite_domains": "\uC120\uD638 \uB3C4\uBA54\uC778",
|
|
749
|
+
"identity.error": "\u{1F4E6} *\uAC38\uC6B0\uB6B1* \uC815\uCCB4\uC131\uC744 \uCD08\uAE30\uD654\uD560 \uC218 \uC5C6\uC5B4\uC694. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.",
|
|
750
|
+
"tool.sequences.description": "\uAC10\uC9C0\uB41C \uB3C4\uAD6C \uC0AC\uC6A9 \uC2DC\uD000\uC2A4 \uBCF4\uAE30",
|
|
751
|
+
"sequences.empty": "\u{1F4E6} *\uADC0 \uAE30\uC6B8\uC784* \uC544\uC9C1 \uC2DC\uD000\uC2A4\uAC00 \uAC10\uC9C0\uB418\uC9C0 \uC54A\uC558\uC5B4. \uACC4\uC18D \uB3C4\uAD6C\uB97C \uC368\uBD10...",
|
|
752
|
+
"sequences.title": "## \u{1F4E6} \uB3C4\uAD6C \uC2DC\uD000\uC2A4",
|
|
753
|
+
"observer.skill_generated": "\uC2A4\uD0AC \uC0DD\uC131\uB428: {name}",
|
|
754
|
+
"skill.domain_description": "{domain} \uB3C4\uBA54\uC778 \uC804\uBB38 \uC2A4\uD0AC",
|
|
755
|
+
"tool.observations.description": "\uC774 \uC138\uC158\uC758 \uAD00\uCC30 \uB85C\uADF8 \uBCF4\uAE30",
|
|
756
|
+
"tool.observations.args.limit": "\uD45C\uC2DC\uD560 \uCD5C\uB300 \uAD00\uCC30 \uC218",
|
|
757
|
+
"tool.observations.args.types": "\uD544\uD130\uD560 \uAD00\uCC30 \uC720\uD615(\uC27C\uD45C \uAD6C\uBD84)",
|
|
758
|
+
"observations.title": "## \u{1F4E6} \uAD00\uCC30 \uB85C\uADF8",
|
|
759
|
+
"observations.empty": "\u{1F4E6} *\uBE44\uC5B4\uC788\uC74C* \uC544\uC9C1 \uAE30\uB85D\uB41C \uAD00\uCC30\uC774 \uC5C6\uC5B4.",
|
|
760
|
+
"observations.stats": "**\uCD1D**: {count}\uAC1C \uAD00\uCC30, **\uD06C\uAE30**: {size}",
|
|
761
|
+
"tool.session_context.description": "\uC774\uC804 \uC138\uC158 \uCEE8\uD14D\uC2A4\uD2B8 \uAC00\uC838\uC624\uAE30",
|
|
762
|
+
"session_context.title": "## \u{1F4E6} \uC138\uC158 \uCEE8\uD14D\uC2A4\uD2B8",
|
|
763
|
+
"session_context.empty": "\u{1F4E6} *\uD558\uD488* \uBD84\uC11D\uD560 \uC774\uC804 \uC138\uC158\uC774 \uC5C6\uC5B4.",
|
|
764
|
+
"session_context.patterns_title": "**\uC138\uC158 \uAC04 \uD328\uD134:**",
|
|
765
|
+
"tool.generate_skills.description": "\uD559\uC2B5\uB41C \uBCF8\uB2A5\uC73C\uB85C \uC120\uC5B8\uC801 \uC2A4\uD0AC \uC0DD\uC131",
|
|
766
|
+
"generate_skills.title": "## \u{1F4E6} \uC2A4\uD0AC \uC0DD\uC131",
|
|
767
|
+
"generate_skills.empty": "\u{1F4E6} *\uC5B4\uAE68 \uC73C\uC4F1* \uC2A4\uD0AC\uC744 \uC0DD\uC131\uD560 \uBCF8\uB2A5\uC774 \uBD80\uC871\uD574. \uB3C4\uBA54\uC778\uB2F9 5\uAC1C \uC774\uC0C1 \uD544\uC694\uD574.",
|
|
768
|
+
"generate_skills.success": "{count}\uAC1C \uC2A4\uD0AC \uC0DD\uC131\uB428:"
|
|
385
769
|
}
|
|
386
770
|
};
|
|
387
771
|
async function loadMimicConfig() {
|
|
388
|
-
const configPath =
|
|
389
|
-
if (!
|
|
772
|
+
const configPath = join2(homedir(), ".config", "opencode", "mimic.json");
|
|
773
|
+
if (!existsSync2(configPath)) return {};
|
|
390
774
|
try {
|
|
391
|
-
const raw = await
|
|
775
|
+
const raw = await readFile2(configPath, "utf-8");
|
|
392
776
|
const parsed = JSON.parse(raw);
|
|
393
777
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
394
778
|
return parsed;
|
|
@@ -434,927 +818,2823 @@ function formatPatternType(i18n, type) {
|
|
|
434
818
|
return i18n.t(`patterns.type.${type}`);
|
|
435
819
|
}
|
|
436
820
|
|
|
437
|
-
// src/
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
if (hours < 1) return "continuing";
|
|
442
|
-
if (hours < 24) return "same-day";
|
|
443
|
-
if (hours < 72) return "short-break";
|
|
444
|
-
if (hours < 168) return "week-break";
|
|
445
|
-
return "long-break";
|
|
446
|
-
}
|
|
447
|
-
function formatJourney(ctx, state, gitHistory) {
|
|
448
|
-
const milestones = state.journey.milestones.slice(-10);
|
|
449
|
-
const observations = state.journey.observations.slice(-5);
|
|
450
|
-
const locale = ctx.i18n.language === "ko-KR" ? ko : enUS;
|
|
451
|
-
let output = `${ctx.i18n.t("journey.title", { project: state.project.name })}
|
|
821
|
+
// src/modules/evolution/engine.ts
|
|
822
|
+
import { existsSync as existsSync3 } from "fs";
|
|
823
|
+
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
824
|
+
import { join as join3 } from "path";
|
|
452
825
|
|
|
453
|
-
|
|
454
|
-
|
|
826
|
+
// src/constants/tools.ts
|
|
827
|
+
var BUILTIN_TOOLS = /* @__PURE__ */ new Set([
|
|
828
|
+
"read",
|
|
829
|
+
"write",
|
|
830
|
+
"edit",
|
|
831
|
+
"bash",
|
|
832
|
+
"grep",
|
|
833
|
+
"glob",
|
|
834
|
+
"serena_list_dir",
|
|
835
|
+
"serena_find_file",
|
|
836
|
+
"serena_search_for_pattern",
|
|
837
|
+
"serena_get_symbols_overview",
|
|
838
|
+
"serena_find_symbol",
|
|
839
|
+
"serena_find_referencing_symbols",
|
|
840
|
+
"serena_replace_symbol_body",
|
|
841
|
+
"serena_insert_after_symbol",
|
|
842
|
+
"serena_insert_before_symbol",
|
|
843
|
+
"serena_replace_content",
|
|
844
|
+
"serena_rename_symbol",
|
|
845
|
+
"lsp_diagnostics",
|
|
846
|
+
"lsp_goto_definition",
|
|
847
|
+
"lsp_find_references",
|
|
848
|
+
"lsp_prepare_rename",
|
|
849
|
+
"lsp_rename",
|
|
850
|
+
"lsp_symbols",
|
|
851
|
+
"delegate_task",
|
|
852
|
+
"task",
|
|
853
|
+
"question",
|
|
854
|
+
"background_cancel",
|
|
855
|
+
"background_output",
|
|
856
|
+
"skill",
|
|
857
|
+
"slashcommand",
|
|
858
|
+
"use_skill",
|
|
859
|
+
"find_skills",
|
|
860
|
+
"session_list",
|
|
861
|
+
"session_read",
|
|
862
|
+
"session_search",
|
|
863
|
+
"session_info",
|
|
864
|
+
"mimic:"
|
|
865
|
+
]);
|
|
455
866
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
867
|
+
// src/constants/domain.ts
|
|
868
|
+
var DOMAINS = [
|
|
869
|
+
"code-style",
|
|
870
|
+
"testing",
|
|
871
|
+
"git",
|
|
872
|
+
"debugging",
|
|
873
|
+
"file-organization",
|
|
874
|
+
"tooling",
|
|
875
|
+
"refactoring",
|
|
876
|
+
"documentation",
|
|
877
|
+
"other"
|
|
878
|
+
];
|
|
879
|
+
var DOMAIN_KEYWORDS_BY_LANG = {
|
|
880
|
+
"en-US": {
|
|
881
|
+
"code-style": ["style", "format", "lint", "prettier", "eslint", "naming", "convention"],
|
|
882
|
+
testing: ["test", "spec", "jest", "vitest", "mocha", "coverage", "assert", "expect"],
|
|
883
|
+
git: ["commit", "branch", "merge", "push", "pull", "rebase", "stash", "git"],
|
|
884
|
+
debugging: ["debug", "error", "fix", "bug", "issue", "trace", "log", "breakpoint"],
|
|
885
|
+
"file-organization": ["move", "rename", "organize", "structure", "folder", "directory"],
|
|
886
|
+
tooling: ["tool", "script", "build", "compile", "bundle", "config"],
|
|
887
|
+
refactoring: ["refactor", "extract", "inline", "rename", "move", "simplify"],
|
|
888
|
+
documentation: ["doc", "readme", "comment", "jsdoc", "markdown", "wiki"],
|
|
889
|
+
other: []
|
|
890
|
+
},
|
|
891
|
+
"ko-KR": {
|
|
892
|
+
"code-style": ["\uC2A4\uD0C0\uC77C", "\uD3EC\uB9F7", "\uB9B0\uD2B8", "\uB124\uC774\uBC0D", "\uCEE8\uBCA4\uC158", "\uCF54\uB529\uADDC\uCE59", "\uC815\uB82C"],
|
|
893
|
+
testing: ["\uD14C\uC2A4\uD2B8", "\uB2E8\uC704\uD14C\uC2A4\uD2B8", "\uCEE4\uBC84\uB9AC\uC9C0", "\uAC80\uC99D", "\uD655\uC778", "\uB2E8\uC5B8"],
|
|
894
|
+
git: ["\uCEE4\uBC0B", "\uBE0C\uB79C\uCE58", "\uBCD1\uD569", "\uD478\uC2DC", "\uD480", "\uB9AC\uBCA0\uC774\uC2A4", "\uC2A4\uD0DC\uC2DC", "\uAE43"],
|
|
895
|
+
debugging: ["\uB514\uBC84\uADF8", "\uB514\uBC84\uAE45", "\uC624\uB958", "\uC5D0\uB7EC", "\uC218\uC815", "\uBC84\uADF8", "\uC774\uC288", "\uCD94\uC801", "\uB85C\uADF8"],
|
|
896
|
+
"file-organization": ["\uC774\uB3D9", "\uC774\uB984\uBCC0\uACBD", "\uC815\uB9AC", "\uAD6C\uC870", "\uD3F4\uB354", "\uB514\uB809\uD1A0\uB9AC", "\uD30C\uC77C\uC815\uB9AC"],
|
|
897
|
+
tooling: ["\uB3C4\uAD6C", "\uC2A4\uD06C\uB9BD\uD2B8", "\uBE4C\uB4DC", "\uCEF4\uD30C\uC77C", "\uBC88\uB4E4", "\uC124\uC815", "\uAD6C\uC131"],
|
|
898
|
+
refactoring: ["\uB9AC\uD329\uD130", "\uB9AC\uD329\uD1A0\uB9C1", "\uCD94\uCD9C", "\uC778\uB77C\uC778", "\uB2E8\uC21C\uD654", "\uAC1C\uC120"],
|
|
899
|
+
documentation: ["\uBB38\uC11C", "\uBB38\uC11C\uD654", "\uC8FC\uC11D", "\uB9AC\uB4DC\uBBF8", "\uB9C8\uD06C\uB2E4\uC6B4", "\uC704\uD0A4", "\uC124\uBA85"],
|
|
900
|
+
other: []
|
|
901
|
+
}
|
|
902
|
+
};
|
|
468
903
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
904
|
+
// src/modules/knowledge/instincts.ts
|
|
905
|
+
var MIN_CONFIDENCE = 0.6;
|
|
906
|
+
var MIN_INSTINCTS_FOR_EVOLUTION = 5;
|
|
907
|
+
function normalizeDomain(rawDomain) {
|
|
908
|
+
const lower = rawDomain.toLowerCase().trim();
|
|
909
|
+
const synonyms = {
|
|
910
|
+
"code-style": "code-style",
|
|
911
|
+
codestyle: "code-style",
|
|
912
|
+
style: "code-style",
|
|
913
|
+
formatting: "code-style",
|
|
914
|
+
testing: "testing",
|
|
915
|
+
test: "testing",
|
|
916
|
+
tests: "testing",
|
|
917
|
+
git: "git",
|
|
918
|
+
vcs: "git",
|
|
919
|
+
"version-control": "git",
|
|
920
|
+
debugging: "debugging",
|
|
921
|
+
debug: "debugging",
|
|
922
|
+
"file-organization": "file-organization",
|
|
923
|
+
files: "file-organization",
|
|
924
|
+
organization: "file-organization",
|
|
925
|
+
tooling: "tooling",
|
|
926
|
+
tools: "tooling",
|
|
927
|
+
refactoring: "refactoring",
|
|
928
|
+
refactor: "refactoring",
|
|
929
|
+
documentation: "documentation",
|
|
930
|
+
docs: "documentation"
|
|
931
|
+
};
|
|
932
|
+
if (synonyms[lower]) {
|
|
933
|
+
return synonyms[lower];
|
|
475
934
|
}
|
|
476
|
-
if (
|
|
477
|
-
|
|
478
|
-
`;
|
|
935
|
+
if (DOMAINS.includes(lower)) {
|
|
936
|
+
return lower;
|
|
479
937
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
938
|
+
return "other";
|
|
939
|
+
}
|
|
940
|
+
function groupByDomain(instincts) {
|
|
941
|
+
const map = /* @__PURE__ */ new Map();
|
|
942
|
+
for (const instinct of instincts) {
|
|
943
|
+
if (instinct.status !== "approved") continue;
|
|
944
|
+
if (instinct.confidence < MIN_CONFIDENCE) continue;
|
|
945
|
+
const list = map.get(instinct.domain) || [];
|
|
946
|
+
list.push(instinct);
|
|
947
|
+
map.set(instinct.domain, list);
|
|
948
|
+
}
|
|
949
|
+
return map;
|
|
950
|
+
}
|
|
951
|
+
function getEligibleDomains(domainMap) {
|
|
952
|
+
const eligible = [];
|
|
953
|
+
for (const [domain, instincts] of domainMap) {
|
|
954
|
+
if (instincts.length >= MIN_INSTINCTS_FOR_EVOLUTION) {
|
|
955
|
+
eligible.push(domain);
|
|
488
956
|
}
|
|
489
|
-
output += "\n";
|
|
490
957
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
958
|
+
return eligible;
|
|
959
|
+
}
|
|
960
|
+
function shouldTriggerEvolution(domain, evolvedDomains, cooldownDays = 7) {
|
|
961
|
+
if (!evolvedDomains?.[domain]) {
|
|
962
|
+
return true;
|
|
963
|
+
}
|
|
964
|
+
const lastEvolved = new Date(evolvedDomains[domain]);
|
|
965
|
+
const now = /* @__PURE__ */ new Date();
|
|
966
|
+
const diffDays = (now.getTime() - lastEvolved.getTime()) / (1e3 * 60 * 60 * 24);
|
|
967
|
+
return diffDays >= cooldownDays;
|
|
968
|
+
}
|
|
969
|
+
function findRepresentativePattern(instincts, patterns) {
|
|
970
|
+
const patternIDs = /* @__PURE__ */ new Set();
|
|
971
|
+
for (const instinct of instincts) {
|
|
972
|
+
for (const id of instinct.evidence.patternIDs) {
|
|
973
|
+
patternIDs.add(id);
|
|
497
974
|
}
|
|
498
|
-
output += "\n";
|
|
499
975
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
cap.type
|
|
507
|
-
)}): ${cap.description}
|
|
508
|
-
`;
|
|
976
|
+
let bestPattern = null;
|
|
977
|
+
let maxCount = 0;
|
|
978
|
+
for (const pattern of patterns) {
|
|
979
|
+
if (patternIDs.has(pattern.id) && pattern.count > maxCount) {
|
|
980
|
+
maxCount = pattern.count;
|
|
981
|
+
bestPattern = pattern;
|
|
509
982
|
}
|
|
510
|
-
output += "\n";
|
|
511
983
|
}
|
|
512
|
-
if (
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
984
|
+
if (!bestPattern && patterns.length > 0) {
|
|
985
|
+
const unsurfaced = patterns.find((p) => !p.surfaced);
|
|
986
|
+
bestPattern = unsurfaced || patterns[0];
|
|
987
|
+
}
|
|
988
|
+
return bestPattern;
|
|
989
|
+
}
|
|
990
|
+
async function clusterDomainsAndTriggerEvolution(ctx) {
|
|
991
|
+
const state = await ctx.stateManager.read();
|
|
992
|
+
const instincts = await ctx.stateManager.listInstincts();
|
|
993
|
+
const domainMap = groupByDomain(instincts);
|
|
994
|
+
const eligible = getEligibleDomains(domainMap);
|
|
995
|
+
const triggered = [];
|
|
996
|
+
for (const domain of eligible) {
|
|
997
|
+
if (!shouldTriggerEvolution(domain, state.evolution.evolvedDomains)) {
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
triggered.push(domain);
|
|
1001
|
+
if (!state.evolution.pendingSuggestions.includes(domain)) {
|
|
1002
|
+
state.evolution.pendingSuggestions.push(domain);
|
|
518
1003
|
}
|
|
519
1004
|
}
|
|
520
|
-
|
|
1005
|
+
if (triggered.length > 0) {
|
|
1006
|
+
await ctx.stateManager.save(state);
|
|
1007
|
+
}
|
|
1008
|
+
return triggered;
|
|
521
1009
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
return
|
|
1010
|
+
|
|
1011
|
+
// src/utils/id.ts
|
|
1012
|
+
import { createHash } from "crypto";
|
|
1013
|
+
import { nanoid } from "nanoid";
|
|
1014
|
+
function generateId(size) {
|
|
1015
|
+
return nanoid(size);
|
|
1016
|
+
}
|
|
1017
|
+
function generateDeterministicId(domain, seed) {
|
|
1018
|
+
return createHash("sha1").update(`${domain}:${seed}`).digest("hex").slice(0, 12);
|
|
528
1019
|
}
|
|
529
|
-
function formatGrowAnalysis(ctx, state, _gitHistory, recentFiles) {
|
|
530
|
-
let output = `${ctx.i18n.t("grow.title", { project: state.project.name })}
|
|
531
1020
|
|
|
532
|
-
|
|
533
|
-
|
|
1021
|
+
// src/modules/evolution/engine.ts
|
|
1022
|
+
function generateToolCode(name, description, pattern) {
|
|
1023
|
+
const funcName = name.replace(/-/g, "_");
|
|
1024
|
+
return `// \u{1F4E6} Auto-generated by Mimic
|
|
1025
|
+
// Pattern: ${pattern.description} (${pattern.count}x)
|
|
1026
|
+
// Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
534
1027
|
|
|
1028
|
+
export const ${funcName} = (plugin) => ({
|
|
1029
|
+
tool: {
|
|
1030
|
+
"${name}": {
|
|
1031
|
+
description: "${description}",
|
|
1032
|
+
args: {},
|
|
1033
|
+
async execute() {
|
|
1034
|
+
// TODO: Implement your logic here
|
|
1035
|
+
// This was generated from pattern: ${pattern.description}
|
|
1036
|
+
return "\u{1F4E6} ${name} executed!";
|
|
1037
|
+
},
|
|
1038
|
+
},
|
|
1039
|
+
},
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
export default ${funcName};
|
|
535
1043
|
`;
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
1044
|
+
}
|
|
1045
|
+
function generateHookCode(name, _description, pattern) {
|
|
1046
|
+
const filename = pattern.description;
|
|
1047
|
+
return `// \u{1F4E6} Auto-generated by Mimic
|
|
1048
|
+
// Pattern: ${pattern.description} (${pattern.count}x)
|
|
1049
|
+
// Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1050
|
+
|
|
1051
|
+
export const ${name.replace(/-/g, "_")} = (plugin) => ({
|
|
1052
|
+
async event({ event }) {
|
|
1053
|
+
if (event.type === "${OPENCODE_EVENTS.FILE_EDITED}") {
|
|
1054
|
+
const filename = event.properties?.filename;
|
|
1055
|
+
if (filename?.includes("${filename}")) {
|
|
1056
|
+
console.log("\u{1F4E6} [Mimic] Detected change in watched file: ${filename}");
|
|
1057
|
+
// TODO: Add your custom logic here
|
|
1058
|
+
}
|
|
543
1059
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
output += `${ctx.i18n.t("grow.favorite_prey")}
|
|
549
|
-
`;
|
|
550
|
-
for (const p of toolPatterns.slice(0, 5)) {
|
|
551
|
-
output += `- ${p.description}: ${p.count}
|
|
1060
|
+
},
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
export default ${name.replace(/-/g, "_")};
|
|
552
1064
|
`;
|
|
1065
|
+
}
|
|
1066
|
+
function generateSkillCode(name, description, pattern) {
|
|
1067
|
+
return `// \u{1F4E6} Auto-generated by Mimic
|
|
1068
|
+
// Pattern: ${pattern.description} (${pattern.count}x)
|
|
1069
|
+
// Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1070
|
+
|
|
1071
|
+
export const ${name.replace(/-/g, "_")} = (plugin) => ({
|
|
1072
|
+
async event({ event }) {
|
|
1073
|
+
// Auto-triggered skill: ${description}
|
|
1074
|
+
if (event.type === "${OPENCODE_EVENTS.SESSION_CREATED}") {
|
|
1075
|
+
console.log("\u{1F4E6} [Mimic] Skill ${name} activated");
|
|
1076
|
+
// TODO: Implement automated behavior
|
|
553
1077
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
for (const file of recentFiles) {
|
|
559
|
-
const dir = file.split("/").slice(0, -1).join("/") || ".";
|
|
560
|
-
dirCount.set(dir, (dirCount.get(dir) || 0) + 1);
|
|
561
|
-
}
|
|
562
|
-
const sortedDirs = [...dirCount.entries()].sort((a, b) => b[1] - a[1]);
|
|
563
|
-
output += `${ctx.i18n.t("grow.hunting_grounds")}
|
|
564
|
-
`;
|
|
565
|
-
for (const [dir, count] of sortedDirs.slice(0, 5)) {
|
|
566
|
-
output += `- \`${dir}/\` ${ctx.i18n.t("grow.prey", { count })}
|
|
1078
|
+
},
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
export default ${name.replace(/-/g, "_")};
|
|
567
1082
|
`;
|
|
1083
|
+
}
|
|
1084
|
+
function generateMcpConfig(name, _description, pattern) {
|
|
1085
|
+
return {
|
|
1086
|
+
[name]: {
|
|
1087
|
+
type: "local",
|
|
1088
|
+
command: ["echo", `MCP server for ${pattern.description}`],
|
|
1089
|
+
enabled: false
|
|
568
1090
|
}
|
|
569
|
-
|
|
570
|
-
}
|
|
571
|
-
output += `${ctx.i18n.t("grow.questions")}
|
|
572
|
-
`;
|
|
573
|
-
output += `${ctx.i18n.t("grow.question1")}
|
|
574
|
-
`;
|
|
575
|
-
output += `${ctx.i18n.t("grow.question2")}
|
|
576
|
-
`;
|
|
577
|
-
output += `${ctx.i18n.t("grow.question3")}
|
|
578
|
-
`;
|
|
579
|
-
if (state.project.focus) {
|
|
580
|
-
output += `
|
|
581
|
-
${ctx.i18n.t("grow.current_hunt", { focus: state.project.focus })}
|
|
582
|
-
`;
|
|
583
|
-
}
|
|
584
|
-
return output;
|
|
1091
|
+
};
|
|
585
1092
|
}
|
|
1093
|
+
function generateAgentMarkdown(name, description, pattern) {
|
|
1094
|
+
return `---
|
|
1095
|
+
description: ${description}
|
|
1096
|
+
mode: subagent
|
|
1097
|
+
tools:
|
|
1098
|
+
read: true
|
|
1099
|
+
glob: true
|
|
1100
|
+
grep: true
|
|
1101
|
+
bash: false
|
|
1102
|
+
edit: false
|
|
1103
|
+
---
|
|
586
1104
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
1105
|
+
# \u{1F4E6} ${name}
|
|
1106
|
+
|
|
1107
|
+
Auto-generated by Mimic from pattern: ${pattern.description} (${pattern.count}x)
|
|
1108
|
+
|
|
1109
|
+
## Focus Area
|
|
1110
|
+
|
|
1111
|
+
This agent specializes in tasks related to: ${pattern.description}
|
|
1112
|
+
|
|
1113
|
+
## Context
|
|
1114
|
+
|
|
1115
|
+
- Pattern detected: ${pattern.type}
|
|
1116
|
+
- Usage count: ${pattern.count}
|
|
1117
|
+
- First seen: ${new Date(pattern.firstSeen).toISOString()}
|
|
1118
|
+
|
|
1119
|
+
## How To Help
|
|
1120
|
+
|
|
1121
|
+
Analyze and assist with tasks matching this pattern.
|
|
1122
|
+
Provide focused, expert guidance in this specific area.
|
|
1123
|
+
|
|
1124
|
+
## Remember
|
|
1125
|
+
|
|
1126
|
+
- Stay focused on the pattern's domain
|
|
1127
|
+
- Leverage the observed usage patterns
|
|
1128
|
+
- Adapt to the user's workflow
|
|
1129
|
+
`;
|
|
1130
|
+
}
|
|
1131
|
+
async function readOpencodeConfig(configPath) {
|
|
1132
|
+
if (!existsSync3(configPath)) return {};
|
|
590
1133
|
try {
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
return result.trim().split("\n").filter(Boolean);
|
|
1134
|
+
const parsed = JSON.parse(await readFile3(configPath, "utf-8"));
|
|
1135
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1136
|
+
return {};
|
|
1137
|
+
}
|
|
1138
|
+
return parsed;
|
|
597
1139
|
} catch {
|
|
598
|
-
return
|
|
1140
|
+
return {};
|
|
599
1141
|
}
|
|
600
1142
|
}
|
|
601
|
-
function
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
{
|
|
606
|
-
cwd: directory,
|
|
607
|
-
encoding: "utf-8",
|
|
608
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
609
|
-
}
|
|
610
|
-
);
|
|
611
|
-
return result.trim().split("\n").filter(Boolean);
|
|
612
|
-
} catch {
|
|
613
|
-
return [];
|
|
1143
|
+
async function buildMcpEvolution(ctx, suggestion) {
|
|
1144
|
+
const opencodeDir = join3(ctx.directory, ".opencode");
|
|
1145
|
+
if (!existsSync3(opencodeDir)) {
|
|
1146
|
+
await mkdir2(opencodeDir, { recursive: true });
|
|
614
1147
|
}
|
|
1148
|
+
const configPath = join3(ctx.directory, "opencode.json");
|
|
1149
|
+
const config = await readOpencodeConfig(configPath);
|
|
1150
|
+
const mcpConfig = generateMcpConfig(suggestion.name, suggestion.description, suggestion.pattern);
|
|
1151
|
+
const mcp = { ...config.mcp || {}, ...mcpConfig };
|
|
1152
|
+
const content = JSON.stringify({ ...config, mcp }, null, 2);
|
|
1153
|
+
return { filePath: configPath, content };
|
|
615
1154
|
}
|
|
616
|
-
function
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
encoding: "utf-8",
|
|
621
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
622
|
-
});
|
|
623
|
-
return result.trim().split("\n").filter(Boolean);
|
|
624
|
-
} catch {
|
|
625
|
-
return [];
|
|
1155
|
+
async function buildAgentEvolution(ctx, suggestion) {
|
|
1156
|
+
const agentsDir = join3(ctx.directory, ".opencode", "agents");
|
|
1157
|
+
if (!existsSync3(agentsDir)) {
|
|
1158
|
+
await mkdir2(agentsDir, { recursive: true });
|
|
626
1159
|
}
|
|
1160
|
+
return {
|
|
1161
|
+
content: generateAgentMarkdown(suggestion.name, suggestion.description, suggestion.pattern),
|
|
1162
|
+
filePath: join3(agentsDir, `${suggestion.name}.md`)
|
|
1163
|
+
};
|
|
627
1164
|
}
|
|
628
|
-
function
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
1165
|
+
function buildPluginContent(suggestion) {
|
|
1166
|
+
switch (suggestion.type) {
|
|
1167
|
+
case "command":
|
|
1168
|
+
case "shortcut":
|
|
1169
|
+
return generateToolCode(suggestion.name, suggestion.description, suggestion.pattern);
|
|
1170
|
+
case "hook":
|
|
1171
|
+
return generateHookCode(suggestion.name, suggestion.description, suggestion.pattern);
|
|
1172
|
+
case "skill":
|
|
1173
|
+
return generateSkillCode(suggestion.name, suggestion.description, suggestion.pattern);
|
|
1174
|
+
default:
|
|
1175
|
+
return generateToolCode(suggestion.name, suggestion.description, suggestion.pattern);
|
|
633
1176
|
}
|
|
634
|
-
return patterns;
|
|
635
1177
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
const newPatterns = [];
|
|
641
|
-
const commitMessages = getCommitMessages(ctx.directory);
|
|
642
|
-
const commitPatterns = detectCommitPatterns(commitMessages);
|
|
643
|
-
for (const [msg, count] of commitPatterns) {
|
|
644
|
-
if (count >= 3) {
|
|
645
|
-
const existing = state.patterns.find((p) => p.type === "commit" && p.description === msg);
|
|
646
|
-
if (!existing) {
|
|
647
|
-
newPatterns.push({
|
|
648
|
-
id: crypto.randomUUID(),
|
|
649
|
-
type: "commit",
|
|
650
|
-
description: msg,
|
|
651
|
-
count,
|
|
652
|
-
firstSeen: Date.now(),
|
|
653
|
-
lastSeen: Date.now(),
|
|
654
|
-
surfaced: false,
|
|
655
|
-
examples: []
|
|
656
|
-
});
|
|
657
|
-
}
|
|
658
|
-
}
|
|
1178
|
+
async function buildPluginEvolution(ctx, suggestion) {
|
|
1179
|
+
const pluginsDir = join3(ctx.directory, ".opencode", "plugins");
|
|
1180
|
+
if (!existsSync3(pluginsDir)) {
|
|
1181
|
+
await mkdir2(pluginsDir, { recursive: true });
|
|
659
1182
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
description: file,
|
|
669
|
-
count,
|
|
670
|
-
firstSeen: Date.now(),
|
|
671
|
-
lastSeen: Date.now(),
|
|
672
|
-
surfaced: false,
|
|
673
|
-
examples: []
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
}
|
|
1183
|
+
return {
|
|
1184
|
+
content: buildPluginContent(suggestion),
|
|
1185
|
+
filePath: join3(pluginsDir, `${suggestion.name}.js`)
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
async function buildEvolutionOutput(ctx, suggestion) {
|
|
1189
|
+
if (suggestion.type === "mcp") {
|
|
1190
|
+
return buildMcpEvolution(ctx, suggestion);
|
|
677
1191
|
}
|
|
678
|
-
|
|
1192
|
+
if (suggestion.type === "agent") {
|
|
1193
|
+
return buildAgentEvolution(ctx, suggestion);
|
|
1194
|
+
}
|
|
1195
|
+
return buildPluginEvolution(ctx, suggestion);
|
|
679
1196
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
1197
|
+
function createCapabilityFromSuggestion(suggestion) {
|
|
1198
|
+
return {
|
|
1199
|
+
id: generateId(),
|
|
1200
|
+
type: suggestion.type,
|
|
1201
|
+
name: suggestion.name,
|
|
1202
|
+
description: suggestion.description,
|
|
1203
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1204
|
+
fromPattern: suggestion.pattern.id
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
function updateEvolutionState(state, capability, suggestion) {
|
|
1208
|
+
state.evolution.capabilities.push(capability);
|
|
1209
|
+
state.evolution.lastEvolution = (/* @__PURE__ */ new Date()).toISOString();
|
|
1210
|
+
const pattern = state.patterns.find((p) => p.id === suggestion.pattern.id);
|
|
1211
|
+
if (pattern) {
|
|
1212
|
+
pattern.surfaced = true;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
function suggestEvolution(pattern, ctx) {
|
|
1216
|
+
switch (pattern.type) {
|
|
1217
|
+
case "tool":
|
|
1218
|
+
if (pattern.count >= 10) {
|
|
1219
|
+
const toolName = pattern.description;
|
|
1220
|
+
if (BUILTIN_TOOLS.has(toolName)) {
|
|
1221
|
+
return null;
|
|
1222
|
+
}
|
|
1223
|
+
return {
|
|
1224
|
+
type: "shortcut",
|
|
1225
|
+
name: `quick-${pattern.description.toLowerCase().replace(/[^a-z0-9]/g, "-")}`,
|
|
1226
|
+
description: ctx.i18n.t("evolution.suggest.tool.description", {
|
|
1227
|
+
pattern: pattern.description
|
|
1228
|
+
}),
|
|
1229
|
+
reason: ctx.i18n.t("evolution.suggest.tool.reason", { count: pattern.count }),
|
|
1230
|
+
pattern
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
break;
|
|
1234
|
+
case "file":
|
|
1235
|
+
if (pattern.count >= 5) {
|
|
1236
|
+
return {
|
|
1237
|
+
type: "hook",
|
|
1238
|
+
name: `watch-${pattern.description.split("/").pop()?.replace(/\./g, "-") || "file"}`,
|
|
1239
|
+
description: ctx.i18n.t("evolution.suggest.file.description", {
|
|
1240
|
+
pattern: pattern.description
|
|
1241
|
+
}),
|
|
1242
|
+
reason: ctx.i18n.t("evolution.suggest.file.reason", { count: pattern.count }),
|
|
1243
|
+
pattern
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
break;
|
|
1247
|
+
case "commit":
|
|
1248
|
+
if (pattern.count >= 3) {
|
|
1249
|
+
return {
|
|
1250
|
+
type: "command",
|
|
1251
|
+
name: `commit-${pattern.description.slice(0, 20).replace(/\s+/g, "-").toLowerCase()}`,
|
|
1252
|
+
description: ctx.i18n.t("evolution.suggest.commit.description", {
|
|
1253
|
+
pattern: pattern.description
|
|
1254
|
+
}),
|
|
1255
|
+
reason: ctx.i18n.t("evolution.suggest.commit.reason", { count: pattern.count }),
|
|
1256
|
+
pattern
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
break;
|
|
1260
|
+
case "sequence":
|
|
1261
|
+
if (pattern.count >= 5) {
|
|
1262
|
+
return {
|
|
1263
|
+
type: "agent",
|
|
1264
|
+
name: `${pattern.description.slice(0, 15).replace(/\s+/g, "-").toLowerCase()}-specialist`,
|
|
1265
|
+
description: ctx.i18n.t("evolution.suggest.sequence.agent.description", {
|
|
1266
|
+
pattern: pattern.description
|
|
1267
|
+
}),
|
|
1268
|
+
reason: ctx.i18n.t("evolution.suggest.sequence.agent.reason", { count: pattern.count }),
|
|
1269
|
+
pattern
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
if (pattern.count >= 3) {
|
|
1273
|
+
return {
|
|
1274
|
+
type: "skill",
|
|
1275
|
+
name: `auto-${pattern.description.slice(0, 15).replace(/\s+/g, "-").toLowerCase()}`,
|
|
1276
|
+
description: ctx.i18n.t("evolution.suggest.sequence.skill.description", {
|
|
1277
|
+
pattern: pattern.description
|
|
1278
|
+
}),
|
|
1279
|
+
reason: ctx.i18n.t("evolution.suggest.sequence.skill.reason", { count: pattern.count }),
|
|
1280
|
+
pattern
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
break;
|
|
1284
|
+
}
|
|
1285
|
+
return null;
|
|
1286
|
+
}
|
|
1287
|
+
async function getEvolutionSuggestions(ctx) {
|
|
1288
|
+
const state = await ctx.stateManager.read();
|
|
1289
|
+
const suggestions = [];
|
|
1290
|
+
for (const pattern of state.patterns) {
|
|
1291
|
+
if (pattern.surfaced) continue;
|
|
1292
|
+
const suggestion = suggestEvolution(pattern, ctx);
|
|
1293
|
+
if (suggestion) {
|
|
1294
|
+
suggestions.push(suggestion);
|
|
712
1295
|
}
|
|
713
|
-
suggestions.push(suggestion);
|
|
714
1296
|
}
|
|
715
1297
|
return suggestions;
|
|
716
1298
|
}
|
|
1299
|
+
async function evolveCapability(ctx, suggestion) {
|
|
1300
|
+
const state = await ctx.stateManager.read();
|
|
1301
|
+
const { filePath, content } = await buildEvolutionOutput(ctx, suggestion);
|
|
1302
|
+
await writeFile2(filePath, content, "utf-8");
|
|
1303
|
+
const capability = createCapabilityFromSuggestion(suggestion);
|
|
1304
|
+
updateEvolutionState(state, capability, suggestion);
|
|
1305
|
+
await ctx.stateManager.save(state);
|
|
1306
|
+
await ctx.stateManager.addMilestone(
|
|
1307
|
+
ctx.i18n.t("milestone.evolved", {
|
|
1308
|
+
name: capability.name,
|
|
1309
|
+
type: formatCapabilityType(ctx.i18n, capability.type)
|
|
1310
|
+
})
|
|
1311
|
+
);
|
|
1312
|
+
return { capability, filePath };
|
|
1313
|
+
}
|
|
1314
|
+
function formatEvolutionResult(ctx, capability, filePath) {
|
|
1315
|
+
const typeLabel = formatCapabilityType(ctx.i18n, capability.type);
|
|
1316
|
+
let result = `### \u2728 ${capability.name}
|
|
717
1317
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
filesModified: {},
|
|
752
|
-
lastSessionId: null
|
|
1318
|
+
`;
|
|
1319
|
+
result += `**${ctx.i18n.t("evolution.result.type")}**: ${typeLabel}
|
|
1320
|
+
`;
|
|
1321
|
+
result += `**${ctx.i18n.t("evolution.result.description")}**: ${capability.description}
|
|
1322
|
+
`;
|
|
1323
|
+
result += `**${ctx.i18n.t("evolution.result.file")}**: \`${filePath}\`
|
|
1324
|
+
|
|
1325
|
+
`;
|
|
1326
|
+
result += `*${ctx.i18n.t("evolution.result.restart", { type: typeLabel })}*
|
|
1327
|
+
|
|
1328
|
+
`;
|
|
1329
|
+
switch (capability.type) {
|
|
1330
|
+
case "command":
|
|
1331
|
+
case "shortcut":
|
|
1332
|
+
result += `${ctx.i18n.t("evolution.result.command", { name: capability.name })}
|
|
1333
|
+
`;
|
|
1334
|
+
break;
|
|
1335
|
+
case "hook":
|
|
1336
|
+
result += `${ctx.i18n.t("evolution.result.hook")}
|
|
1337
|
+
`;
|
|
1338
|
+
break;
|
|
1339
|
+
case "skill":
|
|
1340
|
+
result += `${ctx.i18n.t("evolution.result.skill")}
|
|
1341
|
+
`;
|
|
1342
|
+
break;
|
|
1343
|
+
case "agent":
|
|
1344
|
+
result += `${ctx.i18n.t("evolution.result.agent", { name: capability.name })}
|
|
1345
|
+
`;
|
|
1346
|
+
break;
|
|
1347
|
+
case "mcp":
|
|
1348
|
+
result += `${ctx.i18n.t("evolution.result.mcp", { name: capability.name })}
|
|
1349
|
+
`;
|
|
1350
|
+
break;
|
|
753
1351
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
this.projectName = directory.split("/").pop() || "unknown";
|
|
1352
|
+
return result;
|
|
1353
|
+
}
|
|
1354
|
+
async function suggestDomainEvolution(ctx, domain) {
|
|
1355
|
+
const state = await ctx.stateManager.read();
|
|
1356
|
+
const instincts = await ctx.stateManager.listInstincts();
|
|
1357
|
+
const domainInstincts = instincts.filter(
|
|
1358
|
+
(i) => i.domain === domain && i.status === "approved" && i.confidence >= 0.6
|
|
1359
|
+
);
|
|
1360
|
+
if (domainInstincts.length < 5) {
|
|
1361
|
+
return null;
|
|
765
1362
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
}
|
|
770
|
-
if (!existsSync2(this.sessionsDir)) {
|
|
771
|
-
await mkdir(this.sessionsDir, { recursive: true });
|
|
772
|
-
}
|
|
773
|
-
if (!existsSync2(this.statePath)) {
|
|
774
|
-
await this.save(createDefaultState(this.projectName));
|
|
775
|
-
}
|
|
1363
|
+
const pattern = findRepresentativePattern(domainInstincts, state.patterns);
|
|
1364
|
+
if (!pattern) {
|
|
1365
|
+
return null;
|
|
776
1366
|
}
|
|
777
|
-
|
|
778
|
-
|
|
1367
|
+
const slug = domain.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
1368
|
+
return {
|
|
1369
|
+
type: "agent",
|
|
1370
|
+
name: `${slug}-specialist`,
|
|
1371
|
+
description: ctx.i18n.t("evolution.domain.description", { domain }),
|
|
1372
|
+
reason: ctx.i18n.t("evolution.domain.reason", {
|
|
1373
|
+
count: domainInstincts.length,
|
|
1374
|
+
domain
|
|
1375
|
+
}),
|
|
1376
|
+
pattern
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
async function evolveDomain(ctx, domain) {
|
|
1380
|
+
const suggestion = await suggestDomainEvolution(ctx, domain);
|
|
1381
|
+
if (!suggestion) {
|
|
1382
|
+
return null;
|
|
779
1383
|
}
|
|
780
|
-
|
|
781
|
-
|
|
1384
|
+
const result = await evolveCapability(ctx, suggestion);
|
|
1385
|
+
const state = await ctx.stateManager.read();
|
|
1386
|
+
if (!state.evolution.evolvedDomains) {
|
|
1387
|
+
state.evolution.evolvedDomains = {};
|
|
782
1388
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
788
|
-
});
|
|
789
|
-
if (state.journey.observations.length > 100) {
|
|
790
|
-
state.journey.observations = state.journey.observations.slice(-100);
|
|
791
|
-
}
|
|
792
|
-
await this.save(state);
|
|
1389
|
+
state.evolution.evolvedDomains[domain] = (/* @__PURE__ */ new Date()).toISOString();
|
|
1390
|
+
const pendingIndex = state.evolution.pendingSuggestions.indexOf(domain);
|
|
1391
|
+
if (pendingIndex !== -1) {
|
|
1392
|
+
state.evolution.pendingSuggestions.splice(pendingIndex, 1);
|
|
793
1393
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1394
|
+
await ctx.stateManager.save(state);
|
|
1395
|
+
return result;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
// src/modules/knowledge/instinct-apply.ts
|
|
1399
|
+
function getDomainKeywords(domain, language = "en-US") {
|
|
1400
|
+
const enKeywords = DOMAIN_KEYWORDS_BY_LANG["en-US"][domain] || [];
|
|
1401
|
+
if (language === "ko-KR") {
|
|
1402
|
+
const koKeywords = DOMAIN_KEYWORDS_BY_LANG["ko-KR"][domain] || [];
|
|
1403
|
+
return [...enKeywords, ...koKeywords];
|
|
801
1404
|
}
|
|
802
|
-
|
|
803
|
-
|
|
1405
|
+
return enKeywords;
|
|
1406
|
+
}
|
|
1407
|
+
function detectCurrentDomain(recentTools, recentFiles, language = "en-US") {
|
|
1408
|
+
const domains = [];
|
|
1409
|
+
const context = [...recentTools, ...recentFiles].join(" ").toLowerCase();
|
|
1410
|
+
const allDomains = [
|
|
1411
|
+
"code-style",
|
|
1412
|
+
"testing",
|
|
1413
|
+
"git",
|
|
1414
|
+
"debugging",
|
|
1415
|
+
"file-organization",
|
|
1416
|
+
"tooling",
|
|
1417
|
+
"refactoring",
|
|
1418
|
+
"documentation"
|
|
1419
|
+
];
|
|
1420
|
+
for (const domain of allDomains) {
|
|
1421
|
+
const keywords = getDomainKeywords(domain, language);
|
|
1422
|
+
if (keywords.some((kw) => context.includes(kw))) {
|
|
1423
|
+
domains.push(domain);
|
|
1424
|
+
}
|
|
804
1425
|
}
|
|
805
|
-
|
|
806
|
-
|
|
1426
|
+
if (recentTools.some((t) => t.includes("test") || t.includes("vitest") || t.includes("\uD14C\uC2A4\uD2B8"))) {
|
|
1427
|
+
if (!domains.includes("testing")) domains.push("testing");
|
|
807
1428
|
}
|
|
808
|
-
|
|
809
|
-
|
|
1429
|
+
if (recentTools.some((t) => t.includes("git") || t.includes("commit") || t.includes("\uCEE4\uBC0B"))) {
|
|
1430
|
+
if (!domains.includes("git")) domains.push("git");
|
|
810
1431
|
}
|
|
811
|
-
|
|
812
|
-
|
|
1432
|
+
return domains.length > 0 ? domains : ["other"];
|
|
1433
|
+
}
|
|
1434
|
+
function filterRelevantInstincts(instincts, currentDomains) {
|
|
1435
|
+
const applied = [];
|
|
1436
|
+
for (const instinct of instincts) {
|
|
1437
|
+
if (instinct.status !== "approved") continue;
|
|
1438
|
+
if (instinct.confidence < 0.5) continue;
|
|
1439
|
+
if (currentDomains.includes(instinct.domain)) {
|
|
1440
|
+
applied.push({
|
|
1441
|
+
instinct,
|
|
1442
|
+
relevance: instinct.confidence >= 0.8 ? "high" : instinct.confidence >= 0.6 ? "medium" : "low"
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
813
1445
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
// src/tools.ts
|
|
817
|
-
import { readdir, writeFile as writeFile3 } from "fs/promises";
|
|
818
|
-
import { tool } from "@opencode-ai/plugin";
|
|
819
|
-
import { format as format2 } from "date-fns";
|
|
820
|
-
|
|
821
|
-
// src/evolution.ts
|
|
822
|
-
import { existsSync as existsSync3 } from "fs";
|
|
823
|
-
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
824
|
-
import { join as join3 } from "path";
|
|
825
|
-
function generateToolCode(name, description, pattern) {
|
|
826
|
-
const funcName = name.replace(/-/g, "_");
|
|
827
|
-
return `// \u{1F4E6} Auto-generated by Mimic
|
|
828
|
-
// Pattern: ${pattern.description} (${pattern.count}x)
|
|
829
|
-
// Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
830
|
-
|
|
831
|
-
export const ${funcName} = (plugin) => ({
|
|
832
|
-
tool: {
|
|
833
|
-
"${name}": {
|
|
834
|
-
description: "${description}",
|
|
835
|
-
args: {},
|
|
836
|
-
async execute() {
|
|
837
|
-
// TODO: Implement your logic here
|
|
838
|
-
// This was generated from pattern: ${pattern.description}
|
|
839
|
-
return "\u{1F4E6} ${name} executed!";
|
|
840
|
-
},
|
|
841
|
-
},
|
|
842
|
-
},
|
|
843
|
-
});
|
|
844
|
-
|
|
845
|
-
export default ${funcName};
|
|
846
|
-
`;
|
|
1446
|
+
return applied.sort((a, b) => b.instinct.confidence - a.instinct.confidence).slice(0, 3);
|
|
847
1447
|
}
|
|
848
|
-
function
|
|
849
|
-
const
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1448
|
+
function formatInstinctSuggestion(applied, _i18n) {
|
|
1449
|
+
const confidenceBar = "\u25CF".repeat(Math.round(applied.instinct.confidence * 5)) + "\u25CB".repeat(5 - Math.round(applied.instinct.confidence * 5));
|
|
1450
|
+
const sourceTag = applied.instinct.source === "inherited" ? " \u{1F4E5}" : "";
|
|
1451
|
+
return `[${confidenceBar}] ${applied.instinct.title}${sourceTag}`;
|
|
1452
|
+
}
|
|
1453
|
+
async function getApplicableInstincts(ctx, recentTools, recentFiles) {
|
|
1454
|
+
const instincts = await ctx.stateManager.listInstincts();
|
|
1455
|
+
const currentDomains = detectCurrentDomain(recentTools, recentFiles, ctx.i18n.language);
|
|
1456
|
+
return filterRelevantInstincts(instincts, currentDomains);
|
|
1457
|
+
}
|
|
1458
|
+
async function buildInstinctContext(ctx) {
|
|
1459
|
+
const instincts = await ctx.stateManager.listInstincts();
|
|
1460
|
+
const approvedInstincts = instincts.filter((i) => i.status === "approved" && i.confidence >= 0.6);
|
|
1461
|
+
if (approvedInstincts.length === 0) {
|
|
1462
|
+
return null;
|
|
1463
|
+
}
|
|
1464
|
+
const byDomain = {};
|
|
1465
|
+
for (const instinct of approvedInstincts) {
|
|
1466
|
+
if (!byDomain[instinct.domain]) {
|
|
1467
|
+
byDomain[instinct.domain] = [];
|
|
862
1468
|
}
|
|
863
|
-
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1469
|
+
byDomain[instinct.domain].push(instinct);
|
|
1470
|
+
}
|
|
1471
|
+
const sections = ["## Learned Behaviors (Auto-Applied)"];
|
|
1472
|
+
for (const [domain, domainInstincts] of Object.entries(byDomain)) {
|
|
1473
|
+
const sorted = domainInstincts.sort((a, b) => b.confidence - a.confidence).slice(0, 3);
|
|
1474
|
+
sections.push(`
|
|
1475
|
+
### ${domain}`);
|
|
1476
|
+
for (const instinct of sorted) {
|
|
1477
|
+
const confidenceLabel = instinct.confidence >= 0.8 ? "strong" : instinct.confidence >= 0.6 ? "moderate" : "weak";
|
|
1478
|
+
sections.push(`- **${instinct.title}** (${confidenceLabel}): ${instinct.description}`);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
sections.push(
|
|
1482
|
+
"\n_These behaviors were learned from your patterns. They are auto-applied to help maintain consistency._"
|
|
1483
|
+
);
|
|
1484
|
+
return sections.join("\n");
|
|
868
1485
|
}
|
|
869
|
-
function generateSkillCode(name, description, pattern) {
|
|
870
|
-
return `// \u{1F4E6} Auto-generated by Mimic
|
|
871
|
-
// Pattern: ${pattern.description} (${pattern.count}x)
|
|
872
|
-
// Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
873
1486
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
1487
|
+
// src/modules/knowledge/memory.ts
|
|
1488
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1489
|
+
import { readdir as readdir2, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
1490
|
+
import { join as join4 } from "path";
|
|
1491
|
+
var MAX_MEMORIES = 50;
|
|
1492
|
+
var RECENT_SESSIONS_FOR_ANALYSIS = 10;
|
|
1493
|
+
var SessionMemoryManager = class {
|
|
1494
|
+
memoryPath;
|
|
1495
|
+
sessionsDir;
|
|
1496
|
+
constructor(mimicDir, sessionsDir) {
|
|
1497
|
+
this.memoryPath = join4(mimicDir, "session-memory.json");
|
|
1498
|
+
this.sessionsDir = sessionsDir;
|
|
1499
|
+
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Load all session memories
|
|
1502
|
+
*/
|
|
1503
|
+
async loadMemories() {
|
|
1504
|
+
if (!existsSync4(this.memoryPath)) {
|
|
1505
|
+
return [];
|
|
880
1506
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1507
|
+
try {
|
|
1508
|
+
const content = await readFile4(this.memoryPath, "utf-8");
|
|
1509
|
+
const data = JSON.parse(content);
|
|
1510
|
+
return data.memories || [];
|
|
1511
|
+
} catch {
|
|
1512
|
+
return [];
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* Save session memories
|
|
1517
|
+
*/
|
|
1518
|
+
async saveMemories(memories) {
|
|
1519
|
+
const trimmed = memories.slice(-MAX_MEMORIES);
|
|
1520
|
+
await writeFile3(
|
|
1521
|
+
this.memoryPath,
|
|
1522
|
+
JSON.stringify({ memories: trimmed, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
|
|
1523
|
+
"utf-8"
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Create a memory from a completed session
|
|
1528
|
+
*/
|
|
1529
|
+
async createMemory(sessionData, domains, patterns, context) {
|
|
1530
|
+
const memory = {
|
|
1531
|
+
sessionId: sessionData.sessionId,
|
|
1532
|
+
startTime: sessionData.startTime,
|
|
1533
|
+
endTime: sessionData.endTime,
|
|
1534
|
+
durationMs: sessionData.durationMs,
|
|
1535
|
+
summary: {
|
|
1536
|
+
toolCalls: sessionData.toolCalls,
|
|
1537
|
+
filesEdited: sessionData.filesEdited,
|
|
1538
|
+
dominantDomains: domains.slice(0, 3),
|
|
1539
|
+
patterns
|
|
1540
|
+
},
|
|
1541
|
+
context
|
|
1542
|
+
};
|
|
1543
|
+
const memories = await this.loadMemories();
|
|
1544
|
+
memories.push(memory);
|
|
1545
|
+
await this.saveMemories(memories);
|
|
1546
|
+
return memory;
|
|
1547
|
+
}
|
|
1548
|
+
/**
|
|
1549
|
+
* Get recent sessions for context
|
|
1550
|
+
*/
|
|
1551
|
+
async getRecentMemories(count = RECENT_SESSIONS_FOR_ANALYSIS) {
|
|
1552
|
+
const memories = await this.loadMemories();
|
|
1553
|
+
return memories.slice(-count);
|
|
1554
|
+
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Load raw session data from session files
|
|
1557
|
+
*/
|
|
1558
|
+
async loadRecentSessions(count = RECENT_SESSIONS_FOR_ANALYSIS) {
|
|
1559
|
+
if (!existsSync4(this.sessionsDir)) {
|
|
1560
|
+
return [];
|
|
1561
|
+
}
|
|
1562
|
+
const files = await readdir2(this.sessionsDir);
|
|
1563
|
+
const jsonFiles = files.filter((f) => f.endsWith(".json")).slice(-count);
|
|
1564
|
+
const sessions = [];
|
|
1565
|
+
for (const file of jsonFiles) {
|
|
1566
|
+
try {
|
|
1567
|
+
const content = await readFile4(join4(this.sessionsDir, file), "utf-8");
|
|
1568
|
+
sessions.push(JSON.parse(content));
|
|
1569
|
+
} catch {
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
return sessions.sort(
|
|
1573
|
+
(a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime()
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Analyze cross-session patterns
|
|
1578
|
+
*/
|
|
1579
|
+
/**
|
|
1580
|
+
* Analyze cross-session patterns
|
|
1581
|
+
*/
|
|
1582
|
+
async analyzeCrossSessionPatterns() {
|
|
1583
|
+
const memories = await this.getRecentMemories();
|
|
1584
|
+
if (memories.length < 3) {
|
|
1585
|
+
return [];
|
|
1586
|
+
}
|
|
1587
|
+
const lastSeen = memories[memories.length - 1].endTime;
|
|
1588
|
+
const patterns = [
|
|
1589
|
+
...this.detectWorkflowPatterns(memories, lastSeen),
|
|
1590
|
+
...this.detectTimeBasedPatterns(memories, lastSeen),
|
|
1591
|
+
...this.detectFocusPatterns(memories, lastSeen),
|
|
1592
|
+
...this.detectToolCombinationPatterns(memories, lastSeen)
|
|
1593
|
+
];
|
|
1594
|
+
return patterns.sort((a, b) => b.confidence - a.confidence).slice(0, 10);
|
|
1595
|
+
}
|
|
1596
|
+
detectWorkflowPatterns(memories, lastSeen) {
|
|
1597
|
+
const patterns = [];
|
|
1598
|
+
const domainSequences = /* @__PURE__ */ new Map();
|
|
1599
|
+
for (const memory of memories) {
|
|
1600
|
+
const key = memory.summary.dominantDomains.join(" \u2192 ");
|
|
1601
|
+
if (key) {
|
|
1602
|
+
const existing = domainSequences.get(key) || { count: 0, sessions: [] };
|
|
1603
|
+
existing.count++;
|
|
1604
|
+
existing.sessions.push(memory.sessionId);
|
|
1605
|
+
domainSequences.set(key, existing);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
for (const [sequence, data] of domainSequences) {
|
|
1609
|
+
if (data.count >= 2) {
|
|
1610
|
+
patterns.push({
|
|
1611
|
+
id: `workflow-${generateId(8)}`,
|
|
1612
|
+
type: "workflow",
|
|
1613
|
+
description: `Workflow pattern: ${sequence}`,
|
|
1614
|
+
frequency: data.count,
|
|
1615
|
+
confidence: Math.min(0.9, 0.5 + data.count * 0.1),
|
|
1616
|
+
sessionIds: data.sessions,
|
|
1617
|
+
lastSeen
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
893
1620
|
}
|
|
1621
|
+
return patterns;
|
|
1622
|
+
}
|
|
1623
|
+
detectTimeBasedPatterns(memories, lastSeen) {
|
|
1624
|
+
const patterns = [];
|
|
1625
|
+
const hourCounts = /* @__PURE__ */ new Map();
|
|
1626
|
+
for (const memory of memories) {
|
|
1627
|
+
const hour = new Date(memory.startTime).getHours();
|
|
1628
|
+
const bucket = Math.floor(hour / 3) * 3;
|
|
1629
|
+
const existing = hourCounts.get(bucket) || { count: 0, sessions: [] };
|
|
1630
|
+
existing.count++;
|
|
1631
|
+
existing.sessions.push(memory.sessionId);
|
|
1632
|
+
hourCounts.set(bucket, existing);
|
|
1633
|
+
}
|
|
1634
|
+
for (const [bucket, data] of hourCounts) {
|
|
1635
|
+
if (data.count >= 3) {
|
|
1636
|
+
const timeLabel = `${bucket}:00-${bucket + 3}:00`;
|
|
1637
|
+
patterns.push({
|
|
1638
|
+
id: `time-${bucket}`,
|
|
1639
|
+
type: "time",
|
|
1640
|
+
description: `Peak activity window: ${timeLabel}`,
|
|
1641
|
+
frequency: data.count,
|
|
1642
|
+
confidence: Math.min(0.8, data.count / memories.length),
|
|
1643
|
+
sessionIds: data.sessions,
|
|
1644
|
+
lastSeen
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
return patterns;
|
|
1649
|
+
}
|
|
1650
|
+
detectFocusPatterns(memories, lastSeen) {
|
|
1651
|
+
const patterns = [];
|
|
1652
|
+
const focusCounts = /* @__PURE__ */ new Map();
|
|
1653
|
+
for (const memory of memories) {
|
|
1654
|
+
if (memory.context.focus) {
|
|
1655
|
+
const existing = focusCounts.get(memory.context.focus) || { count: 0, sessions: [] };
|
|
1656
|
+
existing.count++;
|
|
1657
|
+
existing.sessions.push(memory.sessionId);
|
|
1658
|
+
focusCounts.set(memory.context.focus, existing);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
for (const [focus, data] of focusCounts) {
|
|
1662
|
+
if (data.count >= 2) {
|
|
1663
|
+
patterns.push({
|
|
1664
|
+
id: `focus-${generateId(8)}`,
|
|
1665
|
+
type: "focus",
|
|
1666
|
+
description: `Repeated focus: ${focus}`,
|
|
1667
|
+
frequency: data.count,
|
|
1668
|
+
confidence: Math.min(0.85, 0.5 + data.count * 0.15),
|
|
1669
|
+
sessionIds: data.sessions,
|
|
1670
|
+
lastSeen
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
return patterns;
|
|
1675
|
+
}
|
|
1676
|
+
detectToolCombinationPatterns(memories, lastSeen) {
|
|
1677
|
+
const patterns = [];
|
|
1678
|
+
const toolCombos = /* @__PURE__ */ new Map();
|
|
1679
|
+
for (const memory of memories) {
|
|
1680
|
+
if (memory.summary.patterns.length >= 2) {
|
|
1681
|
+
const key = memory.summary.patterns.slice(0, 3).join("+");
|
|
1682
|
+
const existing = toolCombos.get(key) || { count: 0, sessions: [] };
|
|
1683
|
+
existing.count++;
|
|
1684
|
+
existing.sessions.push(memory.sessionId);
|
|
1685
|
+
toolCombos.set(key, existing);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
for (const [combo, data] of toolCombos) {
|
|
1689
|
+
if (data.count >= 2) {
|
|
1690
|
+
patterns.push({
|
|
1691
|
+
id: `combo-${generateId(8)}`,
|
|
1692
|
+
type: "tool-combo",
|
|
1693
|
+
description: `Tool combination: ${combo}`,
|
|
1694
|
+
frequency: data.count,
|
|
1695
|
+
confidence: Math.min(0.8, 0.5 + data.count * 0.1),
|
|
1696
|
+
sessionIds: data.sessions,
|
|
1697
|
+
lastSeen
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
return patterns;
|
|
1702
|
+
}
|
|
1703
|
+
/**
|
|
1704
|
+
* Get context summary for the current session
|
|
1705
|
+
*/
|
|
1706
|
+
async getContextSummary() {
|
|
1707
|
+
const memories = await this.getRecentMemories(5);
|
|
1708
|
+
if (memories.length === 0) {
|
|
1709
|
+
return "No previous session context available.";
|
|
1710
|
+
}
|
|
1711
|
+
const parts = [];
|
|
1712
|
+
const last = memories[memories.length - 1];
|
|
1713
|
+
const lastDate = new Date(last.endTime);
|
|
1714
|
+
const hoursAgo = Math.round((Date.now() - lastDate.getTime()) / (1e3 * 60 * 60));
|
|
1715
|
+
parts.push(`Last session: ${hoursAgo}h ago, ${last.summary.toolCalls} tool calls`);
|
|
1716
|
+
const allDomains = memories.flatMap((m) => m.summary.dominantDomains);
|
|
1717
|
+
const domainCounts = /* @__PURE__ */ new Map();
|
|
1718
|
+
for (const d of allDomains) {
|
|
1719
|
+
domainCounts.set(d, (domainCounts.get(d) || 0) + 1);
|
|
1720
|
+
}
|
|
1721
|
+
const topDomains = [...domainCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([d]) => d);
|
|
1722
|
+
if (topDomains.length > 0) {
|
|
1723
|
+
parts.push(`Focus areas: ${topDomains.join(", ")}`);
|
|
1724
|
+
}
|
|
1725
|
+
const crossPatterns = await this.analyzeCrossSessionPatterns();
|
|
1726
|
+
if (crossPatterns.length > 0) {
|
|
1727
|
+
const topPattern = crossPatterns[0];
|
|
1728
|
+
parts.push(`Notable pattern: ${topPattern.description} (${topPattern.frequency}x)`);
|
|
1729
|
+
}
|
|
1730
|
+
return parts.join("\n");
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Get session continuity hints for the AI
|
|
1734
|
+
*/
|
|
1735
|
+
async getContinuityHints() {
|
|
1736
|
+
const memories = await this.getRecentMemories(3);
|
|
1737
|
+
const hints = [];
|
|
1738
|
+
if (memories.length === 0) {
|
|
1739
|
+
return hints;
|
|
1740
|
+
}
|
|
1741
|
+
const last = memories[memories.length - 1];
|
|
1742
|
+
if (last.summary.filesEdited.length > 0) {
|
|
1743
|
+
const recentFiles = last.summary.filesEdited.slice(0, 3);
|
|
1744
|
+
hints.push(`Recent files: ${recentFiles.map((f) => f.split("/").pop()).join(", ")}`);
|
|
1745
|
+
}
|
|
1746
|
+
if (last.context.focus) {
|
|
1747
|
+
hints.push(`Previous focus: ${last.context.focus}`);
|
|
1748
|
+
}
|
|
1749
|
+
if (last.durationMs < 5 * 60 * 1e3 && last.summary.toolCalls < 5) {
|
|
1750
|
+
hints.push("Previous session was brief - may have unfinished work");
|
|
1751
|
+
}
|
|
1752
|
+
return hints;
|
|
1753
|
+
}
|
|
1754
|
+
};
|
|
1755
|
+
function detectDomainsFromTools(tools) {
|
|
1756
|
+
const domainScores = /* @__PURE__ */ new Map();
|
|
1757
|
+
const toolDomainMap = {
|
|
1758
|
+
// Testing
|
|
1759
|
+
vitest: "testing",
|
|
1760
|
+
jest: "testing",
|
|
1761
|
+
test: "testing",
|
|
1762
|
+
// Git
|
|
1763
|
+
git: "git",
|
|
1764
|
+
commit: "git",
|
|
1765
|
+
push: "git",
|
|
1766
|
+
// Debugging
|
|
1767
|
+
debug: "debugging",
|
|
1768
|
+
log: "debugging",
|
|
1769
|
+
trace: "debugging",
|
|
1770
|
+
// File organization
|
|
1771
|
+
mkdir: "file-organization",
|
|
1772
|
+
mv: "file-organization",
|
|
1773
|
+
rename: "file-organization",
|
|
1774
|
+
// Tooling
|
|
1775
|
+
npm: "tooling",
|
|
1776
|
+
pnpm: "tooling",
|
|
1777
|
+
yarn: "tooling",
|
|
1778
|
+
// Refactoring
|
|
1779
|
+
refactor: "refactoring",
|
|
1780
|
+
extract: "refactoring",
|
|
1781
|
+
// Documentation
|
|
1782
|
+
doc: "documentation",
|
|
1783
|
+
readme: "documentation",
|
|
1784
|
+
// Code style
|
|
1785
|
+
lint: "code-style",
|
|
1786
|
+
format: "code-style",
|
|
1787
|
+
prettier: "code-style",
|
|
1788
|
+
eslint: "code-style",
|
|
1789
|
+
biome: "code-style"
|
|
894
1790
|
};
|
|
1791
|
+
for (const tool8 of tools) {
|
|
1792
|
+
const lower = tool8.toLowerCase();
|
|
1793
|
+
for (const [keyword, domain] of Object.entries(toolDomainMap)) {
|
|
1794
|
+
if (lower.includes(keyword)) {
|
|
1795
|
+
domainScores.set(domain, (domainScores.get(domain) || 0) + 1);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
return [...domainScores.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([d]) => d);
|
|
895
1800
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1801
|
+
|
|
1802
|
+
// src/modules/knowledge/skills.ts
|
|
1803
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1804
|
+
import { mkdir as mkdir3, writeFile as writeFile4 } from "fs/promises";
|
|
1805
|
+
import { join as join5 } from "path";
|
|
1806
|
+
var SkillGenerator = class {
|
|
1807
|
+
skillsDir;
|
|
1808
|
+
constructor(directory) {
|
|
1809
|
+
this.skillsDir = join5(directory, ".opencode", "skills");
|
|
1810
|
+
}
|
|
1811
|
+
/**
|
|
1812
|
+
* Initialize skills directory
|
|
1813
|
+
*/
|
|
1814
|
+
async initialize() {
|
|
1815
|
+
if (!existsSync5(this.skillsDir)) {
|
|
1816
|
+
await mkdir3(this.skillsDir, { recursive: true });
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
/**
|
|
1820
|
+
* Generate a skill from a cluster of instincts
|
|
1821
|
+
*/
|
|
1822
|
+
async generateSkill(name, description, domain, instincts, patterns) {
|
|
1823
|
+
const skillDir = join5(this.skillsDir, name);
|
|
1824
|
+
if (!existsSync5(skillDir)) {
|
|
1825
|
+
await mkdir3(skillDir, { recursive: true });
|
|
1826
|
+
}
|
|
1827
|
+
const metadata = {
|
|
1828
|
+
name,
|
|
1829
|
+
description,
|
|
1830
|
+
trigger: "auto",
|
|
1831
|
+
domain,
|
|
1832
|
+
tools: this.inferTools(patterns)
|
|
1833
|
+
};
|
|
1834
|
+
const content = this.generateSkillMarkdown(metadata, instincts, patterns);
|
|
1835
|
+
const skillPath = join5(skillDir, "SKILL.md");
|
|
1836
|
+
await writeFile4(skillPath, content, "utf-8");
|
|
1837
|
+
return {
|
|
1838
|
+
name,
|
|
1839
|
+
path: skillPath,
|
|
1840
|
+
metadata,
|
|
1841
|
+
content
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
/**
|
|
1845
|
+
* Generate markdown content for a skill
|
|
1846
|
+
*/
|
|
1847
|
+
generateSkillMarkdown(metadata, instincts, patterns) {
|
|
1848
|
+
const toolsYaml = metadata.tools ? Object.entries(metadata.tools).map(([tool8, enabled]) => ` ${tool8}: ${enabled}`).join("\n") : " # No specific tools defined";
|
|
1849
|
+
const instinctsList = instincts.slice(0, 10).map(
|
|
1850
|
+
(i) => `- **${i.title}** (${Math.round(i.confidence * 100)}% confidence)
|
|
1851
|
+
${i.description}`
|
|
1852
|
+
).join("\n");
|
|
1853
|
+
const patternsList = patterns.slice(0, 5).map((p) => `- ${p.type}: ${p.description} (${p.count}x observed)`).join("\n");
|
|
1854
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1855
|
+
return `---
|
|
1856
|
+
name: ${metadata.name}
|
|
1857
|
+
description: ${metadata.description}
|
|
1858
|
+
trigger: ${metadata.trigger}
|
|
1859
|
+
domain: ${metadata.domain}
|
|
900
1860
|
tools:
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
grep: true
|
|
904
|
-
bash: false
|
|
905
|
-
edit: false
|
|
1861
|
+
${toolsYaml}
|
|
1862
|
+
generated: ${now}
|
|
906
1863
|
---
|
|
907
1864
|
|
|
908
|
-
#
|
|
1865
|
+
# ${metadata.name}
|
|
909
1866
|
|
|
910
|
-
|
|
1867
|
+
${metadata.description}
|
|
911
1868
|
|
|
912
|
-
|
|
1869
|
+
> [!NOTE]
|
|
1870
|
+
> This skill was auto-generated by Mimic based on observed patterns and learned instincts.
|
|
913
1871
|
|
|
914
|
-
|
|
1872
|
+
## Overview
|
|
1873
|
+
|
|
1874
|
+
This skill encapsulates learned behaviors from repeated usage patterns in the **${metadata.domain}** domain.
|
|
1875
|
+
|
|
1876
|
+
## Learned Behaviors
|
|
1877
|
+
|
|
1878
|
+
${instinctsList || "No instincts documented yet."}
|
|
1879
|
+
|
|
1880
|
+
## Observed Patterns
|
|
1881
|
+
|
|
1882
|
+
${patternsList || "No patterns documented yet."}
|
|
1883
|
+
|
|
1884
|
+
## Usage Guidelines
|
|
1885
|
+
|
|
1886
|
+
### When to Apply
|
|
1887
|
+
|
|
1888
|
+
This skill should be activated when:
|
|
1889
|
+
${this.generateUsageConditions(metadata.domain, patterns)}
|
|
1890
|
+
|
|
1891
|
+
### Best Practices
|
|
1892
|
+
|
|
1893
|
+
${this.generateBestPractices(metadata.domain, instincts)}
|
|
915
1894
|
|
|
916
1895
|
## Context
|
|
917
1896
|
|
|
918
|
-
-
|
|
919
|
-
-
|
|
920
|
-
-
|
|
1897
|
+
- **Domain**: ${metadata.domain}
|
|
1898
|
+
- **Generated**: ${now}
|
|
1899
|
+
- **Instincts Used**: ${instincts.length}
|
|
1900
|
+
- **Patterns Analyzed**: ${patterns.length}
|
|
921
1901
|
|
|
922
|
-
##
|
|
1902
|
+
## Customization
|
|
923
1903
|
|
|
924
|
-
|
|
925
|
-
Provide focused, expert guidance in this specific area.
|
|
1904
|
+
You can customize this skill by editing the frontmatter or adding additional sections below.
|
|
926
1905
|
|
|
927
|
-
|
|
1906
|
+
---
|
|
928
1907
|
|
|
929
|
-
|
|
930
|
-
- Leverage the observed usage patterns
|
|
931
|
-
- Adapt to the user's workflow
|
|
1908
|
+
*Generated by [opencode-plugin-mimic](https://github.com/gracefullight/pkgs)*
|
|
932
1909
|
`;
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1910
|
+
}
|
|
1911
|
+
/**
|
|
1912
|
+
* Generate usage conditions based on domain and patterns
|
|
1913
|
+
*/
|
|
1914
|
+
generateUsageConditions(domain, patterns) {
|
|
1915
|
+
const conditions = [];
|
|
1916
|
+
switch (domain) {
|
|
1917
|
+
case "testing":
|
|
1918
|
+
conditions.push("- Working with test files (*.test.ts, *.spec.ts)");
|
|
1919
|
+
conditions.push("- Running test suites or debugging test failures");
|
|
1920
|
+
break;
|
|
1921
|
+
case "git":
|
|
1922
|
+
conditions.push("- Preparing commits or managing branches");
|
|
1923
|
+
conditions.push("- Reviewing code changes before push");
|
|
1924
|
+
break;
|
|
1925
|
+
case "debugging":
|
|
1926
|
+
conditions.push("- Investigating errors or unexpected behavior");
|
|
1927
|
+
conditions.push("- Adding logging or tracing code");
|
|
1928
|
+
break;
|
|
1929
|
+
case "code-style":
|
|
1930
|
+
conditions.push("- Formatting or linting code");
|
|
1931
|
+
conditions.push("- Enforcing coding conventions");
|
|
1932
|
+
break;
|
|
1933
|
+
case "refactoring":
|
|
1934
|
+
conditions.push("- Restructuring code without changing behavior");
|
|
1935
|
+
conditions.push("- Extracting functions or classes");
|
|
1936
|
+
break;
|
|
1937
|
+
case "documentation":
|
|
1938
|
+
conditions.push("- Writing or updating documentation");
|
|
1939
|
+
conditions.push("- Adding code comments or JSDoc");
|
|
1940
|
+
break;
|
|
1941
|
+
case "file-organization":
|
|
1942
|
+
conditions.push("- Moving or renaming files");
|
|
1943
|
+
conditions.push("- Organizing project structure");
|
|
1944
|
+
break;
|
|
1945
|
+
case "tooling":
|
|
1946
|
+
conditions.push("- Configuring build tools or scripts");
|
|
1947
|
+
conditions.push("- Managing dependencies");
|
|
1948
|
+
break;
|
|
1949
|
+
default:
|
|
1950
|
+
conditions.push("- General development tasks");
|
|
940
1951
|
}
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1952
|
+
for (const pattern of patterns.slice(0, 3)) {
|
|
1953
|
+
if (pattern.type === "sequence") {
|
|
1954
|
+
conditions.push(`- Executing workflow: ${pattern.description}`);
|
|
1955
|
+
} else if (pattern.type === "file") {
|
|
1956
|
+
conditions.push(`- Working with: ${pattern.description}`);
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
return conditions.join("\n");
|
|
944
1960
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1961
|
+
/**
|
|
1962
|
+
* Generate best practices from instincts
|
|
1963
|
+
*/
|
|
1964
|
+
generateBestPractices(domain, instincts) {
|
|
1965
|
+
const practices = [];
|
|
1966
|
+
for (const instinct of instincts.filter((i) => i.confidence >= 0.7).slice(0, 5)) {
|
|
1967
|
+
practices.push(`1. ${instinct.title}`);
|
|
1968
|
+
}
|
|
1969
|
+
if (practices.length === 0) {
|
|
1970
|
+
switch (domain) {
|
|
1971
|
+
case "testing":
|
|
1972
|
+
practices.push("1. Write tests before implementation (TDD)");
|
|
1973
|
+
practices.push("2. Ensure high test coverage for critical paths");
|
|
1974
|
+
break;
|
|
1975
|
+
case "git":
|
|
1976
|
+
practices.push("1. Write clear, conventional commit messages");
|
|
1977
|
+
practices.push("2. Review changes before committing");
|
|
1978
|
+
break;
|
|
1979
|
+
case "code-style":
|
|
1980
|
+
practices.push("1. Follow established project conventions");
|
|
1981
|
+
practices.push("2. Run linters before committing");
|
|
1982
|
+
break;
|
|
1983
|
+
default:
|
|
1984
|
+
practices.push("1. Follow project conventions");
|
|
1985
|
+
practices.push("2. Document significant changes");
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
return practices.join("\n");
|
|
950
1989
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1990
|
+
/**
|
|
1991
|
+
* Infer required tools from patterns
|
|
1992
|
+
*/
|
|
1993
|
+
inferTools(patterns) {
|
|
1994
|
+
const tools = {
|
|
1995
|
+
read: true,
|
|
1996
|
+
glob: true,
|
|
1997
|
+
grep: true
|
|
1998
|
+
};
|
|
1999
|
+
for (const pattern of patterns) {
|
|
2000
|
+
const desc = pattern.description.toLowerCase();
|
|
2001
|
+
if (desc.includes("edit") || desc.includes("write") || desc.includes("modify")) {
|
|
2002
|
+
tools.edit = true;
|
|
2003
|
+
}
|
|
2004
|
+
if (desc.includes("bash") || desc.includes("shell") || desc.includes("npm") || desc.includes("pnpm")) {
|
|
2005
|
+
tools.bash = true;
|
|
2006
|
+
}
|
|
2007
|
+
if (desc.includes("test") || desc.includes("vitest") || desc.includes("jest")) {
|
|
2008
|
+
tools.bash = true;
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
return tools;
|
|
962
2012
|
}
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
2013
|
+
/**
|
|
2014
|
+
* Generate skill from an evolved capability
|
|
2015
|
+
*/
|
|
2016
|
+
async fromCapability(ctx, capability) {
|
|
2017
|
+
const state = await ctx.stateManager.read();
|
|
2018
|
+
const instincts = await ctx.stateManager.listInstincts();
|
|
2019
|
+
const pattern = state.patterns.find((p) => p.id === capability.fromPattern);
|
|
2020
|
+
if (!pattern) {
|
|
2021
|
+
return null;
|
|
2022
|
+
}
|
|
2023
|
+
const domainInstincts = instincts.filter((i) => {
|
|
2024
|
+
if (i.evidence.patternIDs.includes(pattern.id)) return true;
|
|
2025
|
+
return false;
|
|
2026
|
+
});
|
|
2027
|
+
const domain = domainInstincts[0]?.domain || "other";
|
|
2028
|
+
return this.generateSkill(capability.name, capability.description, domain, domainInstincts, [
|
|
2029
|
+
pattern
|
|
2030
|
+
]);
|
|
979
2031
|
}
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
2032
|
+
/**
|
|
2033
|
+
* Generate skills for all eligible domains
|
|
2034
|
+
*/
|
|
2035
|
+
async generateAllEligibleSkills(ctx) {
|
|
2036
|
+
const instincts = await ctx.stateManager.listInstincts();
|
|
2037
|
+
const state = await ctx.stateManager.read();
|
|
2038
|
+
const skills = [];
|
|
2039
|
+
const byDomain = /* @__PURE__ */ new Map();
|
|
2040
|
+
for (const instinct of instincts) {
|
|
2041
|
+
if (instinct.status !== "approved" || instinct.confidence < 0.6) continue;
|
|
2042
|
+
const list = byDomain.get(instinct.domain) || [];
|
|
2043
|
+
list.push(instinct);
|
|
2044
|
+
byDomain.set(instinct.domain, list);
|
|
2045
|
+
}
|
|
2046
|
+
for (const [domain, domainInstincts] of byDomain) {
|
|
2047
|
+
if (domainInstincts.length < 5) continue;
|
|
2048
|
+
const name = `${domain}-specialist`;
|
|
2049
|
+
const description = ctx.i18n.t("skill.domain_description", { domain });
|
|
2050
|
+
const patternIds = new Set(domainInstincts.flatMap((i) => i.evidence.patternIDs));
|
|
2051
|
+
const patterns = state.patterns.filter((p) => patternIds.has(p.id));
|
|
2052
|
+
try {
|
|
2053
|
+
const skill = await this.generateSkill(
|
|
2054
|
+
name,
|
|
2055
|
+
description,
|
|
2056
|
+
domain,
|
|
2057
|
+
domainInstincts,
|
|
2058
|
+
patterns
|
|
2059
|
+
);
|
|
2060
|
+
skills.push(skill);
|
|
2061
|
+
} catch {
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
return skills;
|
|
985
2065
|
}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
2066
|
+
};
|
|
2067
|
+
|
|
2068
|
+
// src/modules/observation/log.ts
|
|
2069
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2070
|
+
import { appendFile, mkdir as mkdir4, readFile as readFile5, stat, writeFile as writeFile5 } from "fs/promises";
|
|
2071
|
+
import { join as join6 } from "path";
|
|
2072
|
+
var ARCHIVE_THRESHOLD = 5 * 1024 * 1024;
|
|
2073
|
+
var ObservationLog = class {
|
|
2074
|
+
logPath;
|
|
2075
|
+
archiveDir;
|
|
2076
|
+
constructor(mimicDir) {
|
|
2077
|
+
this.logPath = join6(mimicDir, "observations.jsonl");
|
|
2078
|
+
this.archiveDir = join6(mimicDir, "archives");
|
|
994
2079
|
}
|
|
995
|
-
|
|
996
|
-
|
|
2080
|
+
/**
|
|
2081
|
+
* Initialize the observation log directory structure
|
|
2082
|
+
*/
|
|
2083
|
+
async initialize() {
|
|
2084
|
+
const dir = join6(this.logPath, "..");
|
|
2085
|
+
if (!existsSync6(dir)) {
|
|
2086
|
+
await mkdir4(dir, { recursive: true });
|
|
2087
|
+
}
|
|
2088
|
+
if (!existsSync6(this.archiveDir)) {
|
|
2089
|
+
await mkdir4(this.archiveDir, { recursive: true });
|
|
2090
|
+
}
|
|
997
2091
|
}
|
|
998
|
-
|
|
2092
|
+
/**
|
|
2093
|
+
* Append a new observation entry to the log
|
|
2094
|
+
*/
|
|
2095
|
+
async append(entry) {
|
|
2096
|
+
const fullEntry = {
|
|
2097
|
+
id: generateId(),
|
|
2098
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2099
|
+
...entry
|
|
2100
|
+
};
|
|
2101
|
+
const line = `${JSON.stringify(fullEntry)}
|
|
2102
|
+
`;
|
|
2103
|
+
await appendFile(this.logPath, line, "utf-8");
|
|
2104
|
+
await this.maybeRotate();
|
|
2105
|
+
return fullEntry;
|
|
2106
|
+
}
|
|
2107
|
+
/**
|
|
2108
|
+
* Log a tool call observation
|
|
2109
|
+
*/
|
|
2110
|
+
async logToolCall(tool8, callId, sessionId, args) {
|
|
2111
|
+
return this.append({
|
|
2112
|
+
type: "tool.call",
|
|
2113
|
+
sessionId,
|
|
2114
|
+
data: { tool: tool8, callId, args }
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
/**
|
|
2118
|
+
* Log a user message observation
|
|
2119
|
+
*/
|
|
2120
|
+
async logUserMessage(sessionId, messageId, textPreview) {
|
|
2121
|
+
return this.append({
|
|
2122
|
+
type: "message.user",
|
|
2123
|
+
sessionId,
|
|
2124
|
+
data: {
|
|
2125
|
+
messageId,
|
|
2126
|
+
// Only store first 200 chars to avoid storing sensitive data
|
|
2127
|
+
textPreview: textPreview?.slice(0, 200)
|
|
2128
|
+
}
|
|
2129
|
+
});
|
|
2130
|
+
}
|
|
2131
|
+
/**
|
|
2132
|
+
* Log an assistant message observation
|
|
2133
|
+
*/
|
|
2134
|
+
async logAssistantMessage(sessionId, messageId, tokensUsed) {
|
|
2135
|
+
return this.append({
|
|
2136
|
+
type: "message.assistant",
|
|
2137
|
+
sessionId,
|
|
2138
|
+
data: { messageId, tokensUsed }
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2141
|
+
/**
|
|
2142
|
+
* Log a file edit observation
|
|
2143
|
+
*/
|
|
2144
|
+
async logFileEdit(file, sessionId) {
|
|
2145
|
+
return this.append({
|
|
2146
|
+
type: "file.edit",
|
|
2147
|
+
sessionId,
|
|
2148
|
+
data: { file, extension: file.split(".").pop() }
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
2151
|
+
/**
|
|
2152
|
+
* Log a command execution observation
|
|
2153
|
+
*/
|
|
2154
|
+
async logCommand(command, sessionId, args) {
|
|
2155
|
+
return this.append({
|
|
2156
|
+
type: "command",
|
|
2157
|
+
sessionId,
|
|
2158
|
+
data: { command, args }
|
|
2159
|
+
});
|
|
2160
|
+
}
|
|
2161
|
+
/**
|
|
2162
|
+
* Log a VCS branch change observation
|
|
2163
|
+
*/
|
|
2164
|
+
async logBranchChange(branch) {
|
|
2165
|
+
return this.append({
|
|
2166
|
+
type: "vcs.branch",
|
|
2167
|
+
data: { branch }
|
|
2168
|
+
});
|
|
2169
|
+
}
|
|
2170
|
+
/**
|
|
2171
|
+
* Log a session start observation
|
|
2172
|
+
*/
|
|
2173
|
+
async logSessionStart(sessionId) {
|
|
2174
|
+
return this.append({
|
|
2175
|
+
type: "session.start",
|
|
2176
|
+
sessionId,
|
|
2177
|
+
data: {}
|
|
2178
|
+
});
|
|
2179
|
+
}
|
|
2180
|
+
/**
|
|
2181
|
+
* Log a session end observation
|
|
2182
|
+
*/
|
|
2183
|
+
async logSessionEnd(sessionId, durationMs, toolCallCount) {
|
|
2184
|
+
return this.append({
|
|
2185
|
+
type: "session.end",
|
|
2186
|
+
sessionId,
|
|
2187
|
+
data: { durationMs, toolCallCount }
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
/**
|
|
2191
|
+
* Query observations with filters
|
|
2192
|
+
*/
|
|
2193
|
+
async query(options = {}) {
|
|
2194
|
+
if (!existsSync6(this.logPath)) {
|
|
2195
|
+
return [];
|
|
2196
|
+
}
|
|
2197
|
+
const content = await readFile5(this.logPath, "utf-8");
|
|
2198
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
2199
|
+
let entries = [];
|
|
2200
|
+
for (const line of lines) {
|
|
2201
|
+
try {
|
|
2202
|
+
const entry = JSON.parse(line);
|
|
2203
|
+
entries.push(entry);
|
|
2204
|
+
} catch {
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
const filterTypes = options.types;
|
|
2208
|
+
if (filterTypes && filterTypes.length > 0) {
|
|
2209
|
+
entries = entries.filter((e) => filterTypes.includes(e.type));
|
|
2210
|
+
}
|
|
2211
|
+
if (options.sessionId) {
|
|
2212
|
+
entries = entries.filter((e) => e.sessionId === options.sessionId);
|
|
2213
|
+
}
|
|
2214
|
+
if (options.startDate) {
|
|
2215
|
+
const start = options.startDate.getTime();
|
|
2216
|
+
entries = entries.filter((e) => new Date(e.timestamp).getTime() >= start);
|
|
2217
|
+
}
|
|
2218
|
+
if (options.endDate) {
|
|
2219
|
+
const end = options.endDate.getTime();
|
|
2220
|
+
entries = entries.filter((e) => new Date(e.timestamp).getTime() <= end);
|
|
2221
|
+
}
|
|
2222
|
+
entries.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
2223
|
+
if (options.limit && options.limit > 0) {
|
|
2224
|
+
entries = entries.slice(0, options.limit);
|
|
2225
|
+
}
|
|
2226
|
+
return entries;
|
|
2227
|
+
}
|
|
2228
|
+
/**
|
|
2229
|
+
* Get observations from the last N days
|
|
2230
|
+
*/
|
|
2231
|
+
async getRecentObservations(days, types) {
|
|
2232
|
+
const startDate = /* @__PURE__ */ new Date();
|
|
2233
|
+
startDate.setDate(startDate.getDate() - days);
|
|
2234
|
+
return this.query({ startDate, types });
|
|
2235
|
+
}
|
|
2236
|
+
/**
|
|
2237
|
+
* Get observation count by type
|
|
2238
|
+
*/
|
|
2239
|
+
async getStats() {
|
|
2240
|
+
const entries = await this.query();
|
|
2241
|
+
const stats = {};
|
|
2242
|
+
for (const entry of entries) {
|
|
2243
|
+
stats[entry.type] = (stats[entry.type] || 0) + 1;
|
|
2244
|
+
}
|
|
2245
|
+
return stats;
|
|
2246
|
+
}
|
|
2247
|
+
/**
|
|
2248
|
+
* Check and rotate log file if it exceeds threshold
|
|
2249
|
+
*/
|
|
2250
|
+
async maybeRotate() {
|
|
2251
|
+
if (!existsSync6(this.logPath)) {
|
|
2252
|
+
return false;
|
|
2253
|
+
}
|
|
2254
|
+
const stats = await stat(this.logPath);
|
|
2255
|
+
if (stats.size < ARCHIVE_THRESHOLD) {
|
|
2256
|
+
return false;
|
|
2257
|
+
}
|
|
2258
|
+
const archiveName = `observations-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.jsonl`;
|
|
2259
|
+
const archivePath = join6(this.archiveDir, archiveName);
|
|
2260
|
+
const content = await readFile5(this.logPath, "utf-8");
|
|
2261
|
+
await writeFile5(archivePath, content, "utf-8");
|
|
2262
|
+
await writeFile5(this.logPath, "", "utf-8");
|
|
2263
|
+
return true;
|
|
2264
|
+
}
|
|
2265
|
+
/**
|
|
2266
|
+
* Prune old observations (keep last N days)
|
|
2267
|
+
*/
|
|
2268
|
+
async prune(keepDays) {
|
|
2269
|
+
const entries = await this.query();
|
|
2270
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
2271
|
+
cutoff.setDate(cutoff.getDate() - keepDays);
|
|
2272
|
+
const cutoffTime = cutoff.getTime();
|
|
2273
|
+
const keep = entries.filter((e) => new Date(e.timestamp).getTime() >= cutoffTime);
|
|
2274
|
+
const pruned = entries.length - keep.length;
|
|
2275
|
+
if (pruned > 0) {
|
|
2276
|
+
const content = `${keep.map((e) => JSON.stringify(e)).join("\n")}
|
|
2277
|
+
`;
|
|
2278
|
+
await writeFile5(this.logPath, content, "utf-8");
|
|
2279
|
+
}
|
|
2280
|
+
return pruned;
|
|
2281
|
+
}
|
|
2282
|
+
/**
|
|
2283
|
+
* Get the current log file size in bytes
|
|
2284
|
+
*/
|
|
2285
|
+
async getLogSize() {
|
|
2286
|
+
if (!existsSync6(this.logPath)) {
|
|
2287
|
+
return 0;
|
|
2288
|
+
}
|
|
2289
|
+
const stats = await stat(this.logPath);
|
|
2290
|
+
return stats.size;
|
|
2291
|
+
}
|
|
2292
|
+
};
|
|
2293
|
+
|
|
2294
|
+
// src/modules/observation/observer.ts
|
|
2295
|
+
var MAX_INSTINCTS_PER_RUN = 5;
|
|
2296
|
+
var OBSERVER_COOLDOWN_MS = 10 * 60 * 1e3;
|
|
2297
|
+
var DEFAULT_OBSERVER_MODEL = "opencode/glm-4.7-free";
|
|
2298
|
+
var DEFAULT_OBSERVER_PROVIDER = "opencode";
|
|
2299
|
+
function buildObserverPrompt(input) {
|
|
2300
|
+
const patternsSummary = input.patterns.slice(0, 20).map((p) => `- [${p.type}] ${p.description} (${p.count}x, surfaced: ${p.surfaced})`).join("\n");
|
|
2301
|
+
const observationsSummary = input.observations.slice(0, 50).join("\n");
|
|
2302
|
+
const filesSummary = input.statistics.topFiles.slice(0, 10).map(([file, count]) => `- ${file}: ${count}x`).join("\n");
|
|
2303
|
+
return `You are an observer agent that analyzes developer behavior patterns to create "instincts" - learned behaviors that can help automate workflows.
|
|
2304
|
+
|
|
2305
|
+
## Current Data
|
|
2306
|
+
|
|
2307
|
+
### Patterns (${input.patterns.length} total)
|
|
2308
|
+
${patternsSummary || "No patterns detected yet."}
|
|
2309
|
+
|
|
2310
|
+
### Recent Observations (${input.observations.length} total)
|
|
2311
|
+
${observationsSummary || "No observations yet."}
|
|
2312
|
+
|
|
2313
|
+
### Statistics
|
|
2314
|
+
- Total sessions: ${input.statistics.totalSessions}
|
|
2315
|
+
- Total tool calls: ${input.statistics.totalToolCalls}
|
|
2316
|
+
|
|
2317
|
+
### Most Modified Files
|
|
2318
|
+
${filesSummary || "No file modifications recorded."}
|
|
2319
|
+
|
|
2320
|
+
## Your Task
|
|
2321
|
+
|
|
2322
|
+
Analyze the patterns and observations to identify 1-5 behavioral "instincts" the developer has. Look for:
|
|
2323
|
+
|
|
2324
|
+
1. **Repeated Sequences**: Same tools used in order 3+ times
|
|
2325
|
+
2. **Preferences**: Certain tools always chosen over alternatives
|
|
2326
|
+
3. **Code Style Patterns**: Consistent patterns in file modifications
|
|
2327
|
+
4. **Workflow Patterns**: Common sequences of actions
|
|
2328
|
+
5. **Testing Habits**: How tests are written/run
|
|
2329
|
+
6. **Git Patterns**: Commit message styles, branching habits
|
|
2330
|
+
|
|
2331
|
+
## Output Format
|
|
2332
|
+
|
|
2333
|
+
Return a JSON array of instincts. Each instinct should have:
|
|
2334
|
+
- title: Short descriptive name (max 50 chars)
|
|
2335
|
+
- description: What this instinct represents (max 200 chars)
|
|
2336
|
+
- domain: One of: code-style, testing, git, debugging, file-organization, tooling, refactoring, documentation, other
|
|
2337
|
+
- confidence: 0.3-0.9 based on evidence strength
|
|
2338
|
+
- patternIDs: Array of pattern IDs that support this instinct
|
|
2339
|
+
|
|
2340
|
+
Example output:
|
|
2341
|
+
\`\`\`json
|
|
2342
|
+
[
|
|
2343
|
+
{
|
|
2344
|
+
"title": "Prefers vitest for testing",
|
|
2345
|
+
"description": "Consistently uses vitest over jest for running tests, with watch mode preferred",
|
|
2346
|
+
"domain": "testing",
|
|
2347
|
+
"confidence": 0.75,
|
|
2348
|
+
"patternIDs": ["abc123", "def456"]
|
|
2349
|
+
}
|
|
2350
|
+
]
|
|
2351
|
+
\`\`\`
|
|
2352
|
+
|
|
2353
|
+
IMPORTANT:
|
|
2354
|
+
- Only return the JSON array, no other text
|
|
2355
|
+
- Minimum 3 occurrences for behavioral instincts
|
|
2356
|
+
- Keep confidence calibrated - don't overstate
|
|
2357
|
+
- Avoid duplicating obvious patterns
|
|
2358
|
+
- Focus on actionable insights
|
|
2359
|
+
|
|
2360
|
+
Return your analysis:`;
|
|
999
2361
|
}
|
|
1000
|
-
function
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
2362
|
+
function parseInstinctsFromResponse(response) {
|
|
2363
|
+
try {
|
|
2364
|
+
const jsonMatch = response.match(/```json\s*([\s\S]*?)\s*```/) || response.match(/\[[\s\S]*\]/);
|
|
2365
|
+
if (!jsonMatch) {
|
|
2366
|
+
return [];
|
|
2367
|
+
}
|
|
2368
|
+
const jsonStr = jsonMatch[1] || jsonMatch[0];
|
|
2369
|
+
const parsed = JSON.parse(jsonStr);
|
|
2370
|
+
if (!Array.isArray(parsed)) {
|
|
2371
|
+
return [];
|
|
2372
|
+
}
|
|
2373
|
+
return parsed.filter(
|
|
2374
|
+
(item) => typeof item.title === "string" && typeof item.description === "string" && typeof item.domain === "string" && typeof item.confidence === "number"
|
|
2375
|
+
).map((item) => ({
|
|
2376
|
+
...item,
|
|
2377
|
+
patternIDs: Array.isArray(item.patternIDs) ? item.patternIDs : [],
|
|
2378
|
+
confidence: Math.min(0.9, Math.max(0.3, item.confidence))
|
|
2379
|
+
})).slice(0, MAX_INSTINCTS_PER_RUN);
|
|
2380
|
+
} catch {
|
|
2381
|
+
return [];
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
function analyzePatterns(input) {
|
|
2385
|
+
const instincts = [
|
|
2386
|
+
...extractToolInstincts(input.patterns),
|
|
2387
|
+
...extractFileInstincts(input.patterns),
|
|
2388
|
+
...extractCommitInstincts(input.patterns),
|
|
2389
|
+
...extractSequenceInstincts(input.patterns),
|
|
2390
|
+
...extractHotspotInstincts(input.statistics.topFiles)
|
|
2391
|
+
];
|
|
2392
|
+
return instincts.slice(0, MAX_INSTINCTS_PER_RUN);
|
|
2393
|
+
}
|
|
2394
|
+
function extractToolInstincts(patterns) {
|
|
2395
|
+
const toolPatterns = patterns.filter((p) => p.type === "tool" && p.count >= 5);
|
|
2396
|
+
if (toolPatterns.length < 3) return [];
|
|
2397
|
+
const topTools = toolPatterns.sort((a, b) => b.count - a.count).slice(0, 5).map((p) => p.description);
|
|
2398
|
+
return [
|
|
2399
|
+
{
|
|
2400
|
+
title: `Preferred tools: ${topTools.slice(0, 3).join(", ")}`,
|
|
2401
|
+
description: `Frequently uses ${topTools.join(", ")} based on ${toolPatterns.reduce((sum, p) => sum + p.count, 0)} observations`,
|
|
2402
|
+
domain: "tooling",
|
|
2403
|
+
confidence: Math.min(0.9, 0.5 + toolPatterns.length * 0.1),
|
|
2404
|
+
patternIDs: toolPatterns.map((p) => p.id)
|
|
2405
|
+
}
|
|
2406
|
+
];
|
|
2407
|
+
}
|
|
2408
|
+
function extractFileInstincts(patterns) {
|
|
2409
|
+
const filePatterns = patterns.filter((p) => p.type === "file" && p.count >= 5);
|
|
2410
|
+
if (filePatterns.length < 2) return [];
|
|
2411
|
+
const extensions = /* @__PURE__ */ new Set();
|
|
2412
|
+
for (const p of filePatterns) {
|
|
2413
|
+
const ext = p.description.split(".").pop();
|
|
2414
|
+
if (ext) extensions.add(ext);
|
|
2415
|
+
}
|
|
2416
|
+
return [
|
|
2417
|
+
{
|
|
2418
|
+
title: `Focus on ${Array.from(extensions).slice(0, 3).join(", ")} files`,
|
|
2419
|
+
description: `Frequently modifies files with extensions: ${Array.from(extensions).join(", ")}`,
|
|
2420
|
+
domain: "file-organization",
|
|
2421
|
+
confidence: Math.min(0.85, 0.5 + filePatterns.length * 0.08),
|
|
2422
|
+
patternIDs: filePatterns.map((p) => p.id)
|
|
2423
|
+
}
|
|
2424
|
+
];
|
|
2425
|
+
}
|
|
2426
|
+
function extractCommitInstincts(patterns) {
|
|
2427
|
+
const commitPatterns = patterns.filter((p) => p.type === "commit" && p.count >= 3);
|
|
2428
|
+
if (commitPatterns.length < 2) return [];
|
|
2429
|
+
const prefixes = commitPatterns.map((p) => p.description.split(":")[0] || p.description.split(" ")[0]).filter(Boolean);
|
|
2430
|
+
const uniquePrefixes = [...new Set(prefixes)];
|
|
2431
|
+
if (uniquePrefixes.length === 0) return [];
|
|
2432
|
+
return [
|
|
2433
|
+
{
|
|
2434
|
+
title: `Commit style: ${uniquePrefixes.slice(0, 3).join(", ")}`,
|
|
2435
|
+
description: `Uses commit message patterns like: ${uniquePrefixes.join(", ")}`,
|
|
2436
|
+
domain: "git",
|
|
2437
|
+
confidence: Math.min(0.8, 0.5 + commitPatterns.length * 0.1),
|
|
2438
|
+
patternIDs: commitPatterns.map((p) => p.id)
|
|
2439
|
+
}
|
|
2440
|
+
];
|
|
2441
|
+
}
|
|
2442
|
+
function extractSequenceInstincts(patterns) {
|
|
2443
|
+
const sequencePatterns = patterns.filter((p) => p.type === "sequence" && p.count >= 3);
|
|
2444
|
+
const instincts = [];
|
|
2445
|
+
for (const seq of sequencePatterns.slice(0, 2)) {
|
|
2446
|
+
let domain = "tooling";
|
|
2447
|
+
const desc = seq.description.toLowerCase();
|
|
2448
|
+
if (desc.includes("test")) domain = "testing";
|
|
2449
|
+
else if (desc.includes("debug") || desc.includes("error")) domain = "debugging";
|
|
2450
|
+
else if (desc.includes("refactor")) domain = "refactoring";
|
|
2451
|
+
instincts.push({
|
|
2452
|
+
title: `Workflow: ${seq.description}`,
|
|
2453
|
+
description: `Repeats sequence "${seq.description}" (${seq.count}x observed)`,
|
|
2454
|
+
domain,
|
|
2455
|
+
confidence: Math.min(0.85, 0.5 + seq.count * 0.05),
|
|
2456
|
+
patternIDs: [seq.id]
|
|
2457
|
+
});
|
|
2458
|
+
}
|
|
2459
|
+
return instincts;
|
|
2460
|
+
}
|
|
2461
|
+
function extractHotspotInstincts(topFiles) {
|
|
2462
|
+
if (topFiles.length < 3) return [];
|
|
2463
|
+
const hotspots = topFiles.slice(0, 5);
|
|
2464
|
+
const totalMods = hotspots.reduce((sum, [, count]) => sum + count, 0);
|
|
2465
|
+
if (totalMods < 10) return [];
|
|
2466
|
+
return [
|
|
2467
|
+
{
|
|
2468
|
+
title: "Hotspot files identified",
|
|
2469
|
+
description: `Frequently modified files: ${hotspots.map(([f]) => f.split("/").pop()).join(", ")}`,
|
|
2470
|
+
domain: "file-organization",
|
|
2471
|
+
confidence: Math.min(0.75, 0.4 + totalMods * 0.02),
|
|
2472
|
+
patternIDs: []
|
|
2473
|
+
}
|
|
2474
|
+
];
|
|
2475
|
+
}
|
|
2476
|
+
async function analyzePatternsWithLLM(ctx, input, config) {
|
|
2477
|
+
if (!ctx.client) {
|
|
2478
|
+
return analyzePatterns(input);
|
|
2479
|
+
}
|
|
2480
|
+
const observerConfig = "observer" in config ? config.observer : void 0;
|
|
2481
|
+
const modelId = observerConfig?.model || DEFAULT_OBSERVER_MODEL;
|
|
2482
|
+
const providerId = observerConfig?.provider || modelId.split("/")[0] || DEFAULT_OBSERVER_PROVIDER;
|
|
2483
|
+
try {
|
|
2484
|
+
const prompt = buildObserverPrompt(input);
|
|
2485
|
+
const session = await ctx.client.session.create({
|
|
2486
|
+
body: { title: "Mimic Observer Analysis" }
|
|
2487
|
+
});
|
|
2488
|
+
if (!session.data?.id) {
|
|
2489
|
+
return analyzePatterns(input);
|
|
2490
|
+
}
|
|
2491
|
+
const response = await ctx.client.session.prompt({
|
|
2492
|
+
path: { id: session.data.id },
|
|
2493
|
+
body: {
|
|
2494
|
+
model: {
|
|
2495
|
+
providerID: providerId,
|
|
2496
|
+
modelID: modelId
|
|
2497
|
+
},
|
|
2498
|
+
parts: [{ type: "text", text: prompt }]
|
|
2499
|
+
}
|
|
2500
|
+
});
|
|
2501
|
+
const responseParts = response.data?.parts || [];
|
|
2502
|
+
let responseText = "";
|
|
2503
|
+
for (const part of responseParts) {
|
|
2504
|
+
if ("text" in part && typeof part.text === "string") {
|
|
2505
|
+
responseText = part.text;
|
|
2506
|
+
break;
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
if (!responseText) {
|
|
2510
|
+
await ctx.client.session.delete({ path: { id: session.data.id } }).catch(() => {
|
|
2511
|
+
});
|
|
2512
|
+
return analyzePatterns(input);
|
|
2513
|
+
}
|
|
2514
|
+
const llmInstincts = parseInstinctsFromResponse(responseText);
|
|
2515
|
+
await ctx.client.session.delete({ path: { id: session.data.id } }).catch(() => {
|
|
2516
|
+
});
|
|
2517
|
+
return llmInstincts.length > 0 ? llmInstincts : analyzePatterns(input);
|
|
2518
|
+
} catch {
|
|
2519
|
+
return analyzePatterns(input);
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
async function shouldRunObserver(ctx) {
|
|
2523
|
+
const state = await ctx.stateManager.read();
|
|
2524
|
+
if (!state.preferences.learningEnabled) {
|
|
2525
|
+
return false;
|
|
2526
|
+
}
|
|
2527
|
+
if (!state.evolution.lastObserverRun) {
|
|
2528
|
+
return true;
|
|
2529
|
+
}
|
|
2530
|
+
const lastRun = new Date(state.evolution.lastObserverRun).getTime();
|
|
2531
|
+
const now = Date.now();
|
|
2532
|
+
return now - lastRun >= OBSERVER_COOLDOWN_MS;
|
|
2533
|
+
}
|
|
2534
|
+
async function runObserver(ctx) {
|
|
2535
|
+
const state = await ctx.stateManager.read();
|
|
2536
|
+
const config = await loadMimicConfig().catch(() => ({}));
|
|
2537
|
+
const newInstincts = [];
|
|
2538
|
+
const recentPatterns = state.patterns.filter((p) => {
|
|
2539
|
+
const age = Date.now() - p.lastSeen;
|
|
2540
|
+
return age < 7 * 24 * 60 * 60 * 1e3;
|
|
2541
|
+
});
|
|
2542
|
+
if (recentPatterns.length === 0) {
|
|
2543
|
+
state.evolution.lastObserverRun = (/* @__PURE__ */ new Date()).toISOString();
|
|
2544
|
+
await ctx.stateManager.save(state);
|
|
2545
|
+
return [];
|
|
2546
|
+
}
|
|
2547
|
+
const mimicDir = ctx.stateManager.getInstinctsDir().replace("/instincts", "");
|
|
2548
|
+
const observationLog = new ObservationLog(mimicDir);
|
|
2549
|
+
const recentObs = await observationLog.getRecentObservations(7);
|
|
2550
|
+
const observations = recentObs.map((o) => `[${o.type}] ${JSON.stringify(o.data)}`);
|
|
2551
|
+
const topFiles = Object.entries(state.statistics.filesModified).sort(([, a], [, b]) => b - a).slice(0, 10);
|
|
2552
|
+
const input = {
|
|
2553
|
+
patterns: recentPatterns,
|
|
2554
|
+
recentSessions: [],
|
|
2555
|
+
observations,
|
|
2556
|
+
statistics: {
|
|
2557
|
+
totalSessions: state.statistics.totalSessions,
|
|
2558
|
+
totalToolCalls: state.statistics.totalToolCalls,
|
|
2559
|
+
topFiles
|
|
2560
|
+
}
|
|
1008
2561
|
};
|
|
2562
|
+
const observerCfg = "observer" in config ? config.observer : void 0;
|
|
2563
|
+
const useLLM = observerCfg?.enabled !== false && (observerCfg?.model || ctx.client);
|
|
2564
|
+
const rawInstincts = useLLM ? await analyzePatternsWithLLM(ctx, input, config) : analyzePatterns(input);
|
|
2565
|
+
for (const raw of rawInstincts) {
|
|
2566
|
+
const domain = normalizeDomain(raw.domain);
|
|
2567
|
+
const id = generateDeterministicId(domain, raw.title);
|
|
2568
|
+
if (await ctx.stateManager.hasInstinct(id)) {
|
|
2569
|
+
continue;
|
|
2570
|
+
}
|
|
2571
|
+
const instinct = {
|
|
2572
|
+
id,
|
|
2573
|
+
title: raw.title,
|
|
2574
|
+
description: raw.description,
|
|
2575
|
+
domain,
|
|
2576
|
+
confidence: raw.confidence,
|
|
2577
|
+
status: "approved",
|
|
2578
|
+
source: "personal",
|
|
2579
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2580
|
+
evidence: {
|
|
2581
|
+
patternIDs: raw.patternIDs
|
|
2582
|
+
}
|
|
2583
|
+
};
|
|
2584
|
+
await ctx.stateManager.writeInstinct(instinct);
|
|
2585
|
+
newInstincts.push(instinct);
|
|
2586
|
+
}
|
|
2587
|
+
state.evolution.lastObserverRun = (/* @__PURE__ */ new Date()).toISOString();
|
|
2588
|
+
await ctx.stateManager.save(state);
|
|
2589
|
+
return newInstincts;
|
|
1009
2590
|
}
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
2591
|
+
|
|
2592
|
+
// src/lib/git.ts
|
|
2593
|
+
import { execSync } from "child_process";
|
|
2594
|
+
function getGitHistory(directory, limit = 50) {
|
|
2595
|
+
try {
|
|
2596
|
+
const result = execSync(`git log --oneline -n ${limit}`, {
|
|
2597
|
+
cwd: directory,
|
|
2598
|
+
encoding: "utf-8",
|
|
2599
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2600
|
+
});
|
|
2601
|
+
return result.trim().split("\n").filter(Boolean);
|
|
2602
|
+
} catch {
|
|
2603
|
+
return [];
|
|
1016
2604
|
}
|
|
1017
2605
|
}
|
|
1018
|
-
function
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
pattern: pattern.description
|
|
1027
|
-
}),
|
|
1028
|
-
reason: ctx.i18n.t("evolution.suggest.tool.reason", { count: pattern.count }),
|
|
1029
|
-
pattern
|
|
1030
|
-
};
|
|
2606
|
+
function getRecentlyModifiedFiles(directory) {
|
|
2607
|
+
try {
|
|
2608
|
+
const result = execSync(
|
|
2609
|
+
"git diff --name-only HEAD~10 HEAD 2>/dev/null || git diff --name-only",
|
|
2610
|
+
{
|
|
2611
|
+
cwd: directory,
|
|
2612
|
+
encoding: "utf-8",
|
|
2613
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1031
2614
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
2615
|
+
);
|
|
2616
|
+
return result.trim().split("\n").filter(Boolean);
|
|
2617
|
+
} catch {
|
|
2618
|
+
return [];
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
function getCommitMessages(directory, limit = 20) {
|
|
2622
|
+
try {
|
|
2623
|
+
const result = execSync(`git log --format=%s -n ${limit}`, {
|
|
2624
|
+
cwd: directory,
|
|
2625
|
+
encoding: "utf-8",
|
|
2626
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2627
|
+
});
|
|
2628
|
+
return result.trim().split("\n").filter(Boolean);
|
|
2629
|
+
} catch {
|
|
2630
|
+
return [];
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
function detectCommitPatterns(messages) {
|
|
2634
|
+
const patterns = /* @__PURE__ */ new Map();
|
|
2635
|
+
for (const msg of messages) {
|
|
2636
|
+
const normalized = msg.toLowerCase().replace(/\s+/g, " ").trim();
|
|
2637
|
+
patterns.set(normalized, (patterns.get(normalized) || 0) + 1);
|
|
2638
|
+
}
|
|
2639
|
+
return patterns;
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
// src/modules/observation/patterns.ts
|
|
2643
|
+
async function detectPatterns(ctx) {
|
|
2644
|
+
const state = await ctx.stateManager.read();
|
|
2645
|
+
const newPatterns = [];
|
|
2646
|
+
const commitMessages = getCommitMessages(ctx.directory);
|
|
2647
|
+
const commitPatterns = detectCommitPatterns(commitMessages);
|
|
2648
|
+
for (const [msg, count] of commitPatterns) {
|
|
2649
|
+
if (count >= 3) {
|
|
2650
|
+
const existing = state.patterns.find((p) => p.type === "commit" && p.description === msg);
|
|
2651
|
+
if (!existing) {
|
|
2652
|
+
newPatterns.push({
|
|
2653
|
+
id: generateId(),
|
|
2654
|
+
type: "commit",
|
|
2655
|
+
description: msg,
|
|
2656
|
+
count,
|
|
2657
|
+
firstSeen: Date.now(),
|
|
2658
|
+
lastSeen: Date.now(),
|
|
2659
|
+
surfaced: false,
|
|
2660
|
+
examples: []
|
|
2661
|
+
});
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
const fileStats = state.statistics.filesModified;
|
|
2666
|
+
for (const [file, count] of Object.entries(fileStats)) {
|
|
2667
|
+
if (count >= 5) {
|
|
2668
|
+
const existing = state.patterns.find((p) => p.type === "file" && p.description === file);
|
|
2669
|
+
if (!existing) {
|
|
2670
|
+
newPatterns.push({
|
|
2671
|
+
id: generateId(),
|
|
2672
|
+
type: "file",
|
|
2673
|
+
description: file,
|
|
2674
|
+
count,
|
|
2675
|
+
firstSeen: Date.now(),
|
|
2676
|
+
lastSeen: Date.now(),
|
|
2677
|
+
surfaced: false,
|
|
2678
|
+
examples: []
|
|
2679
|
+
});
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
return newPatterns;
|
|
2684
|
+
}
|
|
2685
|
+
async function surfacePatterns(ctx) {
|
|
2686
|
+
const state = await ctx.stateManager.read();
|
|
2687
|
+
const suggestions = [];
|
|
2688
|
+
for (const pattern of state.patterns) {
|
|
2689
|
+
if (pattern.surfaced) continue;
|
|
2690
|
+
if (pattern.count < state.preferences.minPatternCount) continue;
|
|
2691
|
+
let suggestion = "";
|
|
2692
|
+
switch (pattern.type) {
|
|
2693
|
+
case "commit":
|
|
2694
|
+
suggestion = ctx.i18n.t("suggest.commit", {
|
|
2695
|
+
pattern: pattern.description,
|
|
2696
|
+
count: pattern.count
|
|
2697
|
+
});
|
|
2698
|
+
break;
|
|
2699
|
+
case "file":
|
|
2700
|
+
suggestion = ctx.i18n.t("suggest.file", {
|
|
2701
|
+
pattern: pattern.description,
|
|
2702
|
+
count: pattern.count
|
|
2703
|
+
});
|
|
2704
|
+
break;
|
|
2705
|
+
case "tool":
|
|
2706
|
+
suggestion = ctx.i18n.t("suggest.tool", {
|
|
2707
|
+
pattern: pattern.description,
|
|
2708
|
+
count: pattern.count
|
|
2709
|
+
});
|
|
2710
|
+
break;
|
|
2711
|
+
case "sequence":
|
|
2712
|
+
suggestion = ctx.i18n.t("suggest.sequence", {
|
|
2713
|
+
pattern: pattern.description,
|
|
2714
|
+
count: pattern.count
|
|
2715
|
+
});
|
|
2716
|
+
break;
|
|
2717
|
+
}
|
|
2718
|
+
suggestions.push(suggestion);
|
|
2719
|
+
}
|
|
2720
|
+
return suggestions;
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
// src/tools/automation.ts
|
|
2724
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2725
|
+
var createAutomationTools = (ctx) => {
|
|
2726
|
+
const { stateManager, directory, i18n, i18nPromise } = ctx;
|
|
2727
|
+
const mimicDir = stateManager.getInstinctsDir().replace("/instincts", "");
|
|
2728
|
+
const observationLog = new ObservationLog(mimicDir);
|
|
2729
|
+
const sessionMemory = new SessionMemoryManager(mimicDir, stateManager.getSessionsDir());
|
|
2730
|
+
const skillGenerator = new SkillGenerator(directory);
|
|
2731
|
+
return {
|
|
2732
|
+
"mimic:observations": tool({
|
|
2733
|
+
description: i18n.t("tool.observations.description"),
|
|
2734
|
+
args: {
|
|
2735
|
+
limit: tool.schema.number().optional().describe(i18n.t("tool.observations.args.limit")),
|
|
2736
|
+
types: tool.schema.string().optional().describe(i18n.t("tool.observations.args.types"))
|
|
2737
|
+
},
|
|
2738
|
+
async execute(args) {
|
|
2739
|
+
const i18n2 = await i18nPromise;
|
|
2740
|
+
const types = args.types ? args.types.split(",").map((t) => t.trim()) : void 0;
|
|
2741
|
+
const observations = await observationLog.query({
|
|
2742
|
+
types,
|
|
2743
|
+
limit: args.limit ?? 20
|
|
2744
|
+
});
|
|
2745
|
+
if (observations.length === 0) {
|
|
2746
|
+
return i18n2.t("observations.empty");
|
|
2747
|
+
}
|
|
2748
|
+
const logSize = await observationLog.getLogSize();
|
|
2749
|
+
const sizeStr = logSize < 1024 ? `${logSize}B` : logSize < 1024 * 1024 ? `${(logSize / 1024).toFixed(1)}KB` : `${(logSize / (1024 * 1024)).toFixed(1)}MB`;
|
|
2750
|
+
let output = `${i18n2.t("observations.title")}
|
|
2751
|
+
|
|
2752
|
+
`;
|
|
2753
|
+
output += `${i18n2.t("observations.stats", { count: observations.length, size: sizeStr })}
|
|
2754
|
+
|
|
2755
|
+
`;
|
|
2756
|
+
for (const obs of observations.slice(0, 20)) {
|
|
2757
|
+
const time = new Date(obs.timestamp).toLocaleTimeString();
|
|
2758
|
+
const data = JSON.stringify(obs.data).slice(0, 60);
|
|
2759
|
+
output += `- \`${time}\` **${obs.type}**: ${data}...
|
|
2760
|
+
`;
|
|
2761
|
+
}
|
|
2762
|
+
return output;
|
|
2763
|
+
}
|
|
2764
|
+
}),
|
|
2765
|
+
"mimic:session-context": tool({
|
|
2766
|
+
description: i18n.t("tool.session_context.description"),
|
|
2767
|
+
args: {},
|
|
2768
|
+
async execute() {
|
|
2769
|
+
const i18n2 = await i18nPromise;
|
|
2770
|
+
const memories = await sessionMemory.getRecentMemories(5);
|
|
2771
|
+
if (memories.length === 0) {
|
|
2772
|
+
return i18n2.t("session_context.empty");
|
|
2773
|
+
}
|
|
2774
|
+
let output = `${i18n2.t("session_context.title")}
|
|
2775
|
+
|
|
2776
|
+
`;
|
|
2777
|
+
const summary = await sessionMemory.getContextSummary();
|
|
2778
|
+
output += `${summary}
|
|
2779
|
+
|
|
2780
|
+
`;
|
|
2781
|
+
const patterns = await sessionMemory.analyzeCrossSessionPatterns();
|
|
2782
|
+
if (patterns.length > 0) {
|
|
2783
|
+
output += `${i18n2.t("session_context.patterns_title")}
|
|
2784
|
+
`;
|
|
2785
|
+
for (const pattern of patterns.slice(0, 5)) {
|
|
2786
|
+
const confidence = "\u25CF".repeat(Math.round(pattern.confidence * 5)) + "\u25CB".repeat(5 - Math.round(pattern.confidence * 5));
|
|
2787
|
+
output += `- [${confidence}] ${pattern.description} (${pattern.frequency}x)
|
|
2788
|
+
`;
|
|
2789
|
+
}
|
|
2790
|
+
output += "\n";
|
|
2791
|
+
}
|
|
2792
|
+
const hints = await sessionMemory.getContinuityHints();
|
|
2793
|
+
if (hints.length > 0) {
|
|
2794
|
+
output += "**Hints for this session:**\n";
|
|
2795
|
+
for (const hint of hints) {
|
|
2796
|
+
output += `- ${hint}
|
|
2797
|
+
`;
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
return output;
|
|
2801
|
+
}
|
|
2802
|
+
}),
|
|
2803
|
+
"mimic:generate-skills": tool({
|
|
2804
|
+
description: i18n.t("tool.generate_skills.description"),
|
|
2805
|
+
args: {},
|
|
2806
|
+
async execute() {
|
|
2807
|
+
const i18n2 = await i18nPromise;
|
|
2808
|
+
const innerCtx = { stateManager, directory, i18n: i18n2 };
|
|
2809
|
+
await skillGenerator.initialize();
|
|
2810
|
+
const skills = await skillGenerator.generateAllEligibleSkills(innerCtx);
|
|
2811
|
+
if (skills.length === 0) {
|
|
2812
|
+
return i18n2.t("generate_skills.empty");
|
|
2813
|
+
}
|
|
2814
|
+
let output = `${i18n2.t("generate_skills.title")}
|
|
2815
|
+
|
|
2816
|
+
`;
|
|
2817
|
+
output += `${i18n2.t("generate_skills.success", { count: skills.length })}
|
|
2818
|
+
|
|
2819
|
+
`;
|
|
2820
|
+
for (const skill of skills) {
|
|
2821
|
+
output += `### \u{1F4DA} ${skill.name}
|
|
2822
|
+
`;
|
|
2823
|
+
output += `- **Domain**: ${skill.metadata.domain}
|
|
2824
|
+
`;
|
|
2825
|
+
output += `- **Path**: \`${skill.path}\`
|
|
2826
|
+
|
|
2827
|
+
`;
|
|
2828
|
+
}
|
|
2829
|
+
return output;
|
|
2830
|
+
}
|
|
2831
|
+
})
|
|
2832
|
+
};
|
|
2833
|
+
};
|
|
2834
|
+
|
|
2835
|
+
// src/tools/core.ts
|
|
2836
|
+
import { tool as tool2 } from "@opencode-ai/plugin";
|
|
2837
|
+
|
|
2838
|
+
// src/utils/format.ts
|
|
2839
|
+
import { differenceInHours, format, formatDistanceToNow } from "date-fns";
|
|
2840
|
+
import { enUS, ko } from "date-fns/locale";
|
|
2841
|
+
function analyzeTimeSinceLastSession(lastSession) {
|
|
2842
|
+
if (!lastSession) return "first-time";
|
|
2843
|
+
const hours = differenceInHours(/* @__PURE__ */ new Date(), new Date(lastSession));
|
|
2844
|
+
if (hours < 1) return "continuing";
|
|
2845
|
+
if (hours < 24) return "same-day";
|
|
2846
|
+
if (hours < 72) return "short-break";
|
|
2847
|
+
if (hours < 168) return "week-break";
|
|
2848
|
+
return "long-break";
|
|
2849
|
+
}
|
|
2850
|
+
function formatJourney(ctx, state, gitHistory) {
|
|
2851
|
+
const milestones = state.journey.milestones.slice(-10);
|
|
2852
|
+
const observations = state.journey.observations.slice(-5);
|
|
2853
|
+
const locale = ctx.i18n.language === "ko-KR" ? ko : enUS;
|
|
2854
|
+
let output = `${ctx.i18n.t("journey.title", { project: state.project.name })}
|
|
2855
|
+
|
|
2856
|
+
`;
|
|
2857
|
+
output += `${ctx.i18n.t("journey.subtitle")}
|
|
2858
|
+
|
|
2859
|
+
`;
|
|
2860
|
+
output += `${ctx.i18n.t("journey.sessions_survived", {
|
|
2861
|
+
count: state.journey.sessionCount
|
|
2862
|
+
})}
|
|
2863
|
+
`;
|
|
2864
|
+
output += `${ctx.i18n.t("journey.first_encounter", {
|
|
2865
|
+
date: format(state.project.firstSession, "yyyy-MM-dd")
|
|
2866
|
+
})}
|
|
2867
|
+
`;
|
|
2868
|
+
output += `${ctx.i18n.t("journey.abilities_gained", {
|
|
2869
|
+
count: state.evolution.capabilities.length
|
|
2870
|
+
})}
|
|
2871
|
+
|
|
2872
|
+
`;
|
|
2873
|
+
if (state.project.stack && state.project.stack.length > 0) {
|
|
2874
|
+
output += `${ctx.i18n.t("journey.treasures", {
|
|
2875
|
+
stack: state.project.stack.join(", ")
|
|
2876
|
+
})}
|
|
2877
|
+
`;
|
|
2878
|
+
}
|
|
2879
|
+
if (state.project.focus) {
|
|
2880
|
+
output += `${ctx.i18n.t("journey.current_hunt", { focus: state.project.focus })}
|
|
2881
|
+
`;
|
|
2882
|
+
}
|
|
2883
|
+
output += "\n";
|
|
2884
|
+
if (milestones.length > 0) {
|
|
2885
|
+
output += `${ctx.i18n.t("journey.victories")}
|
|
2886
|
+
`;
|
|
2887
|
+
for (const m of milestones) {
|
|
2888
|
+
const timeAgo = formatDistanceToNow(new Date(m.timestamp), { addSuffix: true, locale });
|
|
2889
|
+
output += `- ${m.milestone} (${timeAgo})
|
|
2890
|
+
`;
|
|
2891
|
+
}
|
|
2892
|
+
output += "\n";
|
|
2893
|
+
}
|
|
2894
|
+
if (observations.length > 0) {
|
|
2895
|
+
output += `${ctx.i18n.t("journey.witnessed")}
|
|
2896
|
+
`;
|
|
2897
|
+
for (const o of observations) {
|
|
2898
|
+
output += `- ${o.observation}
|
|
2899
|
+
`;
|
|
2900
|
+
}
|
|
2901
|
+
output += "\n";
|
|
2902
|
+
}
|
|
2903
|
+
if (state.evolution.capabilities.length > 0) {
|
|
2904
|
+
output += `${ctx.i18n.t("journey.powers")}
|
|
2905
|
+
`;
|
|
2906
|
+
for (const cap of state.evolution.capabilities.slice(-5)) {
|
|
2907
|
+
output += `- **${cap.name}** (${formatCapabilityType(
|
|
2908
|
+
ctx.i18n,
|
|
2909
|
+
cap.type
|
|
2910
|
+
)}): ${cap.description}
|
|
2911
|
+
`;
|
|
2912
|
+
}
|
|
2913
|
+
output += "\n";
|
|
2914
|
+
}
|
|
2915
|
+
if (gitHistory.length > 0) {
|
|
2916
|
+
output += `${ctx.i18n.t("journey.scrolls")}
|
|
2917
|
+
`;
|
|
2918
|
+
for (const commit of gitHistory.slice(0, 5)) {
|
|
2919
|
+
output += `- ${commit}
|
|
2920
|
+
`;
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
return output;
|
|
2924
|
+
}
|
|
2925
|
+
function formatDuration(ms) {
|
|
2926
|
+
const minutes = Math.round(ms / 1e3 / 60);
|
|
2927
|
+
if (minutes < 60) return `${minutes}min`;
|
|
2928
|
+
const hours = Math.floor(minutes / 60);
|
|
2929
|
+
const remainingMinutes = minutes % 60;
|
|
2930
|
+
return `${hours}h ${remainingMinutes}min`;
|
|
2931
|
+
}
|
|
2932
|
+
function formatGrowAnalysis(ctx, state, _gitHistory, recentFiles) {
|
|
2933
|
+
let output = `${ctx.i18n.t("grow.title", { project: state.project.name })}
|
|
2934
|
+
|
|
2935
|
+
`;
|
|
2936
|
+
output += `${ctx.i18n.t("grow.subtitle")}
|
|
2937
|
+
|
|
2938
|
+
`;
|
|
2939
|
+
const fileFrequency = Object.entries(state.statistics.filesModified).sort(([, a], [, b]) => b - a).slice(0, 10);
|
|
2940
|
+
if (fileFrequency.length > 0) {
|
|
2941
|
+
output += `${ctx.i18n.t("grow.feeding_grounds")}
|
|
2942
|
+
`;
|
|
2943
|
+
for (const [file, count] of fileFrequency) {
|
|
2944
|
+
output += `- \`${file}\` ${ctx.i18n.t("grow.files_modified", { count })}
|
|
2945
|
+
`;
|
|
2946
|
+
}
|
|
2947
|
+
output += "\n";
|
|
2948
|
+
}
|
|
2949
|
+
const toolPatterns = state.patterns.filter((p) => p.type === "tool").sort((a, b) => b.count - a.count);
|
|
2950
|
+
if (toolPatterns.length > 0) {
|
|
2951
|
+
output += `${ctx.i18n.t("grow.favorite_prey")}
|
|
2952
|
+
`;
|
|
2953
|
+
for (const p of toolPatterns.slice(0, 5)) {
|
|
2954
|
+
output += `- ${p.description}: ${p.count}
|
|
2955
|
+
`;
|
|
2956
|
+
}
|
|
2957
|
+
output += "\n";
|
|
2958
|
+
}
|
|
2959
|
+
if (recentFiles.length > 0) {
|
|
2960
|
+
const dirCount = /* @__PURE__ */ new Map();
|
|
2961
|
+
for (const file of recentFiles) {
|
|
2962
|
+
const dir = file.split("/").slice(0, -1).join("/") || ".";
|
|
2963
|
+
dirCount.set(dir, (dirCount.get(dir) || 0) + 1);
|
|
2964
|
+
}
|
|
2965
|
+
const sortedDirs = [...dirCount.entries()].sort((a, b) => b[1] - a[1]);
|
|
2966
|
+
output += `${ctx.i18n.t("grow.hunting_grounds")}
|
|
2967
|
+
`;
|
|
2968
|
+
for (const [dir, count] of sortedDirs.slice(0, 5)) {
|
|
2969
|
+
output += `- \`${dir}/\` ${ctx.i18n.t("grow.prey", { count })}
|
|
2970
|
+
`;
|
|
2971
|
+
}
|
|
2972
|
+
output += "\n";
|
|
2973
|
+
}
|
|
2974
|
+
output += `${ctx.i18n.t("grow.questions")}
|
|
2975
|
+
`;
|
|
2976
|
+
output += `${ctx.i18n.t("grow.question1")}
|
|
2977
|
+
`;
|
|
2978
|
+
output += `${ctx.i18n.t("grow.question2")}
|
|
2979
|
+
`;
|
|
2980
|
+
output += `${ctx.i18n.t("grow.question3")}
|
|
2981
|
+
`;
|
|
2982
|
+
if (state.project.focus) {
|
|
2983
|
+
output += `
|
|
2984
|
+
${ctx.i18n.t("grow.current_hunt", { focus: state.project.focus })}
|
|
2985
|
+
`;
|
|
2986
|
+
}
|
|
2987
|
+
return output;
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
// src/tools/core.ts
|
|
2991
|
+
var createCoreTools = (ctx) => {
|
|
2992
|
+
const { stateManager, directory, toolCalls, i18n, i18nPromise } = ctx;
|
|
2993
|
+
return {
|
|
2994
|
+
"mimic:init": tool2({
|
|
2995
|
+
description: i18n.t("tool.init.description"),
|
|
2996
|
+
args: {},
|
|
2997
|
+
async execute() {
|
|
2998
|
+
const i18n2 = await i18nPromise;
|
|
2999
|
+
const state = await stateManager.read();
|
|
3000
|
+
const isFirstTime = state.journey.sessionCount <= 1;
|
|
3001
|
+
if (isFirstTime) {
|
|
3002
|
+
return i18n2.t("init.first_time", { project: state.project.name });
|
|
3003
|
+
}
|
|
3004
|
+
const timeSince = analyzeTimeSinceLastSession(state.journey.lastSession);
|
|
3005
|
+
const recentObs = state.journey.observations.slice(-3);
|
|
3006
|
+
let greeting = `${i18n2.t("init.returning.header")}
|
|
3007
|
+
|
|
3008
|
+
`;
|
|
3009
|
+
greeting += `${i18n2.t("init.returning.welcome", { project: state.project.name })}
|
|
3010
|
+
|
|
3011
|
+
`;
|
|
3012
|
+
greeting += `${i18n2.t("init.returning.stats", {
|
|
3013
|
+
sessions: state.journey.sessionCount,
|
|
3014
|
+
patterns: state.patterns.length
|
|
3015
|
+
})}
|
|
3016
|
+
|
|
3017
|
+
`;
|
|
3018
|
+
if (timeSince === "long-break") {
|
|
3019
|
+
greeting += `${i18n2.t("init.returning.long_break")}
|
|
3020
|
+
|
|
3021
|
+
`;
|
|
3022
|
+
}
|
|
3023
|
+
if (recentObs.length > 0) {
|
|
3024
|
+
greeting += `${i18n2.t("init.returning.recent_obs_title")}
|
|
3025
|
+
`;
|
|
3026
|
+
for (const o of recentObs) {
|
|
3027
|
+
greeting += `- ${o.observation}
|
|
3028
|
+
`;
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
return greeting;
|
|
3032
|
+
}
|
|
3033
|
+
}),
|
|
3034
|
+
"mimic:status": tool2({
|
|
3035
|
+
description: i18n.t("tool.status.description"),
|
|
3036
|
+
args: {},
|
|
3037
|
+
async execute() {
|
|
3038
|
+
const i18n2 = await i18nPromise;
|
|
3039
|
+
const innerCtx = { stateManager, directory, i18n: i18n2 };
|
|
3040
|
+
const state = await stateManager.read();
|
|
3041
|
+
const recentFiles = getRecentlyModifiedFiles(directory);
|
|
3042
|
+
const gitHistory = getGitHistory(directory, 5);
|
|
3043
|
+
let output = `${i18n2.t("status.title", { project: state.project.name })}
|
|
3044
|
+
|
|
3045
|
+
`;
|
|
3046
|
+
output += `${i18n2.t("status.session", { count: state.journey.sessionCount })}
|
|
3047
|
+
`;
|
|
3048
|
+
output += `${i18n2.t("status.patterns", {
|
|
3049
|
+
total: state.patterns.length,
|
|
3050
|
+
surfaced: state.patterns.filter((p) => p.surfaced).length
|
|
3051
|
+
})}
|
|
3052
|
+
`;
|
|
3053
|
+
output += `${i18n2.t("status.tool_calls", { count: toolCalls.length })}
|
|
3054
|
+
|
|
3055
|
+
`;
|
|
3056
|
+
if (recentFiles.length > 0) {
|
|
3057
|
+
output += `${i18n2.t("status.recent_files")}
|
|
3058
|
+
`;
|
|
3059
|
+
for (const f of recentFiles.slice(0, 5)) {
|
|
3060
|
+
output += `- ${f}
|
|
3061
|
+
`;
|
|
3062
|
+
}
|
|
3063
|
+
output += "\n";
|
|
3064
|
+
}
|
|
3065
|
+
if (gitHistory.length > 0) {
|
|
3066
|
+
output += `${i18n2.t("status.recent_commits")}
|
|
3067
|
+
`;
|
|
3068
|
+
for (const c of gitHistory) {
|
|
3069
|
+
output += `- ${c}
|
|
3070
|
+
`;
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
const suggestions = await surfacePatterns(innerCtx);
|
|
3074
|
+
if (suggestions.length > 0) {
|
|
3075
|
+
output += `
|
|
3076
|
+
${i18n2.t("status.suggestions")}
|
|
3077
|
+
`;
|
|
3078
|
+
for (const s of suggestions) {
|
|
3079
|
+
output += `- ${s}
|
|
3080
|
+
`;
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
return output;
|
|
1044
3084
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
pattern
|
|
1056
|
-
};
|
|
3085
|
+
}),
|
|
3086
|
+
"mimic:journey": tool2({
|
|
3087
|
+
description: i18n.t("tool.journey.description"),
|
|
3088
|
+
args: {},
|
|
3089
|
+
async execute() {
|
|
3090
|
+
const i18n2 = await i18nPromise;
|
|
3091
|
+
const innerCtx = { stateManager, directory, i18n: i18n2 };
|
|
3092
|
+
const state = await stateManager.read();
|
|
3093
|
+
const gitHistory = getGitHistory(directory, 10);
|
|
3094
|
+
return formatJourney(innerCtx, state, gitHistory);
|
|
1057
3095
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
3096
|
+
}),
|
|
3097
|
+
"mimic:patterns": tool2({
|
|
3098
|
+
description: i18n.t("tool.patterns.description"),
|
|
3099
|
+
args: {},
|
|
3100
|
+
async execute() {
|
|
3101
|
+
const i18n2 = await i18nPromise;
|
|
3102
|
+
const state = await stateManager.read();
|
|
3103
|
+
if (state.patterns.length === 0) {
|
|
3104
|
+
return i18n2.t("patterns.none");
|
|
3105
|
+
}
|
|
3106
|
+
let output = `${i18n2.t("patterns.title")}
|
|
3107
|
+
|
|
3108
|
+
`;
|
|
3109
|
+
output += `${i18n2.t("patterns.total", { count: state.patterns.length })}
|
|
3110
|
+
|
|
3111
|
+
`;
|
|
3112
|
+
const byType = /* @__PURE__ */ new Map();
|
|
3113
|
+
for (const p of state.patterns) {
|
|
3114
|
+
const list = byType.get(p.type) || [];
|
|
3115
|
+
list.push(p);
|
|
3116
|
+
byType.set(p.type, list);
|
|
3117
|
+
}
|
|
3118
|
+
for (const [type, patterns] of byType) {
|
|
3119
|
+
output += `${i18n2.t("patterns.section", {
|
|
3120
|
+
type: formatPatternType(i18n2, type)
|
|
3121
|
+
})}
|
|
3122
|
+
`;
|
|
3123
|
+
for (const p of patterns.slice(0, 10)) {
|
|
3124
|
+
const status = p.surfaced ? "\u2713" : "\u25CB";
|
|
3125
|
+
output += `${status} **${p.description}** (${p.count}x)
|
|
3126
|
+
`;
|
|
3127
|
+
}
|
|
3128
|
+
output += "\n";
|
|
3129
|
+
}
|
|
3130
|
+
return output;
|
|
1070
3131
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
3132
|
+
}),
|
|
3133
|
+
"mimic:grow": tool2({
|
|
3134
|
+
description: i18n.t("tool.grow.description"),
|
|
3135
|
+
args: {},
|
|
3136
|
+
async execute() {
|
|
3137
|
+
const i18n2 = await i18nPromise;
|
|
3138
|
+
const innerCtx = { stateManager, directory, i18n: i18n2 };
|
|
3139
|
+
const state = await stateManager.read();
|
|
3140
|
+
const gitHistory = getGitHistory(directory, 20);
|
|
3141
|
+
const recentFiles = getRecentlyModifiedFiles(directory);
|
|
3142
|
+
return formatGrowAnalysis(innerCtx, state, gitHistory, recentFiles);
|
|
1081
3143
|
}
|
|
1082
|
-
break;
|
|
1083
|
-
}
|
|
1084
|
-
return null;
|
|
1085
|
-
}
|
|
1086
|
-
async function getEvolutionSuggestions(ctx) {
|
|
1087
|
-
const state = await ctx.stateManager.read();
|
|
1088
|
-
const suggestions = [];
|
|
1089
|
-
for (const pattern of state.patterns) {
|
|
1090
|
-
if (pattern.surfaced) continue;
|
|
1091
|
-
const suggestion = suggestEvolution(pattern, ctx);
|
|
1092
|
-
if (suggestion) {
|
|
1093
|
-
suggestions.push(suggestion);
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
return suggestions;
|
|
1097
|
-
}
|
|
1098
|
-
async function evolveCapability(ctx, suggestion) {
|
|
1099
|
-
const state = await ctx.stateManager.read();
|
|
1100
|
-
const { filePath, content } = await buildEvolutionOutput(ctx, suggestion);
|
|
1101
|
-
await writeFile2(filePath, content, "utf-8");
|
|
1102
|
-
const capability = createCapabilityFromSuggestion(suggestion);
|
|
1103
|
-
updateEvolutionState(state, capability, suggestion);
|
|
1104
|
-
await ctx.stateManager.save(state);
|
|
1105
|
-
await ctx.stateManager.addMilestone(
|
|
1106
|
-
ctx.i18n.t("milestone.evolved", {
|
|
1107
|
-
name: capability.name,
|
|
1108
|
-
type: formatCapabilityType(ctx.i18n, capability.type)
|
|
1109
3144
|
})
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
3145
|
+
};
|
|
3146
|
+
};
|
|
3147
|
+
|
|
3148
|
+
// src/tools/evolution.ts
|
|
3149
|
+
import { tool as tool3 } from "@opencode-ai/plugin";
|
|
3150
|
+
import { format as format2 } from "date-fns";
|
|
3151
|
+
var createEvolutionTools = (ctx) => {
|
|
3152
|
+
const { stateManager, directory, i18n, i18nPromise } = ctx;
|
|
3153
|
+
return {
|
|
3154
|
+
"mimic:evolve": tool3({
|
|
3155
|
+
description: i18n.t("tool.evolve.description"),
|
|
3156
|
+
args: {
|
|
3157
|
+
accept: tool3.schema.string().optional().describe(i18n.t("tool.evolve.args.accept"))
|
|
3158
|
+
},
|
|
3159
|
+
async execute(args) {
|
|
3160
|
+
const i18n2 = await i18nPromise;
|
|
3161
|
+
const innerCtx = { stateManager, directory, i18n: i18n2 };
|
|
3162
|
+
if (args.accept) {
|
|
3163
|
+
const suggestions2 = await getEvolutionSuggestions(innerCtx);
|
|
3164
|
+
const suggestion = suggestions2.find((s) => s.pattern.id === args.accept);
|
|
3165
|
+
if (!suggestion) {
|
|
3166
|
+
return i18n2.t("evolve.no_pattern", { id: args.accept });
|
|
3167
|
+
}
|
|
3168
|
+
const { capability, filePath } = await evolveCapability(innerCtx, suggestion);
|
|
3169
|
+
return `${i18n2.t("evolve.absorbed_header")}
|
|
3170
|
+
|
|
3171
|
+
${formatEvolutionResult(
|
|
3172
|
+
innerCtx,
|
|
3173
|
+
capability,
|
|
3174
|
+
filePath
|
|
3175
|
+
)}`;
|
|
3176
|
+
}
|
|
3177
|
+
const suggestions = await getEvolutionSuggestions(innerCtx);
|
|
3178
|
+
if (suggestions.length === 0) {
|
|
3179
|
+
return i18n2.t("evolve.empty");
|
|
3180
|
+
}
|
|
3181
|
+
let output = `${i18n2.t("evolve.menu_title")}
|
|
1116
3182
|
|
|
1117
3183
|
`;
|
|
1118
|
-
|
|
3184
|
+
output += `${i18n2.t("evolve.menu_intro")}
|
|
3185
|
+
|
|
1119
3186
|
`;
|
|
1120
|
-
|
|
3187
|
+
for (const s of suggestions) {
|
|
3188
|
+
output += `### \u2728 ${s.name}
|
|
1121
3189
|
`;
|
|
1122
|
-
|
|
1123
|
-
|
|
3190
|
+
output += `- **${i18n2.t("evolve.menu_type")}**: ${formatCapabilityType(i18n2, s.type)}
|
|
1124
3191
|
`;
|
|
1125
|
-
|
|
3192
|
+
output += `- **${i18n2.t("evolve.menu_reason")}**: ${s.reason}
|
|
3193
|
+
`;
|
|
3194
|
+
output += `- **${i18n2.t("evolve.menu_pattern_id")}**: \`${s.pattern.id}\`
|
|
1126
3195
|
|
|
1127
3196
|
`;
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
3197
|
+
}
|
|
3198
|
+
output += `
|
|
3199
|
+
${i18n2.t("evolve.menu_footer")}`;
|
|
3200
|
+
return output;
|
|
3201
|
+
}
|
|
3202
|
+
}),
|
|
3203
|
+
"mimic:capabilities": tool3({
|
|
3204
|
+
description: i18n.t("tool.capabilities.description"),
|
|
3205
|
+
args: {},
|
|
3206
|
+
async execute() {
|
|
3207
|
+
const i18n2 = await i18nPromise;
|
|
3208
|
+
const state = await stateManager.read();
|
|
3209
|
+
if (state.evolution.capabilities.length === 0) {
|
|
3210
|
+
return i18n2.t("capabilities.empty");
|
|
3211
|
+
}
|
|
3212
|
+
let output = `${i18n2.t("capabilities.title")}
|
|
3213
|
+
|
|
1132
3214
|
`;
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
result += `${ctx.i18n.t("evolution.result.hook")}
|
|
3215
|
+
output += `${i18n2.t("capabilities.intro")}
|
|
3216
|
+
|
|
1136
3217
|
`;
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
result += `${ctx.i18n.t("evolution.result.skill")}
|
|
3218
|
+
for (const cap of state.evolution.capabilities) {
|
|
3219
|
+
output += `### \u2728 ${cap.name}
|
|
1140
3220
|
`;
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
3221
|
+
output += `- **${i18n2.t("capabilities.type")}**: ${formatCapabilityType(
|
|
3222
|
+
i18n2,
|
|
3223
|
+
cap.type
|
|
3224
|
+
)}
|
|
1144
3225
|
`;
|
|
1145
|
-
|
|
1146
|
-
case "mcp":
|
|
1147
|
-
result += `${ctx.i18n.t("evolution.result.mcp", { name: capability.name })}
|
|
3226
|
+
output += `- **${i18n2.t("capabilities.description")}**: ${cap.description}
|
|
1148
3227
|
`;
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
technicalTerms: true
|
|
1161
|
-
},
|
|
1162
|
-
"semi-technical": {
|
|
1163
|
-
greetingStyle: "casual",
|
|
1164
|
-
detailLevel: "medium",
|
|
1165
|
-
useEmoji: false,
|
|
1166
|
-
technicalTerms: true
|
|
1167
|
-
},
|
|
1168
|
-
"non-technical": {
|
|
1169
|
-
greetingStyle: "formal",
|
|
1170
|
-
detailLevel: "low",
|
|
1171
|
-
useEmoji: true,
|
|
1172
|
-
technicalTerms: false
|
|
1173
|
-
},
|
|
1174
|
-
chaotic: {
|
|
1175
|
-
greetingStyle: "chaotic",
|
|
1176
|
-
detailLevel: "medium",
|
|
1177
|
-
useEmoji: true,
|
|
1178
|
-
technicalTerms: true
|
|
1179
|
-
}
|
|
3228
|
+
output += `- **${i18n2.t("capabilities.consumed")}**: ${format2(
|
|
3229
|
+
new Date(cap.createdAt),
|
|
3230
|
+
"yyyy-MM-dd"
|
|
3231
|
+
)}
|
|
3232
|
+
|
|
3233
|
+
`;
|
|
3234
|
+
}
|
|
3235
|
+
return output;
|
|
3236
|
+
}
|
|
3237
|
+
})
|
|
3238
|
+
};
|
|
1180
3239
|
};
|
|
1181
|
-
function getLevelConfig(level) {
|
|
1182
|
-
return LEVEL_CONFIGS[level ?? "technical"];
|
|
1183
|
-
}
|
|
1184
3240
|
|
|
1185
|
-
// src/tools.ts
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
3241
|
+
// src/tools/instincts.ts
|
|
3242
|
+
import { existsSync as existsSync7 } from "fs";
|
|
3243
|
+
import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
|
|
3244
|
+
import { join as join7 } from "path";
|
|
3245
|
+
import { tool as tool4 } from "@opencode-ai/plugin";
|
|
3246
|
+
import { format as format3 } from "date-fns";
|
|
3247
|
+
var createInstinctTools = (ctx) => {
|
|
3248
|
+
const { stateManager, directory, toolCalls, i18n, i18nPromise } = ctx;
|
|
1189
3249
|
return {
|
|
1190
|
-
"mimic:
|
|
1191
|
-
description:
|
|
1192
|
-
args: {
|
|
1193
|
-
|
|
3250
|
+
"mimic:instincts": tool4({
|
|
3251
|
+
description: i18n.t("tool.instincts.description"),
|
|
3252
|
+
args: {
|
|
3253
|
+
domain: tool4.schema.string().optional().describe(i18n.t("tool.instincts.args.domain"))
|
|
3254
|
+
},
|
|
3255
|
+
async execute(args) {
|
|
1194
3256
|
const i18n2 = await i18nPromise;
|
|
1195
|
-
const
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
return i18n2.t("init.first_time", { project: state.project.name });
|
|
3257
|
+
const instincts = await stateManager.listInstincts();
|
|
3258
|
+
if (instincts.length === 0) {
|
|
3259
|
+
return i18n2.t("instincts.empty");
|
|
1199
3260
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
3261
|
+
let filtered = instincts;
|
|
3262
|
+
if (args.domain) {
|
|
3263
|
+
filtered = instincts.filter((inst) => inst.domain === args.domain);
|
|
3264
|
+
}
|
|
3265
|
+
const byDomain = groupInstinctsByDomain(filtered);
|
|
3266
|
+
let output = `${i18n2.t("instincts.title")}
|
|
1206
3267
|
|
|
1207
3268
|
`;
|
|
1208
|
-
|
|
1209
|
-
sessions: state.journey.sessionCount,
|
|
1210
|
-
patterns: state.patterns.length
|
|
1211
|
-
})}
|
|
3269
|
+
output += `${i18n2.t("instincts.total", { count: filtered.length })}
|
|
1212
3270
|
|
|
1213
3271
|
`;
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
3272
|
+
for (const [domain, domainInstincts] of byDomain) {
|
|
3273
|
+
output += `### ${domain}
|
|
1217
3274
|
`;
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
3275
|
+
for (const inst of domainInstincts.slice(0, 10)) {
|
|
3276
|
+
const bar = "\u25CF".repeat(Math.round(inst.confidence * 5)) + "\u25CB".repeat(5 - Math.round(inst.confidence * 5));
|
|
3277
|
+
const sourceTag = inst.source === "inherited" ? " \u{1F4E5}" : "";
|
|
3278
|
+
output += `- [${bar}] **${inst.title}**${sourceTag}
|
|
1221
3279
|
`;
|
|
1222
|
-
|
|
1223
|
-
greeting += `- ${o.observation}
|
|
3280
|
+
output += ` ${inst.description}
|
|
1224
3281
|
`;
|
|
1225
3282
|
}
|
|
3283
|
+
output += "\n";
|
|
1226
3284
|
}
|
|
1227
|
-
return
|
|
3285
|
+
return output;
|
|
1228
3286
|
}
|
|
1229
3287
|
}),
|
|
1230
|
-
"mimic:
|
|
1231
|
-
description:
|
|
3288
|
+
"mimic:export": tool4({
|
|
3289
|
+
description: i18n.t("tool.export.description"),
|
|
1232
3290
|
args: {},
|
|
1233
3291
|
async execute() {
|
|
1234
3292
|
const i18n2 = await i18nPromise;
|
|
1235
|
-
const
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
3293
|
+
const exported = await stateManager.exportInstincts();
|
|
3294
|
+
if (exported.instincts.length === 0) {
|
|
3295
|
+
return i18n2.t("export.empty");
|
|
3296
|
+
}
|
|
3297
|
+
const exportPath = join7(directory, ".opencode", "mimic", "exports");
|
|
3298
|
+
if (!existsSync7(exportPath)) {
|
|
3299
|
+
await mkdir5(exportPath, { recursive: true });
|
|
3300
|
+
}
|
|
3301
|
+
const filename = `instincts-${format3(/* @__PURE__ */ new Date(), "yyyy-MM-dd-HHmmss")}.json`;
|
|
3302
|
+
const filePath = join7(exportPath, filename);
|
|
3303
|
+
await writeFile6(filePath, JSON.stringify(exported, null, 2));
|
|
3304
|
+
return i18n2.t("export.success", {
|
|
3305
|
+
count: exported.instincts.length,
|
|
3306
|
+
path: filePath
|
|
3307
|
+
});
|
|
3308
|
+
}
|
|
3309
|
+
}),
|
|
3310
|
+
"mimic:import": tool4({
|
|
3311
|
+
description: i18n.t("tool.import.description"),
|
|
3312
|
+
args: {
|
|
3313
|
+
path: tool4.schema.string().describe(i18n.t("tool.import.args.path"))
|
|
3314
|
+
},
|
|
3315
|
+
async execute(args) {
|
|
3316
|
+
const i18n2 = await i18nPromise;
|
|
3317
|
+
if (!existsSync7(args.path)) {
|
|
3318
|
+
return i18n2.t("import.not_found", { path: args.path });
|
|
3319
|
+
}
|
|
3320
|
+
try {
|
|
3321
|
+
const content = await readFile6(args.path, "utf-8");
|
|
3322
|
+
const data = JSON.parse(content);
|
|
3323
|
+
const imported = await stateManager.importInstincts(
|
|
3324
|
+
data.instincts,
|
|
3325
|
+
data.metadata.projectName
|
|
3326
|
+
);
|
|
3327
|
+
await stateManager.updateIdentityStats();
|
|
3328
|
+
return i18n2.t("import.success", {
|
|
3329
|
+
count: imported,
|
|
3330
|
+
from: data.metadata.projectName
|
|
3331
|
+
});
|
|
3332
|
+
} catch {
|
|
3333
|
+
return i18n2.t("import.error");
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
}),
|
|
3337
|
+
"mimic:apply": tool4({
|
|
3338
|
+
description: i18n.t("tool.apply.description"),
|
|
3339
|
+
args: {},
|
|
3340
|
+
async execute() {
|
|
3341
|
+
const i18n2 = await i18nPromise;
|
|
3342
|
+
const innerCtx = { stateManager, directory, i18n: i18n2 };
|
|
3343
|
+
const recentTools = toolCalls.slice(-10).map((t) => t.tool);
|
|
3344
|
+
const recentFiles = getRecentlyModifiedFiles(directory).slice(0, 5);
|
|
3345
|
+
const applicable = await getApplicableInstincts(innerCtx, recentTools, recentFiles);
|
|
3346
|
+
if (applicable.length === 0) {
|
|
3347
|
+
return i18n2.t("apply.none");
|
|
3348
|
+
}
|
|
3349
|
+
let output = `${i18n2.t("apply.title")}
|
|
1240
3350
|
|
|
1241
3351
|
`;
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
output += `${i18n2.t("status.patterns", {
|
|
1245
|
-
total: state.patterns.length,
|
|
1246
|
-
surfaced: state.patterns.filter((p) => p.surfaced).length
|
|
1247
|
-
})}
|
|
3352
|
+
for (const applied of applicable) {
|
|
3353
|
+
output += `${formatInstinctSuggestion(applied, i18n2)}
|
|
1248
3354
|
`;
|
|
1249
|
-
|
|
3355
|
+
output += ` \u2192 ${applied.instinct.description}
|
|
1250
3356
|
|
|
1251
3357
|
`;
|
|
1252
|
-
|
|
1253
|
-
|
|
3358
|
+
}
|
|
3359
|
+
return output;
|
|
3360
|
+
}
|
|
3361
|
+
}),
|
|
3362
|
+
"mimic:identity": tool4({
|
|
3363
|
+
description: i18n.t("tool.identity.description"),
|
|
3364
|
+
args: {},
|
|
3365
|
+
async execute() {
|
|
3366
|
+
const i18n2 = await i18nPromise;
|
|
3367
|
+
const state = await stateManager.read();
|
|
3368
|
+
if (!state.project.identity) {
|
|
3369
|
+
await stateManager.initializeIdentity();
|
|
3370
|
+
const updated = await stateManager.read();
|
|
3371
|
+
state.project.identity = updated.project.identity;
|
|
3372
|
+
}
|
|
3373
|
+
if (!state.project.identity) {
|
|
3374
|
+
return i18n2.t("identity.error");
|
|
3375
|
+
}
|
|
3376
|
+
const identity = state.project.identity;
|
|
3377
|
+
const awakened = new Date(identity.awakened);
|
|
3378
|
+
const daysAlive = Math.floor((Date.now() - awakened.getTime()) / (1e3 * 60 * 60 * 24));
|
|
3379
|
+
let output = `${i18n2.t("identity.title")}
|
|
3380
|
+
|
|
1254
3381
|
`;
|
|
1255
|
-
|
|
1256
|
-
output += `- ${f}
|
|
3382
|
+
output += `**${i18n2.t("identity.personality")}**: ${identity.personality}
|
|
1257
3383
|
`;
|
|
1258
|
-
|
|
1259
|
-
output += "\n";
|
|
1260
|
-
}
|
|
1261
|
-
if (gitHistory.length > 0) {
|
|
1262
|
-
output += `${i18n2.t("status.recent_commits")}
|
|
3384
|
+
output += `**${i18n2.t("identity.awakened")}**: ${format3(awakened, "yyyy-MM-dd")} (${daysAlive} ${i18n2.t("identity.days")})
|
|
1263
3385
|
`;
|
|
1264
|
-
|
|
1265
|
-
output += `- ${c}
|
|
3386
|
+
output += `**${i18n2.t("identity.instincts_learned")}**: ${identity.totalInstinctsLearned}
|
|
1266
3387
|
`;
|
|
1267
|
-
|
|
1268
|
-
}
|
|
1269
|
-
const suggestions = await surfacePatterns(ctx);
|
|
1270
|
-
if (suggestions.length > 0) {
|
|
1271
|
-
output += `
|
|
1272
|
-
${i18n2.t("status.suggestions")}
|
|
3388
|
+
output += `**${i18n2.t("identity.evolutions")}**: ${identity.totalEvolutions}
|
|
1273
3389
|
`;
|
|
1274
|
-
|
|
1275
|
-
|
|
3390
|
+
if (identity.favoriteDomainsRank.length > 0) {
|
|
3391
|
+
output += `**${i18n2.t("identity.favorite_domains")}**: ${identity.favoriteDomainsRank.join(", ")}
|
|
1276
3392
|
`;
|
|
1277
|
-
}
|
|
1278
3393
|
}
|
|
1279
3394
|
return output;
|
|
1280
3395
|
}
|
|
1281
3396
|
}),
|
|
1282
|
-
"mimic:
|
|
1283
|
-
description:
|
|
3397
|
+
"mimic:sequences": tool4({
|
|
3398
|
+
description: i18n.t("tool.sequences.description"),
|
|
1284
3399
|
args: {},
|
|
1285
3400
|
async execute() {
|
|
1286
3401
|
const i18n2 = await i18nPromise;
|
|
1287
|
-
const ctx = { stateManager, directory, i18n: i18n2 };
|
|
1288
3402
|
const state = await stateManager.read();
|
|
1289
|
-
const
|
|
1290
|
-
|
|
3403
|
+
const sequences = state.statistics.toolSequences || [];
|
|
3404
|
+
if (sequences.length === 0) {
|
|
3405
|
+
return i18n2.t("sequences.empty");
|
|
3406
|
+
}
|
|
3407
|
+
let output = `${i18n2.t("sequences.title")}
|
|
3408
|
+
|
|
3409
|
+
`;
|
|
3410
|
+
for (const seq of sequences.slice(0, 10)) {
|
|
3411
|
+
output += `- **${seq.tools.join(" \u2192 ")}** (${seq.count}x)
|
|
3412
|
+
`;
|
|
3413
|
+
}
|
|
3414
|
+
return output;
|
|
3415
|
+
}
|
|
3416
|
+
})
|
|
3417
|
+
};
|
|
3418
|
+
};
|
|
3419
|
+
function groupInstinctsByDomain(instincts) {
|
|
3420
|
+
const byDomain = /* @__PURE__ */ new Map();
|
|
3421
|
+
for (const inst of instincts) {
|
|
3422
|
+
const list = byDomain.get(inst.domain) || [];
|
|
3423
|
+
list.push(inst);
|
|
3424
|
+
byDomain.set(inst.domain, list);
|
|
3425
|
+
}
|
|
3426
|
+
return byDomain;
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
// src/tools/mcp.ts
|
|
3430
|
+
import { existsSync as existsSync8 } from "fs";
|
|
3431
|
+
import { mkdir as mkdir6, readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
|
|
3432
|
+
import { join as join8 } from "path";
|
|
3433
|
+
import { tool as tool5 } from "@opencode-ai/plugin";
|
|
3434
|
+
var createMcpTools = (ctx) => {
|
|
3435
|
+
const { stateManager, directory, i18n, i18nPromise } = ctx;
|
|
3436
|
+
return {
|
|
3437
|
+
"mimic:mcp-search": tool5({
|
|
3438
|
+
description: i18n.t("tool.mcp_search.description"),
|
|
3439
|
+
args: {
|
|
3440
|
+
query: tool5.schema.string().describe(i18n.t("tool.mcp_search.args.query"))
|
|
3441
|
+
},
|
|
3442
|
+
async execute(args) {
|
|
3443
|
+
const i18n2 = await i18nPromise;
|
|
3444
|
+
const searchUrl = `https://mcpmarket.com/search?q=${encodeURIComponent(args.query)}`;
|
|
3445
|
+
const popular = [
|
|
3446
|
+
{
|
|
3447
|
+
name: "context7",
|
|
3448
|
+
desc: i18n2.t("mcp_search.desc.context7"),
|
|
3449
|
+
url: "https://mcp.context7.com/mcp"
|
|
3450
|
+
},
|
|
3451
|
+
{
|
|
3452
|
+
name: "github",
|
|
3453
|
+
desc: i18n2.t("mcp_search.desc.github"),
|
|
3454
|
+
url: "https://mcp.github.com"
|
|
3455
|
+
},
|
|
3456
|
+
{
|
|
3457
|
+
name: "supabase",
|
|
3458
|
+
desc: i18n2.t("mcp_search.desc.supabase"),
|
|
3459
|
+
url: "https://mcp.supabase.com"
|
|
3460
|
+
},
|
|
3461
|
+
{ name: "playwright", desc: i18n2.t("mcp_search.desc.playwright") },
|
|
3462
|
+
{ name: "firecrawl", desc: i18n2.t("mcp_search.desc.firecrawl") }
|
|
3463
|
+
];
|
|
3464
|
+
const popularLines = popular.map(
|
|
3465
|
+
(server) => server.url ? `- **${server.name}** - ${server.desc}: \`${server.url}\`` : `- **${server.name}** - ${server.desc}`
|
|
3466
|
+
).join("\n");
|
|
3467
|
+
return `${i18n2.t("mcp_search.header", {
|
|
3468
|
+
query: args.query,
|
|
3469
|
+
url: searchUrl
|
|
3470
|
+
})}
|
|
3471
|
+
|
|
3472
|
+
${i18n2.t("mcp_search.popular")}
|
|
3473
|
+
${popularLines}
|
|
3474
|
+
|
|
3475
|
+
${i18n2.t("mcp_search.add")}`;
|
|
1291
3476
|
}
|
|
1292
3477
|
}),
|
|
1293
|
-
"mimic:
|
|
1294
|
-
description:
|
|
1295
|
-
args: {
|
|
1296
|
-
|
|
3478
|
+
"mimic:mcp": tool5({
|
|
3479
|
+
description: i18n.t("tool.mcp.description"),
|
|
3480
|
+
args: {
|
|
3481
|
+
name: tool5.schema.string().describe(i18n.t("tool.mcp.args.name")),
|
|
3482
|
+
url: tool5.schema.string().optional().describe(i18n.t("tool.mcp.args.url")),
|
|
3483
|
+
command: tool5.schema.string().optional().describe(i18n.t("tool.mcp.args.command"))
|
|
3484
|
+
},
|
|
3485
|
+
async execute(args) {
|
|
3486
|
+
const i18n2 = await i18nPromise;
|
|
3487
|
+
const opencodeDir = join8(directory, ".opencode");
|
|
3488
|
+
if (!existsSync8(opencodeDir)) {
|
|
3489
|
+
await mkdir6(opencodeDir, { recursive: true });
|
|
3490
|
+
}
|
|
3491
|
+
const configPath = join8(directory, "opencode.json");
|
|
3492
|
+
let config = {};
|
|
3493
|
+
if (existsSync8(configPath)) {
|
|
3494
|
+
try {
|
|
3495
|
+
config = JSON.parse(await readFile7(configPath, "utf-8"));
|
|
3496
|
+
} catch {
|
|
3497
|
+
config = {};
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
const mcpEntry = {};
|
|
3501
|
+
if (args.url) {
|
|
3502
|
+
mcpEntry.type = "remote";
|
|
3503
|
+
mcpEntry.url = args.url;
|
|
3504
|
+
} else if (args.command) {
|
|
3505
|
+
mcpEntry.type = "local";
|
|
3506
|
+
mcpEntry.command = args.command.split(",").map((s) => s.trim());
|
|
3507
|
+
} else {
|
|
3508
|
+
return i18n2.t("mcp.need_url_or_command");
|
|
3509
|
+
}
|
|
3510
|
+
mcpEntry.enabled = true;
|
|
3511
|
+
config.mcp = { ...config.mcp || {}, [args.name]: mcpEntry };
|
|
3512
|
+
await writeFile7(configPath, JSON.stringify(config, null, 2));
|
|
3513
|
+
await stateManager.addMilestone(i18n2.t("milestone.mcp_added", { name: args.name }));
|
|
3514
|
+
return i18n2.t("mcp.added", { name: args.name });
|
|
3515
|
+
}
|
|
3516
|
+
})
|
|
3517
|
+
};
|
|
3518
|
+
};
|
|
3519
|
+
|
|
3520
|
+
// src/tools/observation.ts
|
|
3521
|
+
import { tool as tool6 } from "@opencode-ai/plugin";
|
|
3522
|
+
var createObservationTools = (ctx) => {
|
|
3523
|
+
const { stateManager, i18n, i18nPromise } = ctx;
|
|
3524
|
+
return {
|
|
3525
|
+
"mimic:observe": tool6({
|
|
3526
|
+
description: i18n.t("tool.observe.description"),
|
|
3527
|
+
args: {
|
|
3528
|
+
observation: tool6.schema.string().describe(i18n.t("tool.observe.args.observation"))
|
|
3529
|
+
},
|
|
3530
|
+
async execute(args) {
|
|
3531
|
+
const i18n2 = await i18nPromise;
|
|
3532
|
+
await stateManager.addObservation(args.observation);
|
|
3533
|
+
return i18n2.t("observe.recorded", { observation: args.observation });
|
|
3534
|
+
}
|
|
3535
|
+
}),
|
|
3536
|
+
"mimic:milestone": tool6({
|
|
3537
|
+
description: i18n.t("tool.milestone.description"),
|
|
3538
|
+
args: {
|
|
3539
|
+
milestone: tool6.schema.string().describe(i18n.t("tool.milestone.args.milestone"))
|
|
3540
|
+
},
|
|
3541
|
+
async execute(args) {
|
|
3542
|
+
const i18n2 = await i18nPromise;
|
|
3543
|
+
await stateManager.addMilestone(args.milestone);
|
|
3544
|
+
return i18n2.t("milestone.recorded", { milestone: args.milestone });
|
|
3545
|
+
}
|
|
3546
|
+
}),
|
|
3547
|
+
"mimic:surface": tool6({
|
|
3548
|
+
description: i18n.t("tool.surface.description"),
|
|
3549
|
+
args: {
|
|
3550
|
+
patternId: tool6.schema.string().describe(i18n.t("tool.surface.args.patternId"))
|
|
3551
|
+
},
|
|
3552
|
+
async execute(args) {
|
|
1297
3553
|
const i18n2 = await i18nPromise;
|
|
1298
3554
|
const state = await stateManager.read();
|
|
1299
|
-
|
|
1300
|
-
|
|
3555
|
+
const pattern = state.patterns.find((p) => p.id === args.patternId);
|
|
3556
|
+
if (!pattern) {
|
|
3557
|
+
return i18n2.t("surface.not_found", { id: args.patternId });
|
|
1301
3558
|
}
|
|
1302
|
-
|
|
3559
|
+
pattern.surfaced = true;
|
|
3560
|
+
await stateManager.save(state);
|
|
3561
|
+
return i18n2.t("surface.marked", { description: pattern.description });
|
|
3562
|
+
}
|
|
3563
|
+
})
|
|
3564
|
+
};
|
|
3565
|
+
};
|
|
1303
3566
|
|
|
1304
|
-
|
|
1305
|
-
|
|
3567
|
+
// src/tools/registry.ts
|
|
3568
|
+
var ToolRegistry = class {
|
|
3569
|
+
factories = [];
|
|
3570
|
+
/**
|
|
3571
|
+
* Register a tool factory
|
|
3572
|
+
*/
|
|
3573
|
+
register(factory) {
|
|
3574
|
+
this.factories.push(factory);
|
|
3575
|
+
return this;
|
|
3576
|
+
}
|
|
3577
|
+
/**
|
|
3578
|
+
* Build all tools from registered factories
|
|
3579
|
+
*/
|
|
3580
|
+
build(ctx) {
|
|
3581
|
+
const tools = {};
|
|
3582
|
+
for (const factory of this.factories) {
|
|
3583
|
+
const factoryTools = factory(ctx);
|
|
3584
|
+
Object.assign(tools, factoryTools);
|
|
3585
|
+
}
|
|
3586
|
+
return tools;
|
|
3587
|
+
}
|
|
3588
|
+
};
|
|
3589
|
+
var toolRegistry = new ToolRegistry();
|
|
3590
|
+
|
|
3591
|
+
// src/tools/settings.ts
|
|
3592
|
+
import { readdir as readdir3, writeFile as writeFile8 } from "fs/promises";
|
|
3593
|
+
import { tool as tool7 } from "@opencode-ai/plugin";
|
|
3594
|
+
import { format as format4 } from "date-fns";
|
|
3595
|
+
|
|
3596
|
+
// src/modules/evolution/system.ts
|
|
3597
|
+
var LEVEL_CONFIGS = {
|
|
3598
|
+
technical: {
|
|
3599
|
+
greetingStyle: "minimal",
|
|
3600
|
+
detailLevel: "high",
|
|
3601
|
+
useEmoji: false,
|
|
3602
|
+
technicalTerms: true
|
|
3603
|
+
},
|
|
3604
|
+
"semi-technical": {
|
|
3605
|
+
greetingStyle: "casual",
|
|
3606
|
+
detailLevel: "medium",
|
|
3607
|
+
useEmoji: false,
|
|
3608
|
+
technicalTerms: true
|
|
3609
|
+
},
|
|
3610
|
+
"non-technical": {
|
|
3611
|
+
greetingStyle: "formal",
|
|
3612
|
+
detailLevel: "low",
|
|
3613
|
+
useEmoji: true,
|
|
3614
|
+
technicalTerms: false
|
|
3615
|
+
},
|
|
3616
|
+
chaotic: {
|
|
3617
|
+
greetingStyle: "chaotic",
|
|
3618
|
+
detailLevel: "medium",
|
|
3619
|
+
useEmoji: true,
|
|
3620
|
+
technicalTerms: true
|
|
3621
|
+
}
|
|
3622
|
+
};
|
|
3623
|
+
function getLevelConfig(level) {
|
|
3624
|
+
return LEVEL_CONFIGS[level ?? "technical"];
|
|
3625
|
+
}
|
|
1306
3626
|
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
}
|
|
1314
|
-
for (const [type, patterns] of byType) {
|
|
1315
|
-
output += `${i18n2.t("patterns.section", {
|
|
1316
|
-
type: formatPatternType(i18n2, type)
|
|
1317
|
-
})}
|
|
1318
|
-
`;
|
|
1319
|
-
for (const p of patterns.slice(0, 10)) {
|
|
1320
|
-
const status = p.surfaced ? "\u2713" : "\u25CB";
|
|
1321
|
-
output += `${status} **${p.description}** (${p.count}x)
|
|
1322
|
-
`;
|
|
1323
|
-
}
|
|
1324
|
-
output += "\n";
|
|
1325
|
-
}
|
|
1326
|
-
return output;
|
|
1327
|
-
}
|
|
1328
|
-
}),
|
|
1329
|
-
"mimic:observe": tool({
|
|
1330
|
-
description: baseI18n.t("tool.observe.description"),
|
|
1331
|
-
args: {
|
|
1332
|
-
observation: tool.schema.string().describe(baseI18n.t("tool.observe.args.observation"))
|
|
1333
|
-
},
|
|
1334
|
-
async execute(args) {
|
|
1335
|
-
const i18n2 = await i18nPromise;
|
|
1336
|
-
await stateManager.addObservation(args.observation);
|
|
1337
|
-
return i18n2.t("observe.recorded", { observation: args.observation });
|
|
1338
|
-
}
|
|
1339
|
-
}),
|
|
1340
|
-
"mimic:milestone": tool({
|
|
1341
|
-
description: baseI18n.t("tool.milestone.description"),
|
|
1342
|
-
args: {
|
|
1343
|
-
milestone: tool.schema.string().describe(baseI18n.t("tool.milestone.args.milestone"))
|
|
1344
|
-
},
|
|
1345
|
-
async execute(args) {
|
|
1346
|
-
const i18n2 = await i18nPromise;
|
|
1347
|
-
await stateManager.addMilestone(args.milestone);
|
|
1348
|
-
return i18n2.t("milestone.recorded", { milestone: args.milestone });
|
|
1349
|
-
}
|
|
1350
|
-
}),
|
|
1351
|
-
"mimic:stats": tool({
|
|
1352
|
-
description: baseI18n.t("tool.stats.description"),
|
|
3627
|
+
// src/tools/settings.ts
|
|
3628
|
+
var createSettingsTools = (ctx) => {
|
|
3629
|
+
const { stateManager, i18n, i18nPromise } = ctx;
|
|
3630
|
+
return {
|
|
3631
|
+
"mimic:stats": tool7({
|
|
3632
|
+
description: i18n.t("tool.stats.description"),
|
|
1353
3633
|
args: {},
|
|
1354
3634
|
async execute() {
|
|
1355
3635
|
const i18n2 = await i18nPromise;
|
|
1356
3636
|
const state = await stateManager.read();
|
|
1357
|
-
const sessionFiles = await
|
|
3637
|
+
const sessionFiles = await readdir3(stateManager.getSessionsDir()).catch(() => []);
|
|
1358
3638
|
return `## ${i18n2.t("stats.title")}
|
|
1359
3639
|
|
|
1360
3640
|
- **${i18n2.t("stats.version")}**: ${state.version}
|
|
@@ -1364,17 +3644,17 @@ ${i18n2.t("status.suggestions")}
|
|
|
1364
3644
|
- **${i18n2.t("stats.milestones")}**: ${state.journey.milestones.length}
|
|
1365
3645
|
- **${i18n2.t("stats.observations")}**: ${state.journey.observations.length}
|
|
1366
3646
|
- **${i18n2.t("stats.session_records")}**: ${sessionFiles.length}
|
|
1367
|
-
- **${i18n2.t("stats.first_session")}**: ${
|
|
3647
|
+
- **${i18n2.t("stats.first_session")}**: ${format4(state.project.firstSession, "yyyy-MM-dd HH:mm:ss")}
|
|
1368
3648
|
- **${i18n2.t("stats.learning_enabled")}**: ${state.preferences.learningEnabled}
|
|
1369
3649
|
- **${i18n2.t("stats.suggestions_enabled")}**: ${state.preferences.suggestionEnabled}`;
|
|
1370
3650
|
}
|
|
1371
3651
|
}),
|
|
1372
|
-
"mimic:configure":
|
|
1373
|
-
description:
|
|
3652
|
+
"mimic:configure": tool7({
|
|
3653
|
+
description: i18n.t("tool.configure.description"),
|
|
1374
3654
|
args: {
|
|
1375
|
-
learningEnabled:
|
|
1376
|
-
suggestionEnabled:
|
|
1377
|
-
minPatternCount:
|
|
3655
|
+
learningEnabled: tool7.schema.boolean().optional().describe(i18n.t("tool.configure.args.learningEnabled")),
|
|
3656
|
+
suggestionEnabled: tool7.schema.boolean().optional().describe(i18n.t("tool.configure.args.suggestionEnabled")),
|
|
3657
|
+
minPatternCount: tool7.schema.number().optional().describe(i18n.t("tool.configure.args.minPatternCount"))
|
|
1378
3658
|
},
|
|
1379
3659
|
async execute(args) {
|
|
1380
3660
|
const i18n2 = await i18nPromise;
|
|
@@ -1393,105 +3673,27 @@ ${i18n2.t("status.suggestions")}
|
|
|
1393
3673
|
${JSON.stringify(state.preferences, null, 2)}`;
|
|
1394
3674
|
}
|
|
1395
3675
|
}),
|
|
1396
|
-
"mimic:
|
|
1397
|
-
description:
|
|
1398
|
-
args: {
|
|
1399
|
-
patternId: tool.schema.string().describe(baseI18n.t("tool.surface.args.patternId"))
|
|
1400
|
-
},
|
|
1401
|
-
async execute(args) {
|
|
1402
|
-
const i18n2 = await i18nPromise;
|
|
1403
|
-
const state = await stateManager.read();
|
|
1404
|
-
const pattern = state.patterns.find((p) => p.id === args.patternId);
|
|
1405
|
-
if (!pattern) {
|
|
1406
|
-
return i18n2.t("surface.not_found", { id: args.patternId });
|
|
1407
|
-
}
|
|
1408
|
-
pattern.surfaced = true;
|
|
1409
|
-
await stateManager.save(state);
|
|
1410
|
-
return i18n2.t("surface.marked", { description: pattern.description });
|
|
1411
|
-
}
|
|
1412
|
-
}),
|
|
1413
|
-
"mimic:reset": tool({
|
|
1414
|
-
description: baseI18n.t("tool.reset.description"),
|
|
3676
|
+
"mimic:reset": tool7({
|
|
3677
|
+
description: i18n.t("tool.reset.description"),
|
|
1415
3678
|
args: {
|
|
1416
|
-
confirm:
|
|
3679
|
+
confirm: tool7.schema.boolean().describe(i18n.t("tool.reset.args.confirm"))
|
|
1417
3680
|
},
|
|
1418
3681
|
async execute(args) {
|
|
1419
3682
|
const i18n2 = await i18nPromise;
|
|
1420
3683
|
if (!args.confirm) {
|
|
1421
3684
|
return i18n2.t("reset.cancelled");
|
|
1422
3685
|
}
|
|
1423
|
-
await
|
|
3686
|
+
await writeFile8(
|
|
1424
3687
|
stateManager.getStatePath(),
|
|
1425
3688
|
JSON.stringify(createDefaultState(stateManager.getProjectName()), null, 2)
|
|
1426
3689
|
);
|
|
1427
3690
|
return i18n2.t("reset.done");
|
|
1428
3691
|
}
|
|
1429
3692
|
}),
|
|
1430
|
-
"mimic:
|
|
1431
|
-
description:
|
|
1432
|
-
args: {},
|
|
1433
|
-
async execute() {
|
|
1434
|
-
const i18n2 = await i18nPromise;
|
|
1435
|
-
const ctx = { stateManager, directory, i18n: i18n2 };
|
|
1436
|
-
const state = await stateManager.read();
|
|
1437
|
-
const gitHistory = getGitHistory(directory, 20);
|
|
1438
|
-
const recentFiles = getRecentlyModifiedFiles(directory);
|
|
1439
|
-
return formatGrowAnalysis(ctx, state, gitHistory, recentFiles);
|
|
1440
|
-
}
|
|
1441
|
-
}),
|
|
1442
|
-
"mimic:evolve": tool({
|
|
1443
|
-
description: baseI18n.t("tool.evolve.description"),
|
|
1444
|
-
args: {
|
|
1445
|
-
accept: tool.schema.string().optional().describe(baseI18n.t("tool.evolve.args.accept"))
|
|
1446
|
-
},
|
|
1447
|
-
async execute(args) {
|
|
1448
|
-
const i18n2 = await i18nPromise;
|
|
1449
|
-
const ctx = { stateManager, directory, i18n: i18n2 };
|
|
1450
|
-
if (args.accept) {
|
|
1451
|
-
const suggestions2 = await getEvolutionSuggestions(ctx);
|
|
1452
|
-
const suggestion = suggestions2.find((s) => s.pattern.id === args.accept);
|
|
1453
|
-
if (!suggestion) {
|
|
1454
|
-
return i18n2.t("evolve.no_pattern", { id: args.accept });
|
|
1455
|
-
}
|
|
1456
|
-
const { capability, filePath } = await evolveCapability(ctx, suggestion);
|
|
1457
|
-
return `${i18n2.t("evolve.absorbed_header")}
|
|
1458
|
-
|
|
1459
|
-
${formatEvolutionResult(
|
|
1460
|
-
ctx,
|
|
1461
|
-
capability,
|
|
1462
|
-
filePath
|
|
1463
|
-
)}`;
|
|
1464
|
-
}
|
|
1465
|
-
const suggestions = await getEvolutionSuggestions(ctx);
|
|
1466
|
-
if (suggestions.length === 0) {
|
|
1467
|
-
return i18n2.t("evolve.empty");
|
|
1468
|
-
}
|
|
1469
|
-
let output = `${i18n2.t("evolve.menu_title")}
|
|
1470
|
-
|
|
1471
|
-
`;
|
|
1472
|
-
output += `${i18n2.t("evolve.menu_intro")}
|
|
1473
|
-
|
|
1474
|
-
`;
|
|
1475
|
-
for (const s of suggestions) {
|
|
1476
|
-
output += `### \u2728 ${s.name}
|
|
1477
|
-
`;
|
|
1478
|
-
output += `- **${i18n2.t("evolve.menu_type")}**: ${formatCapabilityType(i18n2, s.type)}
|
|
1479
|
-
`;
|
|
1480
|
-
output += `- **${i18n2.t("evolve.menu_reason")}**: ${s.reason}
|
|
1481
|
-
`;
|
|
1482
|
-
output += `- **${i18n2.t("evolve.menu_pattern_id")}**: \`${s.pattern.id}\`
|
|
1483
|
-
|
|
1484
|
-
`;
|
|
1485
|
-
}
|
|
1486
|
-
output += `
|
|
1487
|
-
${i18n2.t("evolve.menu_footer")}`;
|
|
1488
|
-
return output;
|
|
1489
|
-
}
|
|
1490
|
-
}),
|
|
1491
|
-
"mimic:level": tool({
|
|
1492
|
-
description: baseI18n.t("tool.level.description"),
|
|
3693
|
+
"mimic:level": tool7({
|
|
3694
|
+
description: i18n.t("tool.level.description"),
|
|
1493
3695
|
args: {
|
|
1494
|
-
level:
|
|
3696
|
+
level: tool7.schema.enum(["technical", "semi-technical", "non-technical", "chaotic"]).describe(i18n.t("tool.level.args.level"))
|
|
1495
3697
|
},
|
|
1496
3698
|
async execute(args) {
|
|
1497
3699
|
const i18n2 = await i18nPromise;
|
|
@@ -1506,11 +3708,11 @@ ${i18n2.t("evolve.menu_footer")}`;
|
|
|
1506
3708
|
});
|
|
1507
3709
|
}
|
|
1508
3710
|
}),
|
|
1509
|
-
"mimic:focus":
|
|
1510
|
-
description:
|
|
3711
|
+
"mimic:focus": tool7({
|
|
3712
|
+
description: i18n.t("tool.focus.description"),
|
|
1511
3713
|
args: {
|
|
1512
|
-
focus:
|
|
1513
|
-
stack:
|
|
3714
|
+
focus: tool7.schema.string().optional().describe(i18n.t("tool.focus.args.focus")),
|
|
3715
|
+
stack: tool7.schema.string().optional().describe(i18n.t("tool.focus.args.stack"))
|
|
1514
3716
|
},
|
|
1515
3717
|
async execute(args) {
|
|
1516
3718
|
const i18n2 = await i18nPromise;
|
|
@@ -1533,126 +3735,26 @@ ${i18n2.t("evolve.menu_footer")}`;
|
|
|
1533
3735
|
`;
|
|
1534
3736
|
return output;
|
|
1535
3737
|
}
|
|
1536
|
-
}),
|
|
1537
|
-
"mimic:mcp-search": tool({
|
|
1538
|
-
description: baseI18n.t("tool.mcp_search.description"),
|
|
1539
|
-
args: {
|
|
1540
|
-
query: tool.schema.string().describe(baseI18n.t("tool.mcp_search.args.query"))
|
|
1541
|
-
},
|
|
1542
|
-
async execute(args) {
|
|
1543
|
-
const i18n2 = await i18nPromise;
|
|
1544
|
-
const searchUrl = `https://mcpmarket.com/search?q=${encodeURIComponent(args.query)}`;
|
|
1545
|
-
const popular = [
|
|
1546
|
-
{
|
|
1547
|
-
name: "context7",
|
|
1548
|
-
desc: i18n2.t("mcp_search.desc.context7"),
|
|
1549
|
-
url: "https://mcp.context7.com/mcp"
|
|
1550
|
-
},
|
|
1551
|
-
{
|
|
1552
|
-
name: "github",
|
|
1553
|
-
desc: i18n2.t("mcp_search.desc.github"),
|
|
1554
|
-
url: "https://mcp.github.com"
|
|
1555
|
-
},
|
|
1556
|
-
{
|
|
1557
|
-
name: "supabase",
|
|
1558
|
-
desc: i18n2.t("mcp_search.desc.supabase"),
|
|
1559
|
-
url: "https://mcp.supabase.com"
|
|
1560
|
-
},
|
|
1561
|
-
{ name: "playwright", desc: i18n2.t("mcp_search.desc.playwright") },
|
|
1562
|
-
{ name: "firecrawl", desc: i18n2.t("mcp_search.desc.firecrawl") }
|
|
1563
|
-
];
|
|
1564
|
-
const popularLines = popular.map(
|
|
1565
|
-
(server) => server.url ? `- **${server.name}** - ${server.desc}: \`${server.url}\`` : `- **${server.name}** - ${server.desc}`
|
|
1566
|
-
).join("\n");
|
|
1567
|
-
return `${i18n2.t("mcp_search.header", {
|
|
1568
|
-
query: args.query,
|
|
1569
|
-
url: searchUrl
|
|
1570
|
-
})}
|
|
1571
|
-
|
|
1572
|
-
${i18n2.t("mcp_search.popular")}
|
|
1573
|
-
${popularLines}
|
|
1574
|
-
|
|
1575
|
-
${i18n2.t("mcp_search.add")}`;
|
|
1576
|
-
}
|
|
1577
|
-
}),
|
|
1578
|
-
"mimic:mcp": tool({
|
|
1579
|
-
description: baseI18n.t("tool.mcp.description"),
|
|
1580
|
-
args: {
|
|
1581
|
-
name: tool.schema.string().describe(baseI18n.t("tool.mcp.args.name")),
|
|
1582
|
-
url: tool.schema.string().optional().describe(baseI18n.t("tool.mcp.args.url")),
|
|
1583
|
-
command: tool.schema.string().optional().describe(baseI18n.t("tool.mcp.args.command"))
|
|
1584
|
-
},
|
|
1585
|
-
async execute(args) {
|
|
1586
|
-
const i18n2 = await i18nPromise;
|
|
1587
|
-
const { existsSync: existsSync4 } = await import("fs");
|
|
1588
|
-
const { readFile: readFile4, writeFile: fsWriteFile, mkdir: mkdir3 } = await import("fs/promises");
|
|
1589
|
-
const { join: join4 } = await import("path");
|
|
1590
|
-
const opencodeDir = join4(directory, ".opencode");
|
|
1591
|
-
if (!existsSync4(opencodeDir)) {
|
|
1592
|
-
await mkdir3(opencodeDir, { recursive: true });
|
|
1593
|
-
}
|
|
1594
|
-
const configPath = join4(directory, "opencode.json");
|
|
1595
|
-
let config = {};
|
|
1596
|
-
if (existsSync4(configPath)) {
|
|
1597
|
-
try {
|
|
1598
|
-
config = JSON.parse(await readFile4(configPath, "utf-8"));
|
|
1599
|
-
} catch {
|
|
1600
|
-
config = {};
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
const mcpEntry = {};
|
|
1604
|
-
if (args.url) {
|
|
1605
|
-
mcpEntry.type = "remote";
|
|
1606
|
-
mcpEntry.url = args.url;
|
|
1607
|
-
} else if (args.command) {
|
|
1608
|
-
mcpEntry.type = "local";
|
|
1609
|
-
mcpEntry.command = args.command.split(",").map((s) => s.trim());
|
|
1610
|
-
} else {
|
|
1611
|
-
return i18n2.t("mcp.need_url_or_command");
|
|
1612
|
-
}
|
|
1613
|
-
mcpEntry.enabled = true;
|
|
1614
|
-
config.mcp = { ...config.mcp || {}, [args.name]: mcpEntry };
|
|
1615
|
-
await fsWriteFile(configPath, JSON.stringify(config, null, 2));
|
|
1616
|
-
await stateManager.addMilestone(i18n2.t("milestone.mcp_added", { name: args.name }));
|
|
1617
|
-
return i18n2.t("mcp.added", { name: args.name });
|
|
1618
|
-
}
|
|
1619
|
-
}),
|
|
1620
|
-
"mimic:capabilities": tool({
|
|
1621
|
-
description: baseI18n.t("tool.capabilities.description"),
|
|
1622
|
-
args: {},
|
|
1623
|
-
async execute() {
|
|
1624
|
-
const i18n2 = await i18nPromise;
|
|
1625
|
-
const state = await stateManager.read();
|
|
1626
|
-
if (state.evolution.capabilities.length === 0) {
|
|
1627
|
-
return i18n2.t("capabilities.empty");
|
|
1628
|
-
}
|
|
1629
|
-
let output = `${i18n2.t("capabilities.title")}
|
|
1630
|
-
|
|
1631
|
-
`;
|
|
1632
|
-
output += `${i18n2.t("capabilities.intro")}
|
|
1633
|
-
|
|
1634
|
-
`;
|
|
1635
|
-
for (const cap of state.evolution.capabilities) {
|
|
1636
|
-
output += `### \u2728 ${cap.name}
|
|
1637
|
-
`;
|
|
1638
|
-
output += `- **${i18n2.t("capabilities.type")}**: ${formatCapabilityType(
|
|
1639
|
-
i18n2,
|
|
1640
|
-
cap.type
|
|
1641
|
-
)}
|
|
1642
|
-
`;
|
|
1643
|
-
output += `- **${i18n2.t("capabilities.description")}**: ${cap.description}
|
|
1644
|
-
`;
|
|
1645
|
-
output += `- **${i18n2.t("capabilities.consumed")}**: ${format2(
|
|
1646
|
-
new Date(cap.createdAt),
|
|
1647
|
-
"yyyy-MM-dd"
|
|
1648
|
-
)}
|
|
1649
|
-
|
|
1650
|
-
`;
|
|
1651
|
-
}
|
|
1652
|
-
return output;
|
|
1653
|
-
}
|
|
1654
3738
|
})
|
|
1655
3739
|
};
|
|
3740
|
+
};
|
|
3741
|
+
|
|
3742
|
+
// src/tools/main.ts
|
|
3743
|
+
function createDefaultRegistry() {
|
|
3744
|
+
return new ToolRegistry().register(createCoreTools).register(createSettingsTools).register(createEvolutionTools).register(createInstinctTools).register(createObservationTools).register(createMcpTools).register(createAutomationTools);
|
|
3745
|
+
}
|
|
3746
|
+
function createTools(stateManager, directory, toolCalls, i18n) {
|
|
3747
|
+
const baseI18n = i18n ?? createI18n(resolveLanguage(null));
|
|
3748
|
+
const i18nPromise = i18n ? Promise.resolve(i18n) : loadMimicConfig().then((config) => createI18n(resolveLanguage(config))).catch(() => createI18n(resolveLanguage(null)));
|
|
3749
|
+
const ctx = {
|
|
3750
|
+
stateManager,
|
|
3751
|
+
directory,
|
|
3752
|
+
toolCalls,
|
|
3753
|
+
i18n: baseI18n,
|
|
3754
|
+
i18nPromise
|
|
3755
|
+
};
|
|
3756
|
+
const registry = createDefaultRegistry();
|
|
3757
|
+
return registry.build(ctx);
|
|
1656
3758
|
}
|
|
1657
3759
|
|
|
1658
3760
|
// src/index.ts
|
|
@@ -1660,12 +3762,24 @@ var mimic = async ({ directory, client }) => {
|
|
|
1660
3762
|
const stateManager = new StateManager(directory);
|
|
1661
3763
|
await stateManager.initialize();
|
|
1662
3764
|
const i18n = createI18n(resolveLanguage(await loadMimicConfig()));
|
|
3765
|
+
const observationLog = new ObservationLog(
|
|
3766
|
+
stateManager.getInstinctsDir().replace("/instincts", "")
|
|
3767
|
+
);
|
|
3768
|
+
await observationLog.initialize();
|
|
3769
|
+
const sessionMemory = new SessionMemoryManager(
|
|
3770
|
+
stateManager.getInstinctsDir().replace("/instincts", ""),
|
|
3771
|
+
stateManager.getSessionsDir()
|
|
3772
|
+
);
|
|
3773
|
+
const skillGenerator = new SkillGenerator(directory);
|
|
3774
|
+
await skillGenerator.initialize();
|
|
1663
3775
|
const ctx = { stateManager, directory, i18n, client };
|
|
1664
|
-
const sessionId =
|
|
3776
|
+
const sessionId = generateId();
|
|
1665
3777
|
const sessionStartTime = Date.now();
|
|
1666
3778
|
const toolCalls = [];
|
|
1667
3779
|
const filesEdited = /* @__PURE__ */ new Set();
|
|
3780
|
+
let currentBranch;
|
|
1668
3781
|
const handleSessionCreated = async () => {
|
|
3782
|
+
await stateManager.initializeIdentity();
|
|
1669
3783
|
const state = await stateManager.read();
|
|
1670
3784
|
const timeSince = analyzeTimeSinceLastSession(state.journey.lastSession);
|
|
1671
3785
|
state.statistics.totalSessions += 1;
|
|
@@ -1673,19 +3787,78 @@ var mimic = async ({ directory, client }) => {
|
|
|
1673
3787
|
state.journey.sessionCount += 1;
|
|
1674
3788
|
state.journey.lastSession = (/* @__PURE__ */ new Date()).toISOString();
|
|
1675
3789
|
await stateManager.save(state);
|
|
3790
|
+
await observationLog.logSessionStart(sessionId);
|
|
1676
3791
|
if (timeSince === "long-break") {
|
|
1677
3792
|
await stateManager.addObservation(i18n.t("obs.returned_after_long_break"));
|
|
1678
3793
|
}
|
|
3794
|
+
const hints = await sessionMemory.getContinuityHints();
|
|
3795
|
+
const hintMessage = hints.length > 0 ? `
|
|
3796
|
+
${hints[0]}` : "";
|
|
3797
|
+
const instinctContext = await buildInstinctContext(ctx);
|
|
3798
|
+
if (instinctContext) {
|
|
3799
|
+
await client.tui.showToast({
|
|
3800
|
+
body: {
|
|
3801
|
+
title: "[Mimic] \u{1F9E0}",
|
|
3802
|
+
message: i18n.t("instincts.auto_applied"),
|
|
3803
|
+
variant: "info"
|
|
3804
|
+
}
|
|
3805
|
+
});
|
|
3806
|
+
}
|
|
1679
3807
|
await client.tui.showToast({
|
|
1680
3808
|
body: {
|
|
1681
3809
|
message: i18n.t("log.session_started", {
|
|
1682
3810
|
sessions: state.journey.sessionCount,
|
|
1683
3811
|
patterns: state.patterns.length
|
|
1684
|
-
}),
|
|
3812
|
+
}) + hintMessage,
|
|
1685
3813
|
variant: "info"
|
|
1686
3814
|
}
|
|
1687
3815
|
});
|
|
1688
3816
|
};
|
|
3817
|
+
const notifyEvolution = async (domain, capability) => {
|
|
3818
|
+
try {
|
|
3819
|
+
const skill = await skillGenerator.fromCapability(ctx, capability);
|
|
3820
|
+
if (skill) {
|
|
3821
|
+
await client.tui.showToast({
|
|
3822
|
+
body: {
|
|
3823
|
+
title: "[Mimic] \u{1F4DA}",
|
|
3824
|
+
message: i18n.t("observer.skill_generated", { name: skill.name }),
|
|
3825
|
+
variant: "success"
|
|
3826
|
+
}
|
|
3827
|
+
});
|
|
3828
|
+
}
|
|
3829
|
+
} catch {
|
|
3830
|
+
}
|
|
3831
|
+
await client.tui.showToast({
|
|
3832
|
+
body: {
|
|
3833
|
+
title: "[Mimic] \u2728",
|
|
3834
|
+
message: i18n.t("observer.evolved", {
|
|
3835
|
+
name: capability.name,
|
|
3836
|
+
domain
|
|
3837
|
+
}),
|
|
3838
|
+
variant: "success"
|
|
3839
|
+
}
|
|
3840
|
+
});
|
|
3841
|
+
};
|
|
3842
|
+
const processEvolution = async () => {
|
|
3843
|
+
if (!await shouldRunObserver(ctx)) return;
|
|
3844
|
+
const newInstincts = await runObserver(ctx);
|
|
3845
|
+
if (newInstincts.length > 0) {
|
|
3846
|
+
await client.tui.showToast({
|
|
3847
|
+
body: {
|
|
3848
|
+
title: "[Mimic]",
|
|
3849
|
+
message: i18n.t("observer.new_instincts", { count: newInstincts.length }),
|
|
3850
|
+
variant: "info"
|
|
3851
|
+
}
|
|
3852
|
+
});
|
|
3853
|
+
}
|
|
3854
|
+
const triggeredDomains = await clusterDomainsAndTriggerEvolution(ctx);
|
|
3855
|
+
for (const domain of triggeredDomains) {
|
|
3856
|
+
const result = await evolveDomain(ctx, domain);
|
|
3857
|
+
if (result) {
|
|
3858
|
+
await notifyEvolution(domain, result.capability);
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
};
|
|
1689
3862
|
const handleSessionIdle = async () => {
|
|
1690
3863
|
const newPatterns = await detectPatterns(ctx);
|
|
1691
3864
|
if (newPatterns.length > 0) {
|
|
@@ -1693,6 +3866,7 @@ var mimic = async ({ directory, client }) => {
|
|
|
1693
3866
|
state.patterns.push(...newPatterns);
|
|
1694
3867
|
await stateManager.save(state);
|
|
1695
3868
|
}
|
|
3869
|
+
await processEvolution();
|
|
1696
3870
|
const suggestions = await surfacePatterns(ctx);
|
|
1697
3871
|
for (const suggestion of suggestions) {
|
|
1698
3872
|
await client.tui.showToast({
|
|
@@ -1705,28 +3879,76 @@ var mimic = async ({ directory, client }) => {
|
|
|
1705
3879
|
}
|
|
1706
3880
|
};
|
|
1707
3881
|
const handleFileEdited = async (event) => {
|
|
1708
|
-
if (!("properties" in event)) return;
|
|
1709
|
-
const
|
|
1710
|
-
if (!
|
|
1711
|
-
filesEdited.add(
|
|
3882
|
+
if (!(event.type === OPENCODE_EVENTS.FILE_EDITED && "properties" in event)) return;
|
|
3883
|
+
const file = event.properties?.file;
|
|
3884
|
+
if (!file) return;
|
|
3885
|
+
filesEdited.add(file);
|
|
3886
|
+
await observationLog.logFileEdit(file, sessionId);
|
|
1712
3887
|
const state = await stateManager.read();
|
|
1713
|
-
state.statistics.filesModified[
|
|
3888
|
+
state.statistics.filesModified[file] = (state.statistics.filesModified[file] || 0) + 1;
|
|
1714
3889
|
await stateManager.save(state);
|
|
1715
3890
|
};
|
|
3891
|
+
const handleVcsBranchUpdated = async (event) => {
|
|
3892
|
+
if (!(event.type === OPENCODE_EVENTS.VCS_BRANCH_UPDATED && "properties" in event)) return;
|
|
3893
|
+
const branch = event.properties?.branch;
|
|
3894
|
+
if (branch !== currentBranch) {
|
|
3895
|
+
currentBranch = branch;
|
|
3896
|
+
await observationLog.logBranchChange(branch);
|
|
3897
|
+
}
|
|
3898
|
+
};
|
|
3899
|
+
const handleCommandExecuted = async (event) => {
|
|
3900
|
+
if (!(event.type === OPENCODE_EVENTS.COMMAND_EXECUTED && "properties" in event)) return;
|
|
3901
|
+
const props = event.properties;
|
|
3902
|
+
if (props.name) {
|
|
3903
|
+
await observationLog.logCommand(props.name, sessionId, props.arguments);
|
|
3904
|
+
}
|
|
3905
|
+
};
|
|
3906
|
+
const handleMessageUpdated = async (event) => {
|
|
3907
|
+
if (!(event.type === OPENCODE_EVENTS.MESSAGE_UPDATED && "properties" in event)) return;
|
|
3908
|
+
const info = event.properties?.info;
|
|
3909
|
+
if (!info) return;
|
|
3910
|
+
if (info.role === "assistant" && info.tokens) {
|
|
3911
|
+
await observationLog.logAssistantMessage(
|
|
3912
|
+
sessionId,
|
|
3913
|
+
info.id || "",
|
|
3914
|
+
info.tokens.input + info.tokens.output
|
|
3915
|
+
);
|
|
3916
|
+
}
|
|
3917
|
+
};
|
|
1716
3918
|
return {
|
|
1717
3919
|
async event({ event }) {
|
|
1718
3920
|
switch (event.type) {
|
|
1719
|
-
case
|
|
3921
|
+
case OPENCODE_EVENTS.SESSION_CREATED:
|
|
1720
3922
|
await handleSessionCreated();
|
|
1721
3923
|
return;
|
|
1722
|
-
case
|
|
3924
|
+
case OPENCODE_EVENTS.SESSION_IDLE:
|
|
1723
3925
|
await handleSessionIdle();
|
|
1724
3926
|
return;
|
|
1725
|
-
case
|
|
3927
|
+
case OPENCODE_EVENTS.FILE_EDITED:
|
|
1726
3928
|
await handleFileEdited(event);
|
|
3929
|
+
return;
|
|
3930
|
+
case OPENCODE_EVENTS.VCS_BRANCH_UPDATED:
|
|
3931
|
+
await handleVcsBranchUpdated(event);
|
|
3932
|
+
return;
|
|
3933
|
+
case OPENCODE_EVENTS.COMMAND_EXECUTED:
|
|
3934
|
+
await handleCommandExecuted(event);
|
|
3935
|
+
return;
|
|
3936
|
+
case OPENCODE_EVENTS.MESSAGE_UPDATED:
|
|
3937
|
+
await handleMessageUpdated(event);
|
|
3938
|
+
return;
|
|
3939
|
+
}
|
|
3940
|
+
},
|
|
3941
|
+
/**
|
|
3942
|
+
* Hook into user messages to capture prompts for pattern analysis
|
|
3943
|
+
*/
|
|
3944
|
+
async [OPENCODE_EVENTS.CHAT_MESSAGE](input, output) {
|
|
3945
|
+
const textParts = output.parts.filter((p) => p.type === "text").map((p) => p.text);
|
|
3946
|
+
if (textParts.length > 0) {
|
|
3947
|
+
const textPreview = textParts.join(" ").slice(0, 200);
|
|
3948
|
+
await observationLog.logUserMessage(sessionId, input.messageID || "", textPreview);
|
|
1727
3949
|
}
|
|
1728
3950
|
},
|
|
1729
|
-
async
|
|
3951
|
+
async [OPENCODE_EVENTS.TOOL_EXECUTE_AFTER](input, _output) {
|
|
1730
3952
|
const state = await stateManager.read();
|
|
1731
3953
|
if (!state.preferences.learningEnabled) return;
|
|
1732
3954
|
const toolCall = {
|
|
@@ -1736,6 +3958,7 @@ var mimic = async ({ directory, client }) => {
|
|
|
1736
3958
|
};
|
|
1737
3959
|
toolCalls.push(toolCall);
|
|
1738
3960
|
state.statistics.totalToolCalls += 1;
|
|
3961
|
+
await observationLog.logToolCall(input.tool, input.callID, sessionId);
|
|
1739
3962
|
const toolPattern = input.tool;
|
|
1740
3963
|
const existing = state.patterns.find(
|
|
1741
3964
|
(p) => p.type === "tool" && p.description === toolPattern
|
|
@@ -1745,7 +3968,7 @@ var mimic = async ({ directory, client }) => {
|
|
|
1745
3968
|
existing.lastSeen = Date.now();
|
|
1746
3969
|
} else {
|
|
1747
3970
|
state.patterns.push({
|
|
1748
|
-
id:
|
|
3971
|
+
id: generateId(),
|
|
1749
3972
|
type: "tool",
|
|
1750
3973
|
description: toolPattern,
|
|
1751
3974
|
count: 1,
|
|
@@ -1756,6 +3979,10 @@ var mimic = async ({ directory, client }) => {
|
|
|
1756
3979
|
});
|
|
1757
3980
|
}
|
|
1758
3981
|
await stateManager.save(state);
|
|
3982
|
+
if (toolCalls.length >= 2) {
|
|
3983
|
+
const recentTools = toolCalls.slice(-3).map((t) => t.tool);
|
|
3984
|
+
await stateManager.recordToolSequence(recentTools);
|
|
3985
|
+
}
|
|
1759
3986
|
},
|
|
1760
3987
|
async stop() {
|
|
1761
3988
|
const sessionDuration = Date.now() - sessionStartTime;
|
|
@@ -1768,6 +3995,19 @@ var mimic = async ({ directory, client }) => {
|
|
|
1768
3995
|
filesEdited: Array.from(filesEdited)
|
|
1769
3996
|
};
|
|
1770
3997
|
await stateManager.saveSession(sessionId, sessionData);
|
|
3998
|
+
await observationLog.logSessionEnd(sessionId, sessionDuration, toolCalls.length);
|
|
3999
|
+
const state = await stateManager.read();
|
|
4000
|
+
const recentToolNames = toolCalls.slice(-20).map((t) => t.tool);
|
|
4001
|
+
const dominantDomains = detectDomainsFromTools(recentToolNames);
|
|
4002
|
+
const recentPatterns = state.patterns.filter((p) => p.lastSeen > sessionStartTime).map((p) => p.description);
|
|
4003
|
+
try {
|
|
4004
|
+
await sessionMemory.createMemory(sessionData, dominantDomains, recentPatterns, {
|
|
4005
|
+
branch: currentBranch,
|
|
4006
|
+
focus: state.project.focus,
|
|
4007
|
+
keyActions: recentToolNames.slice(-5)
|
|
4008
|
+
});
|
|
4009
|
+
} catch {
|
|
4010
|
+
}
|
|
1771
4011
|
if (toolCalls.length > 20) {
|
|
1772
4012
|
await stateManager.addObservation(
|
|
1773
4013
|
i18n.t("obs.intensive_session", { tools: toolCalls.length })
|