opencode-plugin-mimic 0.1.4 → 0.1.6

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