@yugabytedb/meko-mcp 2.2.0 → 2.5.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 (67) hide show
  1. package/README.md +168 -61
  2. package/assets/hooks-handlers/lib/capture-cleanup-cli.js +2134 -0
  3. package/assets/hooks-handlers/lib/capture-notices.js +224 -14
  4. package/assets/hooks-handlers/lib/capture-state.js +206 -1
  5. package/assets/hooks-handlers/lib/capture-status-cli.js +6 -2
  6. package/assets/hooks-handlers/lib/capture.js +1364 -139
  7. package/assets/hooks-handlers/lib/checkpoint-timer.js +445 -18
  8. package/assets/hooks-handlers/lib/journal-bridge.js +218 -0
  9. package/assets/hooks-handlers/lib/kiro-handler.js +26 -4
  10. package/assets/hooks-handlers/lib/opencode-handler.js +643 -0
  11. package/assets/hooks-handlers/lib/opencode-normalizer.js +145 -0
  12. package/assets/hooks-handlers/lib/terminal-handoff.js +10 -1
  13. package/assets/opencode-plugin/meko.js +213 -0
  14. package/assets/scripts/meko-statusline.sh +102 -19
  15. package/assets/skills/meko-mcp-tools/SKILL.md +59 -16
  16. package/assets/skills/meko-mcp-tools/references/_sections.md +2 -2
  17. package/assets/skills/meko-mcp-tools/references/tools-agent-id-conventions.md +15 -7
  18. package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +275 -19
  19. package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +7 -4
  20. package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +39 -11
  21. package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +10 -6
  22. package/assets/skills/meko-mcp-tools/references/tools-overview.md +44 -29
  23. package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +79 -14
  24. package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +47 -1
  25. package/assets/skills/meko-mcp-tools/references/tools-user-id-conventions.md +6 -39
  26. package/assets/skills/meko-mcp-tools-desktop/SKILL.md +57 -21
  27. package/assets/skills/meko-mcp-tools-desktop/desktop/README.md +4 -4
  28. package/assets/skills/meko-mcp-tools-desktop/references/_sections.md +1 -1
  29. package/assets/skills/meko-mcp-tools-desktop/references/tools-agent-id-conventions.md +8 -6
  30. package/assets/skills/meko-mcp-tools-desktop/references/tools-cookbook.md +284 -38
  31. package/assets/skills/meko-mcp-tools-desktop/references/tools-known-limitations.md +32 -10
  32. package/assets/skills/meko-mcp-tools-desktop/references/tools-memory-vs-conversation.md +16 -12
  33. package/assets/skills/meko-mcp-tools-desktop/references/tools-overview.md +46 -31
  34. package/assets/skills/meko-mcp-tools-desktop/references/tools-rag-workflow.md +79 -14
  35. package/assets/skills/meko-mcp-tools-desktop/references/tools-troubleshooting.md +69 -1
  36. package/assets/skills/meko-select-datapack-desktop/SKILL.md +93 -14
  37. package/assets/skills/meko-select-datapack-desktop/references/cookbook.md +44 -12
  38. package/assets/skills/meko-select-datapack-desktop/references/role-display-future.md +14 -10
  39. package/assets/skills/meko-select-datapack-desktop/references/troubleshooting.md +17 -5
  40. package/bin/create-meko-setup.mjs +186 -30
  41. package/lib/autofix.mjs +100 -0
  42. package/lib/browser-login.mjs +495 -0
  43. package/lib/capture-cleanup.mjs +63 -0
  44. package/lib/clients/claude-code.mjs +1 -0
  45. package/lib/clients/codex.mjs +80 -35
  46. package/lib/clients/common.mjs +7 -0
  47. package/lib/clients/index.mjs +2 -0
  48. package/lib/clients/kiro.mjs +5 -5
  49. package/lib/clients/opencode.mjs +537 -0
  50. package/lib/config.mjs +68 -1
  51. package/lib/installer.mjs +18 -2
  52. package/lib/migrate/adapters/claude-code.mjs +11 -2
  53. package/lib/migrate/adapters/codex.mjs +9 -2
  54. package/lib/migrate/adapters/memory-md.mjs +27 -8
  55. package/lib/migrate/adapters/opencode.mjs +148 -0
  56. package/lib/migrate/claude-parser.mjs +153 -9
  57. package/lib/migrate/datapack.mjs +254 -16
  58. package/lib/migrate/id.mjs +195 -13
  59. package/lib/migrate/mcp-client.mjs +59 -1
  60. package/lib/migrate/opencode-normalizer.mjs +132 -0
  61. package/lib/migrate/orchestrator.mjs +294 -78
  62. package/lib/migrate/progress.mjs +239 -5
  63. package/lib/migrate/registry.mjs +72 -0
  64. package/lib/migrate.mjs +97 -9
  65. package/lib/validate.mjs +111 -22
  66. package/package.json +5 -5
  67. 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
+ };
@@ -535,20 +535,42 @@ function journalEvent(event) {
535
535
  }
536
536
 
537
537
  function ensureCaptureIntent(event, journalPath) {
538
- const agentId = deriveAgentId({
538
+ const envOverride = (process.env.MEKO_AGENT_ID || "").trim();
539
+ const derivedAgentId = deriveAgentId({
539
540
  client: "kiro",
540
541
  cwd: event.cwd,
541
- envOverride: process.env.MEKO_AGENT_ID,
542
+ envOverride,
542
543
  });
544
+ const existing = captureState.readState(event.sessionId);
545
+ const existingAgentId = existing.ok && existing.state
546
+ ? String(existing.state.agent_id || "").trim()
547
+ : "";
548
+ const existingSource = existing.ok && existing.state
549
+ ? String(existing.state.agent_id_source || "").trim()
550
+ : "";
551
+
552
+ // Existing Kiro sessions keep their persisted owner. Changing it in place
553
+ // would orphan an already-created conversation and reset the capture intent.
554
+ // A live explicit override still wins, and stale pre-MEKO-385 client ids are
555
+ // allowed to upgrade to Kiro.
556
+ const preserveExisting = !envOverride && existingAgentId && (
557
+ existingSource === "explicit"
558
+ || existingAgentId === "kiro"
559
+ || existingAgentId.startsWith("kiro:")
560
+ );
561
+ const agentId = preserveExisting ? existingAgentId : derivedAgentId;
562
+ const agentIdSource = preserveExisting
563
+ ? (existingSource || "derived")
564
+ : (envOverride ? "explicit" : "derived");
543
565
  const intent = captureState.writeSessionIntent(event.sessionId, {
544
566
  client: "kiro",
545
567
  agent_id: agentId,
546
- agent_id_source: (process.env.MEKO_AGENT_ID || "").trim() ? "explicit" : "derived",
568
+ agent_id_source: agentIdSource,
547
569
  transcript_path: journalPath,
548
570
  datapack_id: process.env.MEKO_DATAPACK_ID || null,
549
571
  datapack_name: process.env.MEKO_DATAPACK_NAME || null,
550
572
  });
551
- return { ...intent, agentId };
573
+ return { ...intent, agentId, derivedAgentId };
552
574
  }
553
575
 
554
576
  function launchCapture(mode, payload) {