opencode-plugin-mimic 0.1.5 → 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 +3227 -1045
- 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,985 +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
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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);
|
|
490
948
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
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);
|
|
497
956
|
}
|
|
498
|
-
output += "\n";
|
|
499
957
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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);
|
|
509
974
|
}
|
|
510
|
-
output += "\n";
|
|
511
975
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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;
|
|
518
982
|
}
|
|
519
983
|
}
|
|
520
|
-
|
|
984
|
+
if (!bestPattern && patterns.length > 0) {
|
|
985
|
+
const unsurfaced = patterns.find((p) => !p.surfaced);
|
|
986
|
+
bestPattern = unsurfaced || patterns[0];
|
|
987
|
+
}
|
|
988
|
+
return bestPattern;
|
|
521
989
|
}
|
|
522
|
-
function
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
const
|
|
526
|
-
const
|
|
527
|
-
|
|
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);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
if (triggered.length > 0) {
|
|
1006
|
+
await ctx.stateManager.save(state);
|
|
1007
|
+
}
|
|
1008
|
+
return triggered;
|
|
528
1009
|
}
|
|
529
|
-
function formatGrowAnalysis(ctx, state, _gitHistory, recentFiles) {
|
|
530
|
-
let output = `${ctx.i18n.t("grow.title", { project: state.project.name })}
|
|
531
1010
|
|
|
532
|
-
|
|
533
|
-
|
|
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);
|
|
1019
|
+
}
|
|
534
1020
|
|
|
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()}
|
|
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
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
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);
|
|
1191
|
+
}
|
|
1192
|
+
if (suggestion.type === "agent") {
|
|
1193
|
+
return buildAgentEvolution(ctx, suggestion);
|
|
1194
|
+
}
|
|
1195
|
+
return buildPluginEvolution(ctx, suggestion);
|
|
1196
|
+
}
|
|
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
|
+
};
|
|
675
1232
|
}
|
|
676
|
-
|
|
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;
|
|
677
1284
|
}
|
|
678
|
-
return
|
|
1285
|
+
return null;
|
|
679
1286
|
}
|
|
680
|
-
async function
|
|
1287
|
+
async function getEvolutionSuggestions(ctx) {
|
|
681
1288
|
const state = await ctx.stateManager.read();
|
|
682
1289
|
const suggestions = [];
|
|
683
1290
|
for (const pattern of state.patterns) {
|
|
684
1291
|
if (pattern.surfaced) continue;
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
case "commit":
|
|
689
|
-
suggestion = ctx.i18n.t("suggest.commit", {
|
|
690
|
-
pattern: pattern.description,
|
|
691
|
-
count: pattern.count
|
|
692
|
-
});
|
|
693
|
-
break;
|
|
694
|
-
case "file":
|
|
695
|
-
suggestion = ctx.i18n.t("suggest.file", {
|
|
696
|
-
pattern: pattern.description,
|
|
697
|
-
count: pattern.count
|
|
698
|
-
});
|
|
699
|
-
break;
|
|
700
|
-
case "tool":
|
|
701
|
-
suggestion = ctx.i18n.t("suggest.tool", {
|
|
702
|
-
pattern: pattern.description,
|
|
703
|
-
count: pattern.count
|
|
704
|
-
});
|
|
705
|
-
break;
|
|
706
|
-
case "sequence":
|
|
707
|
-
suggestion = ctx.i18n.t("suggest.sequence", {
|
|
708
|
-
pattern: pattern.description,
|
|
709
|
-
count: pattern.count
|
|
710
|
-
});
|
|
711
|
-
break;
|
|
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
|
-
totalToolCalls: 0,
|
|
752
|
-
filesModified: {},
|
|
753
|
-
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;
|
|
754
1351
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
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;
|
|
766
1362
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
await writeFile(gitIgnorePath, STATE_JSON_GITIGNORE_LINE + "\n", "utf-8");
|
|
771
|
-
return;
|
|
772
|
-
}
|
|
773
|
-
const content = await readFile2(gitIgnorePath, "utf-8");
|
|
774
|
-
const lines = content.split(/\r?\n/);
|
|
775
|
-
const alreadyExists = lines.some((line) => line.trim() === STATE_JSON_GITIGNORE_LINE);
|
|
776
|
-
if (!alreadyExists) {
|
|
777
|
-
await writeFile(gitIgnorePath, content + "\n" + STATE_JSON_GITIGNORE_LINE + "\n", "utf-8");
|
|
778
|
-
}
|
|
1363
|
+
const pattern = findRepresentativePattern(domainInstincts, state.patterns);
|
|
1364
|
+
if (!pattern) {
|
|
1365
|
+
return null;
|
|
779
1366
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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;
|
|
1383
|
+
}
|
|
1384
|
+
const result = await evolveCapability(ctx, suggestion);
|
|
1385
|
+
const state = await ctx.stateManager.read();
|
|
1386
|
+
if (!state.evolution.evolvedDomains) {
|
|
1387
|
+
state.evolution.evolvedDomains = {};
|
|
1388
|
+
}
|
|
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);
|
|
1393
|
+
}
|
|
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];
|
|
1404
|
+
}
|
|
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);
|
|
784
1424
|
}
|
|
785
|
-
|
|
786
|
-
|
|
1425
|
+
}
|
|
1426
|
+
if (recentTools.some((t) => t.includes("test") || t.includes("vitest") || t.includes("\uD14C\uC2A4\uD2B8"))) {
|
|
1427
|
+
if (!domains.includes("testing")) domains.push("testing");
|
|
1428
|
+
}
|
|
1429
|
+
if (recentTools.some((t) => t.includes("git") || t.includes("commit") || t.includes("\uCEE4\uBC0B"))) {
|
|
1430
|
+
if (!domains.includes("git")) domains.push("git");
|
|
1431
|
+
}
|
|
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
|
+
});
|
|
787
1444
|
}
|
|
788
|
-
|
|
789
|
-
|
|
1445
|
+
}
|
|
1446
|
+
return applied.sort((a, b) => b.instinct.confidence - a.instinct.confidence).slice(0, 3);
|
|
1447
|
+
}
|
|
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] = [];
|
|
790
1468
|
}
|
|
1469
|
+
byDomain[instinct.domain].push(instinct);
|
|
791
1470
|
}
|
|
792
|
-
|
|
793
|
-
|
|
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
|
+
}
|
|
794
1480
|
}
|
|
795
|
-
|
|
796
|
-
|
|
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");
|
|
1485
|
+
}
|
|
1486
|
+
|
|
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;
|
|
797
1499
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
1500
|
+
/**
|
|
1501
|
+
* Load all session memories
|
|
1502
|
+
*/
|
|
1503
|
+
async loadMemories() {
|
|
1504
|
+
if (!existsSync4(this.memoryPath)) {
|
|
1505
|
+
return [];
|
|
1506
|
+
}
|
|
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 [];
|
|
806
1513
|
}
|
|
807
|
-
await this.save(state);
|
|
808
1514
|
}
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
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
|
+
);
|
|
816
1525
|
}
|
|
817
|
-
|
|
818
|
-
|
|
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;
|
|
819
1547
|
}
|
|
820
|
-
|
|
821
|
-
|
|
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);
|
|
822
1554
|
}
|
|
823
|
-
|
|
824
|
-
|
|
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
|
+
);
|
|
825
1575
|
}
|
|
826
|
-
|
|
827
|
-
|
|
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
|
+
}
|
|
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;
|
|
828
1753
|
}
|
|
829
1754
|
};
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
"use_skill",
|
|
872
|
-
"find_skills",
|
|
873
|
-
"session_list",
|
|
874
|
-
"session_read",
|
|
875
|
-
"session_search",
|
|
876
|
-
"session_info",
|
|
877
|
-
"mimic:"
|
|
878
|
-
]);
|
|
879
|
-
function generateToolCode(name, description, pattern) {
|
|
880
|
-
const funcName = name.replace(/-/g, "_");
|
|
881
|
-
return `// \u{1F4E6} Auto-generated by Mimic
|
|
882
|
-
// Pattern: ${pattern.description} (${pattern.count}x)
|
|
883
|
-
// Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
884
|
-
|
|
885
|
-
export const ${funcName} = (plugin) => ({
|
|
886
|
-
tool: {
|
|
887
|
-
"${name}": {
|
|
888
|
-
description: "${description}",
|
|
889
|
-
args: {},
|
|
890
|
-
async execute() {
|
|
891
|
-
// TODO: Implement your logic here
|
|
892
|
-
// This was generated from pattern: ${pattern.description}
|
|
893
|
-
return "\u{1F4E6} ${name} executed!";
|
|
894
|
-
},
|
|
895
|
-
},
|
|
896
|
-
},
|
|
897
|
-
});
|
|
898
|
-
|
|
899
|
-
export default ${funcName};
|
|
900
|
-
`;
|
|
901
|
-
}
|
|
902
|
-
function generateHookCode(name, _description, pattern) {
|
|
903
|
-
const filename = pattern.description;
|
|
904
|
-
return `// \u{1F4E6} Auto-generated by Mimic
|
|
905
|
-
// Pattern: ${pattern.description} (${pattern.count}x)
|
|
906
|
-
// Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
907
|
-
|
|
908
|
-
export const ${name.replace(/-/g, "_")} = (plugin) => ({
|
|
909
|
-
async event({ event }) {
|
|
910
|
-
if (event.type === "file.edited") {
|
|
911
|
-
const filename = event.properties?.filename;
|
|
912
|
-
if (filename?.includes("${filename}")) {
|
|
913
|
-
console.log("\u{1F4E6} [Mimic] Detected change in watched file: ${filename}");
|
|
914
|
-
// TODO: Add your custom logic here
|
|
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"
|
|
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);
|
|
915
1796
|
}
|
|
916
1797
|
}
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
export default ${name.replace(/-/g, "_")};
|
|
921
|
-
`;
|
|
1798
|
+
}
|
|
1799
|
+
return [...domainScores.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([d]) => d);
|
|
922
1800
|
}
|
|
923
|
-
function generateSkillCode(name, description, pattern) {
|
|
924
|
-
return `// \u{1F4E6} Auto-generated by Mimic
|
|
925
|
-
// Pattern: ${pattern.description} (${pattern.count}x)
|
|
926
|
-
// Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
927
1801
|
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
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 });
|
|
934
1817
|
}
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
[name]: {
|
|
944
|
-
type: "local",
|
|
945
|
-
command: ["echo", `MCP server for ${pattern.description}`],
|
|
946
|
-
enabled: false
|
|
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 });
|
|
947
1826
|
}
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
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}
|
|
954
1860
|
tools:
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
grep: true
|
|
958
|
-
bash: false
|
|
959
|
-
edit: false
|
|
1861
|
+
${toolsYaml}
|
|
1862
|
+
generated: ${now}
|
|
960
1863
|
---
|
|
961
1864
|
|
|
962
|
-
#
|
|
1865
|
+
# ${metadata.name}
|
|
963
1866
|
|
|
964
|
-
|
|
1867
|
+
${metadata.description}
|
|
965
1868
|
|
|
966
|
-
|
|
1869
|
+
> [!NOTE]
|
|
1870
|
+
> This skill was auto-generated by Mimic based on observed patterns and learned instincts.
|
|
967
1871
|
|
|
968
|
-
|
|
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)}
|
|
969
1894
|
|
|
970
1895
|
## Context
|
|
971
1896
|
|
|
972
|
-
-
|
|
973
|
-
-
|
|
974
|
-
-
|
|
1897
|
+
- **Domain**: ${metadata.domain}
|
|
1898
|
+
- **Generated**: ${now}
|
|
1899
|
+
- **Instincts Used**: ${instincts.length}
|
|
1900
|
+
- **Patterns Analyzed**: ${patterns.length}
|
|
975
1901
|
|
|
976
|
-
##
|
|
1902
|
+
## Customization
|
|
977
1903
|
|
|
978
|
-
|
|
979
|
-
Provide focused, expert guidance in this specific area.
|
|
1904
|
+
You can customize this skill by editing the frontmatter or adding additional sections below.
|
|
980
1905
|
|
|
981
|
-
|
|
1906
|
+
---
|
|
982
1907
|
|
|
983
|
-
|
|
984
|
-
- Leverage the observed usage patterns
|
|
985
|
-
- Adapt to the user's workflow
|
|
1908
|
+
*Generated by [opencode-plugin-mimic](https://github.com/gracefullight/pkgs)*
|
|
986
1909
|
`;
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
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");
|
|
994
1951
|
}
|
|
995
|
-
|
|
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");
|
|
1960
|
+
}
|
|
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");
|
|
1989
|
+
}
|
|
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;
|
|
2012
|
+
}
|
|
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
|
+
]);
|
|
2031
|
+
}
|
|
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;
|
|
2065
|
+
}
|
|
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");
|
|
2079
|
+
}
|
|
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
|
+
}
|
|
2091
|
+
}
|
|
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:`;
|
|
2361
|
+
}
|
|
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);
|
|
996
2380
|
} catch {
|
|
997
|
-
return
|
|
2381
|
+
return [];
|
|
998
2382
|
}
|
|
999
2383
|
}
|
|
1000
|
-
|
|
1001
|
-
const
|
|
1002
|
-
|
|
1003
|
-
|
|
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);
|
|
1004
2415
|
}
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
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
|
+
];
|
|
1011
2425
|
}
|
|
1012
|
-
|
|
1013
|
-
const
|
|
1014
|
-
if (
|
|
1015
|
-
|
|
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
|
+
});
|
|
1016
2458
|
}
|
|
1017
|
-
return
|
|
1018
|
-
content: generateAgentMarkdown(suggestion.name, suggestion.description, suggestion.pattern),
|
|
1019
|
-
filePath: join3(agentsDir, `${suggestion.name}.md`)
|
|
1020
|
-
};
|
|
2459
|
+
return instincts;
|
|
1021
2460
|
}
|
|
1022
|
-
function
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
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);
|
|
1033
2520
|
}
|
|
1034
2521
|
}
|
|
1035
|
-
async function
|
|
1036
|
-
const
|
|
1037
|
-
if (!
|
|
1038
|
-
|
|
2522
|
+
async function shouldRunObserver(ctx) {
|
|
2523
|
+
const state = await ctx.stateManager.read();
|
|
2524
|
+
if (!state.preferences.learningEnabled) {
|
|
2525
|
+
return false;
|
|
1039
2526
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
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
|
+
}
|
|
1043
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;
|
|
1044
2590
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
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 [];
|
|
1048
2604
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
2605
|
+
}
|
|
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"]
|
|
2614
|
+
}
|
|
2615
|
+
);
|
|
2616
|
+
return result.trim().split("\n").filter(Boolean);
|
|
2617
|
+
} catch {
|
|
2618
|
+
return [];
|
|
1051
2619
|
}
|
|
1052
|
-
return buildPluginEvolution(ctx, suggestion);
|
|
1053
2620
|
}
|
|
1054
|
-
function
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
}
|
|
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
|
+
}
|
|
1063
2632
|
}
|
|
1064
|
-
function
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
pattern.surfaced = true;
|
|
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);
|
|
1070
2638
|
}
|
|
2639
|
+
return patterns;
|
|
1071
2640
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
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
|
+
}
|
|
1079
3072
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
case "file":
|
|
1092
|
-
if (pattern.count >= 5) {
|
|
1093
|
-
return {
|
|
1094
|
-
type: "hook",
|
|
1095
|
-
name: `watch-${pattern.description.split("/").pop()?.replace(/\./g, "-") || "file"}`,
|
|
1096
|
-
description: ctx.i18n.t("evolution.suggest.file.description", {
|
|
1097
|
-
pattern: pattern.description
|
|
1098
|
-
}),
|
|
1099
|
-
reason: ctx.i18n.t("evolution.suggest.file.reason", { count: pattern.count }),
|
|
1100
|
-
pattern
|
|
1101
|
-
};
|
|
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;
|
|
1102
3084
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
pattern
|
|
1114
|
-
};
|
|
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);
|
|
1115
3095
|
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
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;
|
|
1128
3131
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
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);
|
|
1139
3143
|
}
|
|
1140
|
-
break;
|
|
1141
|
-
}
|
|
1142
|
-
return null;
|
|
1143
|
-
}
|
|
1144
|
-
async function getEvolutionSuggestions(ctx) {
|
|
1145
|
-
const state = await ctx.stateManager.read();
|
|
1146
|
-
const suggestions = [];
|
|
1147
|
-
for (const pattern of state.patterns) {
|
|
1148
|
-
if (pattern.surfaced) continue;
|
|
1149
|
-
const suggestion = suggestEvolution(pattern, ctx);
|
|
1150
|
-
if (suggestion) {
|
|
1151
|
-
suggestions.push(suggestion);
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
return suggestions;
|
|
1155
|
-
}
|
|
1156
|
-
async function evolveCapability(ctx, suggestion) {
|
|
1157
|
-
const state = await ctx.stateManager.read();
|
|
1158
|
-
const { filePath, content } = await buildEvolutionOutput(ctx, suggestion);
|
|
1159
|
-
await writeFile2(filePath, content, "utf-8");
|
|
1160
|
-
const capability = createCapabilityFromSuggestion(suggestion);
|
|
1161
|
-
updateEvolutionState(state, capability, suggestion);
|
|
1162
|
-
await ctx.stateManager.save(state);
|
|
1163
|
-
await ctx.stateManager.addMilestone(
|
|
1164
|
-
ctx.i18n.t("milestone.evolved", {
|
|
1165
|
-
name: capability.name,
|
|
1166
|
-
type: formatCapabilityType(ctx.i18n, capability.type)
|
|
1167
3144
|
})
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
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")}
|
|
1174
3182
|
|
|
1175
3183
|
`;
|
|
1176
|
-
|
|
3184
|
+
output += `${i18n2.t("evolve.menu_intro")}
|
|
3185
|
+
|
|
1177
3186
|
`;
|
|
1178
|
-
|
|
3187
|
+
for (const s of suggestions) {
|
|
3188
|
+
output += `### \u2728 ${s.name}
|
|
1179
3189
|
`;
|
|
1180
|
-
|
|
1181
|
-
|
|
3190
|
+
output += `- **${i18n2.t("evolve.menu_type")}**: ${formatCapabilityType(i18n2, s.type)}
|
|
1182
3191
|
`;
|
|
1183
|
-
|
|
3192
|
+
output += `- **${i18n2.t("evolve.menu_reason")}**: ${s.reason}
|
|
3193
|
+
`;
|
|
3194
|
+
output += `- **${i18n2.t("evolve.menu_pattern_id")}**: \`${s.pattern.id}\`
|
|
1184
3195
|
|
|
1185
3196
|
`;
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
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
|
+
|
|
1190
3214
|
`;
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
result += `${ctx.i18n.t("evolution.result.hook")}
|
|
3215
|
+
output += `${i18n2.t("capabilities.intro")}
|
|
3216
|
+
|
|
1194
3217
|
`;
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
result += `${ctx.i18n.t("evolution.result.skill")}
|
|
3218
|
+
for (const cap of state.evolution.capabilities) {
|
|
3219
|
+
output += `### \u2728 ${cap.name}
|
|
1198
3220
|
`;
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
3221
|
+
output += `- **${i18n2.t("capabilities.type")}**: ${formatCapabilityType(
|
|
3222
|
+
i18n2,
|
|
3223
|
+
cap.type
|
|
3224
|
+
)}
|
|
1202
3225
|
`;
|
|
1203
|
-
|
|
1204
|
-
case "mcp":
|
|
1205
|
-
result += `${ctx.i18n.t("evolution.result.mcp", { name: capability.name })}
|
|
3226
|
+
output += `- **${i18n2.t("capabilities.description")}**: ${cap.description}
|
|
1206
3227
|
`;
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
technicalTerms: true
|
|
1219
|
-
},
|
|
1220
|
-
"semi-technical": {
|
|
1221
|
-
greetingStyle: "casual",
|
|
1222
|
-
detailLevel: "medium",
|
|
1223
|
-
useEmoji: false,
|
|
1224
|
-
technicalTerms: true
|
|
1225
|
-
},
|
|
1226
|
-
"non-technical": {
|
|
1227
|
-
greetingStyle: "formal",
|
|
1228
|
-
detailLevel: "low",
|
|
1229
|
-
useEmoji: true,
|
|
1230
|
-
technicalTerms: false
|
|
1231
|
-
},
|
|
1232
|
-
chaotic: {
|
|
1233
|
-
greetingStyle: "chaotic",
|
|
1234
|
-
detailLevel: "medium",
|
|
1235
|
-
useEmoji: true,
|
|
1236
|
-
technicalTerms: true
|
|
1237
|
-
}
|
|
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
|
+
};
|
|
1238
3239
|
};
|
|
1239
|
-
function getLevelConfig(level) {
|
|
1240
|
-
return LEVEL_CONFIGS[level ?? "technical"];
|
|
1241
|
-
}
|
|
1242
3240
|
|
|
1243
|
-
// src/tools.ts
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
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;
|
|
1247
3249
|
return {
|
|
1248
|
-
"mimic:
|
|
1249
|
-
description:
|
|
1250
|
-
args: {
|
|
1251
|
-
|
|
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) {
|
|
1252
3256
|
const i18n2 = await i18nPromise;
|
|
1253
|
-
const
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
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");
|
|
1257
3260
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
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")}
|
|
1264
3267
|
|
|
1265
3268
|
`;
|
|
1266
|
-
|
|
1267
|
-
sessions: state.journey.sessionCount,
|
|
1268
|
-
patterns: state.patterns.length
|
|
1269
|
-
})}
|
|
3269
|
+
output += `${i18n2.t("instincts.total", { count: filtered.length })}
|
|
1270
3270
|
|
|
1271
3271
|
`;
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
3272
|
+
for (const [domain, domainInstincts] of byDomain) {
|
|
3273
|
+
output += `### ${domain}
|
|
1275
3274
|
`;
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
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}
|
|
1279
3279
|
`;
|
|
1280
|
-
|
|
1281
|
-
greeting += `- ${o.observation}
|
|
3280
|
+
output += ` ${inst.description}
|
|
1282
3281
|
`;
|
|
1283
3282
|
}
|
|
3283
|
+
output += "\n";
|
|
1284
3284
|
}
|
|
1285
|
-
return
|
|
3285
|
+
return output;
|
|
1286
3286
|
}
|
|
1287
3287
|
}),
|
|
1288
|
-
"mimic:
|
|
1289
|
-
description:
|
|
3288
|
+
"mimic:export": tool4({
|
|
3289
|
+
description: i18n.t("tool.export.description"),
|
|
1290
3290
|
args: {},
|
|
1291
3291
|
async execute() {
|
|
1292
3292
|
const i18n2 = await i18nPromise;
|
|
1293
|
-
const
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
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")}
|
|
1298
3350
|
|
|
1299
3351
|
`;
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
output += `${i18n2.t("status.patterns", {
|
|
1303
|
-
total: state.patterns.length,
|
|
1304
|
-
surfaced: state.patterns.filter((p) => p.surfaced).length
|
|
1305
|
-
})}
|
|
3352
|
+
for (const applied of applicable) {
|
|
3353
|
+
output += `${formatInstinctSuggestion(applied, i18n2)}
|
|
1306
3354
|
`;
|
|
1307
|
-
|
|
3355
|
+
output += ` \u2192 ${applied.instinct.description}
|
|
1308
3356
|
|
|
1309
3357
|
`;
|
|
1310
|
-
|
|
1311
|
-
|
|
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
|
+
|
|
1312
3381
|
`;
|
|
1313
|
-
|
|
1314
|
-
output += `- ${f}
|
|
3382
|
+
output += `**${i18n2.t("identity.personality")}**: ${identity.personality}
|
|
1315
3383
|
`;
|
|
1316
|
-
|
|
1317
|
-
output += "\n";
|
|
1318
|
-
}
|
|
1319
|
-
if (gitHistory.length > 0) {
|
|
1320
|
-
output += `${i18n2.t("status.recent_commits")}
|
|
3384
|
+
output += `**${i18n2.t("identity.awakened")}**: ${format3(awakened, "yyyy-MM-dd")} (${daysAlive} ${i18n2.t("identity.days")})
|
|
1321
3385
|
`;
|
|
1322
|
-
|
|
1323
|
-
output += `- ${c}
|
|
3386
|
+
output += `**${i18n2.t("identity.instincts_learned")}**: ${identity.totalInstinctsLearned}
|
|
1324
3387
|
`;
|
|
1325
|
-
|
|
1326
|
-
}
|
|
1327
|
-
const suggestions = await surfacePatterns(ctx);
|
|
1328
|
-
if (suggestions.length > 0) {
|
|
1329
|
-
output += `
|
|
1330
|
-
${i18n2.t("status.suggestions")}
|
|
3388
|
+
output += `**${i18n2.t("identity.evolutions")}**: ${identity.totalEvolutions}
|
|
1331
3389
|
`;
|
|
1332
|
-
|
|
1333
|
-
|
|
3390
|
+
if (identity.favoriteDomainsRank.length > 0) {
|
|
3391
|
+
output += `**${i18n2.t("identity.favorite_domains")}**: ${identity.favoriteDomainsRank.join(", ")}
|
|
1334
3392
|
`;
|
|
1335
|
-
}
|
|
1336
3393
|
}
|
|
1337
3394
|
return output;
|
|
1338
3395
|
}
|
|
1339
3396
|
}),
|
|
1340
|
-
"mimic:
|
|
1341
|
-
description:
|
|
3397
|
+
"mimic:sequences": tool4({
|
|
3398
|
+
description: i18n.t("tool.sequences.description"),
|
|
1342
3399
|
args: {},
|
|
1343
3400
|
async execute() {
|
|
1344
3401
|
const i18n2 = await i18nPromise;
|
|
1345
|
-
const ctx = { stateManager, directory, i18n: i18n2 };
|
|
1346
3402
|
const state = await stateManager.read();
|
|
1347
|
-
const
|
|
1348
|
-
|
|
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")}`;
|
|
1349
3476
|
}
|
|
1350
3477
|
}),
|
|
1351
|
-
"mimic:
|
|
1352
|
-
description:
|
|
1353
|
-
args: {
|
|
1354
|
-
|
|
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) {
|
|
1355
3553
|
const i18n2 = await i18nPromise;
|
|
1356
3554
|
const state = await stateManager.read();
|
|
1357
|
-
|
|
1358
|
-
|
|
3555
|
+
const pattern = state.patterns.find((p) => p.id === args.patternId);
|
|
3556
|
+
if (!pattern) {
|
|
3557
|
+
return i18n2.t("surface.not_found", { id: args.patternId });
|
|
1359
3558
|
}
|
|
1360
|
-
|
|
3559
|
+
pattern.surfaced = true;
|
|
3560
|
+
await stateManager.save(state);
|
|
3561
|
+
return i18n2.t("surface.marked", { description: pattern.description });
|
|
3562
|
+
}
|
|
3563
|
+
})
|
|
3564
|
+
};
|
|
3565
|
+
};
|
|
1361
3566
|
|
|
1362
|
-
|
|
1363
|
-
|
|
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
|
+
}
|
|
1364
3626
|
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
}
|
|
1372
|
-
for (const [type, patterns] of byType) {
|
|
1373
|
-
output += `${i18n2.t("patterns.section", {
|
|
1374
|
-
type: formatPatternType(i18n2, type)
|
|
1375
|
-
})}
|
|
1376
|
-
`;
|
|
1377
|
-
for (const p of patterns.slice(0, 10)) {
|
|
1378
|
-
const status = p.surfaced ? "\u2713" : "\u25CB";
|
|
1379
|
-
output += `${status} **${p.description}** (${p.count}x)
|
|
1380
|
-
`;
|
|
1381
|
-
}
|
|
1382
|
-
output += "\n";
|
|
1383
|
-
}
|
|
1384
|
-
return output;
|
|
1385
|
-
}
|
|
1386
|
-
}),
|
|
1387
|
-
"mimic:observe": tool({
|
|
1388
|
-
description: baseI18n.t("tool.observe.description"),
|
|
1389
|
-
args: {
|
|
1390
|
-
observation: tool.schema.string().describe(baseI18n.t("tool.observe.args.observation"))
|
|
1391
|
-
},
|
|
1392
|
-
async execute(args) {
|
|
1393
|
-
const i18n2 = await i18nPromise;
|
|
1394
|
-
await stateManager.addObservation(args.observation);
|
|
1395
|
-
return i18n2.t("observe.recorded", { observation: args.observation });
|
|
1396
|
-
}
|
|
1397
|
-
}),
|
|
1398
|
-
"mimic:milestone": tool({
|
|
1399
|
-
description: baseI18n.t("tool.milestone.description"),
|
|
1400
|
-
args: {
|
|
1401
|
-
milestone: tool.schema.string().describe(baseI18n.t("tool.milestone.args.milestone"))
|
|
1402
|
-
},
|
|
1403
|
-
async execute(args) {
|
|
1404
|
-
const i18n2 = await i18nPromise;
|
|
1405
|
-
await stateManager.addMilestone(args.milestone);
|
|
1406
|
-
return i18n2.t("milestone.recorded", { milestone: args.milestone });
|
|
1407
|
-
}
|
|
1408
|
-
}),
|
|
1409
|
-
"mimic:stats": tool({
|
|
1410
|
-
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"),
|
|
1411
3633
|
args: {},
|
|
1412
3634
|
async execute() {
|
|
1413
3635
|
const i18n2 = await i18nPromise;
|
|
1414
3636
|
const state = await stateManager.read();
|
|
1415
|
-
const sessionFiles = await
|
|
3637
|
+
const sessionFiles = await readdir3(stateManager.getSessionsDir()).catch(() => []);
|
|
1416
3638
|
return `## ${i18n2.t("stats.title")}
|
|
1417
3639
|
|
|
1418
3640
|
- **${i18n2.t("stats.version")}**: ${state.version}
|
|
@@ -1422,17 +3644,17 @@ ${i18n2.t("status.suggestions")}
|
|
|
1422
3644
|
- **${i18n2.t("stats.milestones")}**: ${state.journey.milestones.length}
|
|
1423
3645
|
- **${i18n2.t("stats.observations")}**: ${state.journey.observations.length}
|
|
1424
3646
|
- **${i18n2.t("stats.session_records")}**: ${sessionFiles.length}
|
|
1425
|
-
- **${i18n2.t("stats.first_session")}**: ${
|
|
3647
|
+
- **${i18n2.t("stats.first_session")}**: ${format4(state.project.firstSession, "yyyy-MM-dd HH:mm:ss")}
|
|
1426
3648
|
- **${i18n2.t("stats.learning_enabled")}**: ${state.preferences.learningEnabled}
|
|
1427
3649
|
- **${i18n2.t("stats.suggestions_enabled")}**: ${state.preferences.suggestionEnabled}`;
|
|
1428
3650
|
}
|
|
1429
3651
|
}),
|
|
1430
|
-
"mimic:configure":
|
|
1431
|
-
description:
|
|
3652
|
+
"mimic:configure": tool7({
|
|
3653
|
+
description: i18n.t("tool.configure.description"),
|
|
1432
3654
|
args: {
|
|
1433
|
-
learningEnabled:
|
|
1434
|
-
suggestionEnabled:
|
|
1435
|
-
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"))
|
|
1436
3658
|
},
|
|
1437
3659
|
async execute(args) {
|
|
1438
3660
|
const i18n2 = await i18nPromise;
|
|
@@ -1451,105 +3673,27 @@ ${i18n2.t("status.suggestions")}
|
|
|
1451
3673
|
${JSON.stringify(state.preferences, null, 2)}`;
|
|
1452
3674
|
}
|
|
1453
3675
|
}),
|
|
1454
|
-
"mimic:
|
|
1455
|
-
description:
|
|
1456
|
-
args: {
|
|
1457
|
-
patternId: tool.schema.string().describe(baseI18n.t("tool.surface.args.patternId"))
|
|
1458
|
-
},
|
|
1459
|
-
async execute(args) {
|
|
1460
|
-
const i18n2 = await i18nPromise;
|
|
1461
|
-
const state = await stateManager.read();
|
|
1462
|
-
const pattern = state.patterns.find((p) => p.id === args.patternId);
|
|
1463
|
-
if (!pattern) {
|
|
1464
|
-
return i18n2.t("surface.not_found", { id: args.patternId });
|
|
1465
|
-
}
|
|
1466
|
-
pattern.surfaced = true;
|
|
1467
|
-
await stateManager.save(state);
|
|
1468
|
-
return i18n2.t("surface.marked", { description: pattern.description });
|
|
1469
|
-
}
|
|
1470
|
-
}),
|
|
1471
|
-
"mimic:reset": tool({
|
|
1472
|
-
description: baseI18n.t("tool.reset.description"),
|
|
3676
|
+
"mimic:reset": tool7({
|
|
3677
|
+
description: i18n.t("tool.reset.description"),
|
|
1473
3678
|
args: {
|
|
1474
|
-
confirm:
|
|
3679
|
+
confirm: tool7.schema.boolean().describe(i18n.t("tool.reset.args.confirm"))
|
|
1475
3680
|
},
|
|
1476
3681
|
async execute(args) {
|
|
1477
3682
|
const i18n2 = await i18nPromise;
|
|
1478
3683
|
if (!args.confirm) {
|
|
1479
3684
|
return i18n2.t("reset.cancelled");
|
|
1480
3685
|
}
|
|
1481
|
-
await
|
|
3686
|
+
await writeFile8(
|
|
1482
3687
|
stateManager.getStatePath(),
|
|
1483
3688
|
JSON.stringify(createDefaultState(stateManager.getProjectName()), null, 2)
|
|
1484
3689
|
);
|
|
1485
3690
|
return i18n2.t("reset.done");
|
|
1486
3691
|
}
|
|
1487
3692
|
}),
|
|
1488
|
-
"mimic:
|
|
1489
|
-
description:
|
|
1490
|
-
args: {},
|
|
1491
|
-
async execute() {
|
|
1492
|
-
const i18n2 = await i18nPromise;
|
|
1493
|
-
const ctx = { stateManager, directory, i18n: i18n2 };
|
|
1494
|
-
const state = await stateManager.read();
|
|
1495
|
-
const gitHistory = getGitHistory(directory, 20);
|
|
1496
|
-
const recentFiles = getRecentlyModifiedFiles(directory);
|
|
1497
|
-
return formatGrowAnalysis(ctx, state, gitHistory, recentFiles);
|
|
1498
|
-
}
|
|
1499
|
-
}),
|
|
1500
|
-
"mimic:evolve": tool({
|
|
1501
|
-
description: baseI18n.t("tool.evolve.description"),
|
|
1502
|
-
args: {
|
|
1503
|
-
accept: tool.schema.string().optional().describe(baseI18n.t("tool.evolve.args.accept"))
|
|
1504
|
-
},
|
|
1505
|
-
async execute(args) {
|
|
1506
|
-
const i18n2 = await i18nPromise;
|
|
1507
|
-
const ctx = { stateManager, directory, i18n: i18n2 };
|
|
1508
|
-
if (args.accept) {
|
|
1509
|
-
const suggestions2 = await getEvolutionSuggestions(ctx);
|
|
1510
|
-
const suggestion = suggestions2.find((s) => s.pattern.id === args.accept);
|
|
1511
|
-
if (!suggestion) {
|
|
1512
|
-
return i18n2.t("evolve.no_pattern", { id: args.accept });
|
|
1513
|
-
}
|
|
1514
|
-
const { capability, filePath } = await evolveCapability(ctx, suggestion);
|
|
1515
|
-
return `${i18n2.t("evolve.absorbed_header")}
|
|
1516
|
-
|
|
1517
|
-
${formatEvolutionResult(
|
|
1518
|
-
ctx,
|
|
1519
|
-
capability,
|
|
1520
|
-
filePath
|
|
1521
|
-
)}`;
|
|
1522
|
-
}
|
|
1523
|
-
const suggestions = await getEvolutionSuggestions(ctx);
|
|
1524
|
-
if (suggestions.length === 0) {
|
|
1525
|
-
return i18n2.t("evolve.empty");
|
|
1526
|
-
}
|
|
1527
|
-
let output = `${i18n2.t("evolve.menu_title")}
|
|
1528
|
-
|
|
1529
|
-
`;
|
|
1530
|
-
output += `${i18n2.t("evolve.menu_intro")}
|
|
1531
|
-
|
|
1532
|
-
`;
|
|
1533
|
-
for (const s of suggestions) {
|
|
1534
|
-
output += `### \u2728 ${s.name}
|
|
1535
|
-
`;
|
|
1536
|
-
output += `- **${i18n2.t("evolve.menu_type")}**: ${formatCapabilityType(i18n2, s.type)}
|
|
1537
|
-
`;
|
|
1538
|
-
output += `- **${i18n2.t("evolve.menu_reason")}**: ${s.reason}
|
|
1539
|
-
`;
|
|
1540
|
-
output += `- **${i18n2.t("evolve.menu_pattern_id")}**: \`${s.pattern.id}\`
|
|
1541
|
-
|
|
1542
|
-
`;
|
|
1543
|
-
}
|
|
1544
|
-
output += `
|
|
1545
|
-
${i18n2.t("evolve.menu_footer")}`;
|
|
1546
|
-
return output;
|
|
1547
|
-
}
|
|
1548
|
-
}),
|
|
1549
|
-
"mimic:level": tool({
|
|
1550
|
-
description: baseI18n.t("tool.level.description"),
|
|
3693
|
+
"mimic:level": tool7({
|
|
3694
|
+
description: i18n.t("tool.level.description"),
|
|
1551
3695
|
args: {
|
|
1552
|
-
level:
|
|
3696
|
+
level: tool7.schema.enum(["technical", "semi-technical", "non-technical", "chaotic"]).describe(i18n.t("tool.level.args.level"))
|
|
1553
3697
|
},
|
|
1554
3698
|
async execute(args) {
|
|
1555
3699
|
const i18n2 = await i18nPromise;
|
|
@@ -1564,11 +3708,11 @@ ${i18n2.t("evolve.menu_footer")}`;
|
|
|
1564
3708
|
});
|
|
1565
3709
|
}
|
|
1566
3710
|
}),
|
|
1567
|
-
"mimic:focus":
|
|
1568
|
-
description:
|
|
3711
|
+
"mimic:focus": tool7({
|
|
3712
|
+
description: i18n.t("tool.focus.description"),
|
|
1569
3713
|
args: {
|
|
1570
|
-
focus:
|
|
1571
|
-
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"))
|
|
1572
3716
|
},
|
|
1573
3717
|
async execute(args) {
|
|
1574
3718
|
const i18n2 = await i18nPromise;
|
|
@@ -1591,126 +3735,26 @@ ${i18n2.t("evolve.menu_footer")}`;
|
|
|
1591
3735
|
`;
|
|
1592
3736
|
return output;
|
|
1593
3737
|
}
|
|
1594
|
-
}),
|
|
1595
|
-
"mimic:mcp-search": tool({
|
|
1596
|
-
description: baseI18n.t("tool.mcp_search.description"),
|
|
1597
|
-
args: {
|
|
1598
|
-
query: tool.schema.string().describe(baseI18n.t("tool.mcp_search.args.query"))
|
|
1599
|
-
},
|
|
1600
|
-
async execute(args) {
|
|
1601
|
-
const i18n2 = await i18nPromise;
|
|
1602
|
-
const searchUrl = `https://mcpmarket.com/search?q=${encodeURIComponent(args.query)}`;
|
|
1603
|
-
const popular = [
|
|
1604
|
-
{
|
|
1605
|
-
name: "context7",
|
|
1606
|
-
desc: i18n2.t("mcp_search.desc.context7"),
|
|
1607
|
-
url: "https://mcp.context7.com/mcp"
|
|
1608
|
-
},
|
|
1609
|
-
{
|
|
1610
|
-
name: "github",
|
|
1611
|
-
desc: i18n2.t("mcp_search.desc.github"),
|
|
1612
|
-
url: "https://mcp.github.com"
|
|
1613
|
-
},
|
|
1614
|
-
{
|
|
1615
|
-
name: "supabase",
|
|
1616
|
-
desc: i18n2.t("mcp_search.desc.supabase"),
|
|
1617
|
-
url: "https://mcp.supabase.com"
|
|
1618
|
-
},
|
|
1619
|
-
{ name: "playwright", desc: i18n2.t("mcp_search.desc.playwright") },
|
|
1620
|
-
{ name: "firecrawl", desc: i18n2.t("mcp_search.desc.firecrawl") }
|
|
1621
|
-
];
|
|
1622
|
-
const popularLines = popular.map(
|
|
1623
|
-
(server) => server.url ? `- **${server.name}** - ${server.desc}: \`${server.url}\`` : `- **${server.name}** - ${server.desc}`
|
|
1624
|
-
).join("\n");
|
|
1625
|
-
return `${i18n2.t("mcp_search.header", {
|
|
1626
|
-
query: args.query,
|
|
1627
|
-
url: searchUrl
|
|
1628
|
-
})}
|
|
1629
|
-
|
|
1630
|
-
${i18n2.t("mcp_search.popular")}
|
|
1631
|
-
${popularLines}
|
|
1632
|
-
|
|
1633
|
-
${i18n2.t("mcp_search.add")}`;
|
|
1634
|
-
}
|
|
1635
|
-
}),
|
|
1636
|
-
"mimic:mcp": tool({
|
|
1637
|
-
description: baseI18n.t("tool.mcp.description"),
|
|
1638
|
-
args: {
|
|
1639
|
-
name: tool.schema.string().describe(baseI18n.t("tool.mcp.args.name")),
|
|
1640
|
-
url: tool.schema.string().optional().describe(baseI18n.t("tool.mcp.args.url")),
|
|
1641
|
-
command: tool.schema.string().optional().describe(baseI18n.t("tool.mcp.args.command"))
|
|
1642
|
-
},
|
|
1643
|
-
async execute(args) {
|
|
1644
|
-
const i18n2 = await i18nPromise;
|
|
1645
|
-
const { existsSync: existsSync4 } = await import("fs");
|
|
1646
|
-
const { readFile: readFile4, writeFile: fsWriteFile, mkdir: mkdir3 } = await import("fs/promises");
|
|
1647
|
-
const { join: join4 } = await import("path");
|
|
1648
|
-
const opencodeDir = join4(directory, ".opencode");
|
|
1649
|
-
if (!existsSync4(opencodeDir)) {
|
|
1650
|
-
await mkdir3(opencodeDir, { recursive: true });
|
|
1651
|
-
}
|
|
1652
|
-
const configPath = join4(directory, "opencode.json");
|
|
1653
|
-
let config = {};
|
|
1654
|
-
if (existsSync4(configPath)) {
|
|
1655
|
-
try {
|
|
1656
|
-
config = JSON.parse(await readFile4(configPath, "utf-8"));
|
|
1657
|
-
} catch {
|
|
1658
|
-
config = {};
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
const mcpEntry = {};
|
|
1662
|
-
if (args.url) {
|
|
1663
|
-
mcpEntry.type = "remote";
|
|
1664
|
-
mcpEntry.url = args.url;
|
|
1665
|
-
} else if (args.command) {
|
|
1666
|
-
mcpEntry.type = "local";
|
|
1667
|
-
mcpEntry.command = args.command.split(",").map((s) => s.trim());
|
|
1668
|
-
} else {
|
|
1669
|
-
return i18n2.t("mcp.need_url_or_command");
|
|
1670
|
-
}
|
|
1671
|
-
mcpEntry.enabled = true;
|
|
1672
|
-
config.mcp = { ...config.mcp || {}, [args.name]: mcpEntry };
|
|
1673
|
-
await fsWriteFile(configPath, JSON.stringify(config, null, 2));
|
|
1674
|
-
await stateManager.addMilestone(i18n2.t("milestone.mcp_added", { name: args.name }));
|
|
1675
|
-
return i18n2.t("mcp.added", { name: args.name });
|
|
1676
|
-
}
|
|
1677
|
-
}),
|
|
1678
|
-
"mimic:capabilities": tool({
|
|
1679
|
-
description: baseI18n.t("tool.capabilities.description"),
|
|
1680
|
-
args: {},
|
|
1681
|
-
async execute() {
|
|
1682
|
-
const i18n2 = await i18nPromise;
|
|
1683
|
-
const state = await stateManager.read();
|
|
1684
|
-
if (state.evolution.capabilities.length === 0) {
|
|
1685
|
-
return i18n2.t("capabilities.empty");
|
|
1686
|
-
}
|
|
1687
|
-
let output = `${i18n2.t("capabilities.title")}
|
|
1688
|
-
|
|
1689
|
-
`;
|
|
1690
|
-
output += `${i18n2.t("capabilities.intro")}
|
|
1691
|
-
|
|
1692
|
-
`;
|
|
1693
|
-
for (const cap of state.evolution.capabilities) {
|
|
1694
|
-
output += `### \u2728 ${cap.name}
|
|
1695
|
-
`;
|
|
1696
|
-
output += `- **${i18n2.t("capabilities.type")}**: ${formatCapabilityType(
|
|
1697
|
-
i18n2,
|
|
1698
|
-
cap.type
|
|
1699
|
-
)}
|
|
1700
|
-
`;
|
|
1701
|
-
output += `- **${i18n2.t("capabilities.description")}**: ${cap.description}
|
|
1702
|
-
`;
|
|
1703
|
-
output += `- **${i18n2.t("capabilities.consumed")}**: ${format2(
|
|
1704
|
-
new Date(cap.createdAt),
|
|
1705
|
-
"yyyy-MM-dd"
|
|
1706
|
-
)}
|
|
1707
|
-
|
|
1708
|
-
`;
|
|
1709
|
-
}
|
|
1710
|
-
return output;
|
|
1711
|
-
}
|
|
1712
3738
|
})
|
|
1713
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);
|
|
1714
3758
|
}
|
|
1715
3759
|
|
|
1716
3760
|
// src/index.ts
|
|
@@ -1718,12 +3762,24 @@ var mimic = async ({ directory, client }) => {
|
|
|
1718
3762
|
const stateManager = new StateManager(directory);
|
|
1719
3763
|
await stateManager.initialize();
|
|
1720
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();
|
|
1721
3775
|
const ctx = { stateManager, directory, i18n, client };
|
|
1722
|
-
const sessionId =
|
|
3776
|
+
const sessionId = generateId();
|
|
1723
3777
|
const sessionStartTime = Date.now();
|
|
1724
3778
|
const toolCalls = [];
|
|
1725
3779
|
const filesEdited = /* @__PURE__ */ new Set();
|
|
3780
|
+
let currentBranch;
|
|
1726
3781
|
const handleSessionCreated = async () => {
|
|
3782
|
+
await stateManager.initializeIdentity();
|
|
1727
3783
|
const state = await stateManager.read();
|
|
1728
3784
|
const timeSince = analyzeTimeSinceLastSession(state.journey.lastSession);
|
|
1729
3785
|
state.statistics.totalSessions += 1;
|
|
@@ -1731,19 +3787,78 @@ var mimic = async ({ directory, client }) => {
|
|
|
1731
3787
|
state.journey.sessionCount += 1;
|
|
1732
3788
|
state.journey.lastSession = (/* @__PURE__ */ new Date()).toISOString();
|
|
1733
3789
|
await stateManager.save(state);
|
|
3790
|
+
await observationLog.logSessionStart(sessionId);
|
|
1734
3791
|
if (timeSince === "long-break") {
|
|
1735
3792
|
await stateManager.addObservation(i18n.t("obs.returned_after_long_break"));
|
|
1736
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
|
+
}
|
|
1737
3807
|
await client.tui.showToast({
|
|
1738
3808
|
body: {
|
|
1739
3809
|
message: i18n.t("log.session_started", {
|
|
1740
3810
|
sessions: state.journey.sessionCount,
|
|
1741
3811
|
patterns: state.patterns.length
|
|
1742
|
-
}),
|
|
3812
|
+
}) + hintMessage,
|
|
1743
3813
|
variant: "info"
|
|
1744
3814
|
}
|
|
1745
3815
|
});
|
|
1746
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
|
+
};
|
|
1747
3862
|
const handleSessionIdle = async () => {
|
|
1748
3863
|
const newPatterns = await detectPatterns(ctx);
|
|
1749
3864
|
if (newPatterns.length > 0) {
|
|
@@ -1751,6 +3866,7 @@ var mimic = async ({ directory, client }) => {
|
|
|
1751
3866
|
state.patterns.push(...newPatterns);
|
|
1752
3867
|
await stateManager.save(state);
|
|
1753
3868
|
}
|
|
3869
|
+
await processEvolution();
|
|
1754
3870
|
const suggestions = await surfacePatterns(ctx);
|
|
1755
3871
|
for (const suggestion of suggestions) {
|
|
1756
3872
|
await client.tui.showToast({
|
|
@@ -1763,28 +3879,76 @@ var mimic = async ({ directory, client }) => {
|
|
|
1763
3879
|
}
|
|
1764
3880
|
};
|
|
1765
3881
|
const handleFileEdited = async (event) => {
|
|
1766
|
-
if (!("properties" in event)) return;
|
|
1767
|
-
const
|
|
1768
|
-
if (!
|
|
1769
|
-
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);
|
|
1770
3887
|
const state = await stateManager.read();
|
|
1771
|
-
state.statistics.filesModified[
|
|
3888
|
+
state.statistics.filesModified[file] = (state.statistics.filesModified[file] || 0) + 1;
|
|
1772
3889
|
await stateManager.save(state);
|
|
1773
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
|
+
};
|
|
1774
3918
|
return {
|
|
1775
3919
|
async event({ event }) {
|
|
1776
3920
|
switch (event.type) {
|
|
1777
|
-
case
|
|
3921
|
+
case OPENCODE_EVENTS.SESSION_CREATED:
|
|
1778
3922
|
await handleSessionCreated();
|
|
1779
3923
|
return;
|
|
1780
|
-
case
|
|
3924
|
+
case OPENCODE_EVENTS.SESSION_IDLE:
|
|
1781
3925
|
await handleSessionIdle();
|
|
1782
3926
|
return;
|
|
1783
|
-
case
|
|
3927
|
+
case OPENCODE_EVENTS.FILE_EDITED:
|
|
1784
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);
|
|
1785
3949
|
}
|
|
1786
3950
|
},
|
|
1787
|
-
async
|
|
3951
|
+
async [OPENCODE_EVENTS.TOOL_EXECUTE_AFTER](input, _output) {
|
|
1788
3952
|
const state = await stateManager.read();
|
|
1789
3953
|
if (!state.preferences.learningEnabled) return;
|
|
1790
3954
|
const toolCall = {
|
|
@@ -1794,6 +3958,7 @@ var mimic = async ({ directory, client }) => {
|
|
|
1794
3958
|
};
|
|
1795
3959
|
toolCalls.push(toolCall);
|
|
1796
3960
|
state.statistics.totalToolCalls += 1;
|
|
3961
|
+
await observationLog.logToolCall(input.tool, input.callID, sessionId);
|
|
1797
3962
|
const toolPattern = input.tool;
|
|
1798
3963
|
const existing = state.patterns.find(
|
|
1799
3964
|
(p) => p.type === "tool" && p.description === toolPattern
|
|
@@ -1803,7 +3968,7 @@ var mimic = async ({ directory, client }) => {
|
|
|
1803
3968
|
existing.lastSeen = Date.now();
|
|
1804
3969
|
} else {
|
|
1805
3970
|
state.patterns.push({
|
|
1806
|
-
id:
|
|
3971
|
+
id: generateId(),
|
|
1807
3972
|
type: "tool",
|
|
1808
3973
|
description: toolPattern,
|
|
1809
3974
|
count: 1,
|
|
@@ -1814,6 +3979,10 @@ var mimic = async ({ directory, client }) => {
|
|
|
1814
3979
|
});
|
|
1815
3980
|
}
|
|
1816
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
|
+
}
|
|
1817
3986
|
},
|
|
1818
3987
|
async stop() {
|
|
1819
3988
|
const sessionDuration = Date.now() - sessionStartTime;
|
|
@@ -1826,6 +3995,19 @@ var mimic = async ({ directory, client }) => {
|
|
|
1826
3995
|
filesEdited: Array.from(filesEdited)
|
|
1827
3996
|
};
|
|
1828
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
|
+
}
|
|
1829
4011
|
if (toolCalls.length > 20) {
|
|
1830
4012
|
await stateManager.addObservation(
|
|
1831
4013
|
i18n.t("obs.intensive_session", { tools: toolCalls.length })
|