@yugabytedb/meko-mcp 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +233 -40
  2. package/assets/hooks-handlers/codex-session-end.sh +4 -69
  3. package/assets/hooks-handlers/cursor-session-start.sh +19 -1
  4. package/assets/hooks-handlers/lib/capture-cleanup-cli.js +1473 -0
  5. package/assets/hooks-handlers/lib/capture-notices.js +438 -0
  6. package/assets/hooks-handlers/lib/capture-state.js +2173 -0
  7. package/assets/hooks-handlers/lib/capture-status-cli.js +86 -0
  8. package/assets/hooks-handlers/lib/capture.js +2782 -403
  9. package/assets/hooks-handlers/lib/checkpoint-timer.js +485 -81
  10. package/assets/hooks-handlers/lib/journal-bridge.js +218 -0
  11. package/assets/hooks-handlers/lib/kiro-handler.js +760 -0
  12. package/assets/hooks-handlers/lib/opencode-handler.js +620 -0
  13. package/assets/hooks-handlers/lib/opencode-normalizer.js +145 -0
  14. package/assets/hooks-handlers/lib/terminal-handoff.js +427 -0
  15. package/assets/hooks-handlers/session-end.sh +3 -34
  16. package/assets/opencode-plugin/meko.js +213 -0
  17. package/assets/scripts/meko-statusline.sh +123 -16
  18. package/assets/skills/meko-mcp-tools/SKILL.md +116 -126
  19. package/assets/skills/meko-mcp-tools/references/_sections.md +3 -3
  20. package/assets/skills/meko-mcp-tools/references/tools-agent-id-conventions.md +25 -18
  21. package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +99 -24
  22. package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +1 -1
  23. package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +53 -6
  24. package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +25 -79
  25. package/assets/skills/meko-mcp-tools/references/tools-overview.md +37 -26
  26. package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +14 -8
  27. package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +66 -1
  28. package/assets/skills/meko-mcp-tools/references/tools-user-id-conventions.md +7 -40
  29. package/assets/skills/meko-mcp-tools-desktop/SKILL.md +56 -20
  30. package/assets/skills/meko-mcp-tools-desktop/desktop/README.md +1 -1
  31. package/assets/skills/meko-mcp-tools-desktop/references/_sections.md +1 -1
  32. package/assets/skills/meko-mcp-tools-desktop/references/tools-agent-id-conventions.md +15 -8
  33. package/assets/skills/meko-mcp-tools-desktop/references/tools-cookbook.md +99 -36
  34. package/assets/skills/meko-mcp-tools-desktop/references/tools-known-limitations.md +22 -5
  35. package/assets/skills/meko-mcp-tools-desktop/references/tools-memory-vs-conversation.md +18 -14
  36. package/assets/skills/meko-mcp-tools-desktop/references/tools-overview.md +39 -28
  37. package/assets/skills/meko-mcp-tools-desktop/references/tools-rag-workflow.md +14 -8
  38. package/assets/skills/meko-mcp-tools-desktop/references/tools-troubleshooting.md +67 -1
  39. package/assets/skills/meko-select-datapack-desktop/SKILL.md +4 -4
  40. package/assets/skills/meko-select-datapack-desktop/references/cookbook.md +8 -8
  41. package/assets/skills/meko-select-datapack-desktop/references/role-display-future.md +7 -5
  42. package/assets/skills/meko-select-datapack-desktop/references/troubleshooting.md +4 -4
  43. package/bin/create-meko-setup.mjs +113 -12
  44. package/lib/capture-cleanup.mjs +63 -0
  45. package/lib/capture-status.mjs +67 -0
  46. package/lib/clients/aliases.mjs +5 -0
  47. package/lib/clients/claude-code.mjs +1 -0
  48. package/lib/clients/claude-desktop.mjs +120 -5
  49. package/lib/clients/codex.mjs +85 -37
  50. package/lib/clients/common.mjs +7 -0
  51. package/lib/clients/index.mjs +4 -0
  52. package/lib/clients/kiro.mjs +705 -0
  53. package/lib/clients/opencode.mjs +532 -0
  54. package/lib/config.mjs +158 -1
  55. package/lib/installer.mjs +18 -1
  56. package/lib/migrate/adapters/claude-code.mjs +11 -2
  57. package/lib/migrate/adapters/codex.mjs +497 -0
  58. package/lib/migrate/adapters/memory-md.mjs +27 -8
  59. package/lib/migrate/adapters/opencode.mjs +148 -0
  60. package/lib/migrate/claude-parser.mjs +153 -9
  61. package/lib/migrate/id.mjs +217 -10
  62. package/lib/migrate/mcp-client.mjs +25 -1
  63. package/lib/migrate/opencode-normalizer.mjs +132 -0
  64. package/lib/migrate/orchestrator.mjs +299 -56
  65. package/lib/migrate/progress.mjs +239 -5
  66. package/lib/migrate/registry.mjs +72 -0
  67. package/lib/migrate.mjs +100 -10
  68. package/lib/prompt.mjs +16 -1
  69. package/package.json +4 -4
  70. package/scripts/bundle-assets.mjs +4 -0
@@ -0,0 +1,218 @@
1
+ /** Fsynced OpenCode journal primitives; Kiro keeps a separate implementation. */
2
+ "use strict";
3
+ const fs = require("fs");
4
+ const LOCK_RETRIES = 100;
5
+ const LOCK_RETRY_MS = 10;
6
+ function sleepSync(ms) {
7
+ const cell = new Int32Array(new SharedArrayBuffer(4));
8
+ Atomics.wait(cell, 0, 0, ms);
9
+ }
10
+ function pidAlive(pid) {
11
+ if (!Number.isInteger(pid) || pid <= 0) return false;
12
+ try {
13
+ process.kill(pid, 0);
14
+ return true;
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+ function ensureEventDir(eventDir) {
20
+ fs.mkdirSync(eventDir, { recursive: true, mode: 0o700 });
21
+ try { fs.chmodSync(eventDir, 0o700); } catch { /* best effort */ }
22
+ }
23
+ function acquireEventLock(lockPath, eventDir) {
24
+ ensureEventDir(eventDir);
25
+ for (let attempt = 0; attempt < LOCK_RETRIES; attempt++) {
26
+ try {
27
+ const fd = fs.openSync(lockPath, "wx", 0o600);
28
+ try {
29
+ fs.writeFileSync(fd, JSON.stringify({ pid: process.pid, created_at: Date.now() }));
30
+ } finally {
31
+ fs.closeSync(fd);
32
+ }
33
+ return true;
34
+ } catch (err) {
35
+ if (!err || err.code !== "EEXIST") throw err;
36
+ let existing = null;
37
+ try { existing = JSON.parse(fs.readFileSync(lockPath, "utf8")); } catch {
38
+ try {
39
+ const st = fs.statSync(lockPath);
40
+ if (Date.now() - st.mtimeMs < 2000) {
41
+ sleepSync(LOCK_RETRY_MS);
42
+ continue;
43
+ }
44
+ } catch { /* gone */ }
45
+ }
46
+ if (!existing || !pidAlive(Number(existing.pid))) {
47
+ try { fs.unlinkSync(lockPath); } catch { /* another process won */ }
48
+ continue;
49
+ }
50
+ sleepSync(LOCK_RETRY_MS);
51
+ }
52
+ }
53
+ throw new Error(`timed out acquiring event lock ${lockPath}`);
54
+ }
55
+ function releaseEventLock(lockPath) {
56
+ try {
57
+ const current = JSON.parse(fs.readFileSync(lockPath, "utf8"));
58
+ if (Number(current.pid) === process.pid) fs.unlinkSync(lockPath);
59
+ } catch { /* already gone */ }
60
+ }
61
+ function atomicWriteJson(filePath, value, eventDir) {
62
+ ensureEventDir(eventDir);
63
+ const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
64
+ const fd = fs.openSync(tmp, "w", 0o600);
65
+ try {
66
+ fs.writeFileSync(fd, `${JSON.stringify(value)}\n`, "utf8");
67
+ fs.fsyncSync(fd);
68
+ } finally {
69
+ fs.closeSync(fd);
70
+ }
71
+ fs.renameSync(tmp, filePath);
72
+ try { fs.chmodSync(filePath, 0o600); } catch { /* best effort */ }
73
+ }
74
+ function appendJournal(journalPath, record, eventDir) {
75
+ ensureEventDir(eventDir);
76
+ let separator = "";
77
+ try {
78
+ const contents = fs.readFileSync(journalPath);
79
+ if (contents.length > 0 && contents[contents.length - 1] !== 0x0a) {
80
+ separator = "\n";
81
+ }
82
+ } catch (err) {
83
+ if (!err || err.code !== "ENOENT") throw err;
84
+ }
85
+ const data = Buffer.from(`${separator}${JSON.stringify(record)}\n`, "utf8");
86
+ const fd = fs.openSync(journalPath, "a", 0o600);
87
+ try {
88
+ let offset = 0;
89
+ while (offset < data.length) {
90
+ const written = fs.writeSync(fd, data, offset, data.length - offset, null);
91
+ if (!Number.isInteger(written) || written <= 0) {
92
+ throw new Error(`could not append complete journal record to ${journalPath}`);
93
+ }
94
+ offset += written;
95
+ }
96
+ fs.fsyncSync(fd);
97
+ } finally {
98
+ fs.closeSync(fd);
99
+ }
100
+ try { fs.chmodSync(journalPath, 0o600); } catch { /* best effort */ }
101
+ }
102
+ function journalEmpty(journalPath) {
103
+ try { return fs.statSync(journalPath).size === 0; } catch { return true; }
104
+ }
105
+ function emptyEventState(sessionId) {
106
+ return {
107
+ schema_version: 1,
108
+ session_id: sessionId,
109
+ turn_index: 0,
110
+ pending: null,
111
+ seen_source_ids: [],
112
+ updated_at: new Date().toISOString(),
113
+ };
114
+ }
115
+ function rebuildEventState(journalPath, sessionId) {
116
+ const rebuilt = emptyEventState(sessionId);
117
+ let contents;
118
+ try {
119
+ contents = fs.readFileSync(journalPath, "utf8");
120
+ } catch {
121
+ return rebuilt;
122
+ }
123
+ for (const line of contents.split("\n")) {
124
+ if (!line.trim()) continue;
125
+ let record;
126
+ try {
127
+ record = JSON.parse(line);
128
+ } catch {
129
+ continue;
130
+ }
131
+ if (!record || record.session_id !== sessionId) continue;
132
+ if (record.type === "user") {
133
+ const content = record.message && record.message.content;
134
+ const text = Array.isArray(content) && content[0] && typeof content[0].text === "string"
135
+ ? content[0].text
136
+ : "";
137
+ const uuid = String(record.uuid || "");
138
+ rebuilt.turn_index += 1;
139
+ const mekoObservedMs =
140
+ record.meko && Number.isFinite(record.meko.observed_at_ms)
141
+ ? record.meko.observed_at_ms
142
+ : null;
143
+ const firstObservedAtMs = mekoObservedMs;
144
+ rebuilt.pending = {
145
+ user_uuid: uuid,
146
+ source_message_id: record.source_message_id || null,
147
+ turn_index: rebuilt.turn_index,
148
+ prompt_preview: text.slice(0, 80),
149
+ first_observed_at_ms: firstObservedAtMs,
150
+ };
151
+ if (record.source_message_id) rebuilt.seen_source_ids.push(record.source_message_id);
152
+ // Alias `${userId}:interrupted` so a later original id is already seen.
153
+ const legacySuffix = ":interrupted";
154
+ if (uuid.endsWith(legacySuffix)) {
155
+ const base = uuid.slice(0, -legacySuffix.length);
156
+ if (base) rebuilt.seen_source_ids.push(base);
157
+ }
158
+ } else if (record.type === "assistant" && rebuilt.pending) {
159
+ if (record.source_message_id) rebuilt.seen_source_ids.push(record.source_message_id);
160
+ rebuilt.pending = null;
161
+ }
162
+ }
163
+ return rebuilt;
164
+ }
165
+ function readEventState(statePath, journalPath, sessionId, eventDir) {
166
+ let persisted = null;
167
+ try {
168
+ const value = JSON.parse(fs.readFileSync(statePath, "utf8"));
169
+ if (value && value.schema_version === 1 && value.session_id === sessionId) {
170
+ persisted = value;
171
+ }
172
+ } catch { /* fresh or corrupt rebuildable sidecar */ }
173
+ const rebuilt = rebuildEventState(journalPath, sessionId);
174
+ // MEKO-635: prefer the persisted pending's first_observed_at_ms over the
175
+ // rebuild value from record.meko.observed_at_ms. Fall back to rebuild when
176
+ // the sidecar is missing or names a different pending user.
177
+ let pending = rebuilt.pending;
178
+ const persistedPending = persisted && persisted.pending;
179
+ if (
180
+ pending &&
181
+ persistedPending &&
182
+ persistedPending.user_uuid === pending.user_uuid
183
+ ) {
184
+ const persistedMs = persistedPending.first_observed_at_ms;
185
+ pending = {
186
+ ...pending,
187
+ first_observed_at_ms: Number.isFinite(persistedMs)
188
+ ? persistedMs
189
+ : pending.first_observed_at_ms,
190
+ };
191
+ }
192
+ return {
193
+ ...(persisted || emptyEventState(sessionId)),
194
+ turn_index: rebuilt.turn_index,
195
+ pending,
196
+ seen_source_ids: rebuilt.seen_source_ids,
197
+ tentative_observation:
198
+ pending &&
199
+ persisted &&
200
+ persisted.pending &&
201
+ persisted.pending.user_uuid === pending.user_uuid
202
+ ? persisted.tentative_observation || null
203
+ : null,
204
+ };
205
+ }
206
+ module.exports = {
207
+ LOCK_RETRIES,
208
+ LOCK_RETRY_MS,
209
+ ensureEventDir,
210
+ acquireEventLock,
211
+ releaseEventLock,
212
+ atomicWriteJson,
213
+ appendJournal,
214
+ journalEmpty,
215
+ emptyEventState,
216
+ rebuildEventState,
217
+ readEventState,
218
+ };