@yugabytedb/meko-mcp 2.0.0 → 2.2.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.
- package/README.md +183 -33
- package/assets/hooks-handlers/codex-session-end.sh +13 -0
- package/assets/hooks-handlers/cursor-session-start.sh +19 -1
- package/assets/hooks-handlers/lib/capture-notices.js +321 -0
- package/assets/hooks-handlers/lib/capture-state.js +2079 -0
- package/assets/hooks-handlers/lib/capture-status-cli.js +82 -0
- package/assets/hooks-handlers/lib/capture.js +1653 -225
- package/assets/hooks-handlers/lib/checkpoint-timer.js +455 -50
- package/assets/hooks-handlers/lib/kiro-handler.js +738 -0
- package/assets/hooks-handlers/lib/terminal-handoff.js +418 -0
- package/assets/hooks-handlers/session-end.sh +3 -24
- package/assets/hooks-handlers/session-start.sh +8 -1
- package/assets/hooks-handlers/test_checkpoint_timer.sh +162 -0
- package/assets/scripts/meko-statusline.sh +41 -1
- package/assets/scripts/package-desktop-skill.sh +32 -1
- package/assets/skills/meko-mcp-tools/SKILL.md +90 -137
- package/assets/skills/meko-mcp-tools/references/_sections.md +3 -5
- package/assets/skills/meko-mcp-tools/references/tools-agent-id-conventions.md +31 -28
- package/assets/skills/meko-mcp-tools/references/tools-conversation-dedup.md +5 -7
- package/assets/skills/meko-mcp-tools/references/tools-conversation-reasoning.md +50 -0
- package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +69 -49
- package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +6 -6
- package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +29 -5
- package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +29 -92
- package/assets/skills/meko-mcp-tools/references/tools-overview.md +42 -40
- package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +1 -2
- package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +18 -11
- package/assets/skills/meko-mcp-tools/references/tools-user-id-conventions.md +10 -9
- package/assets/skills/meko-mcp-tools-desktop/SKILL.md +51 -44
- package/assets/skills/meko-mcp-tools-desktop/desktop/README.md +6 -6
- package/assets/skills/meko-mcp-tools-desktop/references/_sections.md +2 -2
- package/assets/skills/meko-mcp-tools-desktop/references/tools-agent-id-conventions.md +34 -25
- package/assets/skills/meko-mcp-tools-desktop/references/tools-conversation-reasoning.md +50 -0
- package/assets/skills/meko-mcp-tools-desktop/references/tools-cookbook.md +63 -37
- package/assets/skills/meko-mcp-tools-desktop/references/tools-known-limitations.md +4 -4
- package/assets/skills/meko-mcp-tools-desktop/references/tools-memory-vs-conversation.md +19 -22
- package/assets/skills/meko-mcp-tools-desktop/references/tools-overview.md +42 -40
- package/assets/skills/meko-mcp-tools-desktop/references/tools-rag-workflow.md +1 -2
- package/assets/skills/meko-mcp-tools-desktop/references/tools-troubleshooting.md +3 -17
- package/assets/skills/meko-select-datapack-desktop/SKILL.md +31 -21
- package/assets/skills/meko-select-datapack-desktop/references/cookbook.md +8 -8
- package/assets/skills/meko-select-datapack-desktop/references/role-display-future.md +41 -0
- package/assets/skills/meko-select-datapack-desktop/references/troubleshooting.md +1 -1
- package/bin/create-meko-setup.mjs +93 -16
- package/lib/capture-status.mjs +67 -0
- package/lib/clients/aliases.mjs +5 -0
- package/lib/clients/claude-code.mjs +8 -4
- package/lib/clients/claude-desktop.mjs +133 -4
- package/lib/clients/codex.mjs +29 -11
- package/lib/clients/common.mjs +2 -0
- package/lib/clients/cursor.mjs +7 -2
- package/lib/clients/index.mjs +2 -0
- package/lib/clients/kiro.mjs +705 -0
- package/lib/config.mjs +90 -0
- package/lib/detect.mjs +7 -2
- package/lib/installer.mjs +1 -0
- package/lib/migrate/adapters/codex.mjs +490 -0
- package/lib/migrate/id.mjs +28 -3
- package/lib/migrate/orchestrator.mjs +34 -7
- package/lib/migrate.mjs +4 -2
- package/lib/prompt.mjs +17 -2
- package/lib/url-selection.mjs +55 -0
- package/package.json +4 -4
- package/assets/skills/meko-mcp-tools/references/tools-scope-permissions.md +0 -71
- package/assets/skills/meko-mcp-tools-desktop/references/tools-scope-permissions.md +0 -71
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* capture-notices.js — SessionStart / statusline health notice builders
|
|
3
|
+
* for Meko Capture V2 (Workstream D).
|
|
4
|
+
*
|
|
5
|
+
* CommonJS, zero npm deps. Safe to require from capture.js SessionStart
|
|
6
|
+
* without touching the drain loop. See docs/plans/meko-capture-v2-contracts.md.
|
|
7
|
+
*
|
|
8
|
+
* Health cache field extension (documented):
|
|
9
|
+
* capture-health.json may include `last_notified_status` — the aggregate
|
|
10
|
+
* status last shown to the user via SessionStart. Used for one-shot
|
|
11
|
+
* recovery notices when status returns to `healthy`. writeHealthCache()
|
|
12
|
+
* preserves this field when callers omit it.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
"use strict";
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
computeAggregateHealth,
|
|
19
|
+
readHealthCache,
|
|
20
|
+
scanStates,
|
|
21
|
+
writeHealthCache,
|
|
22
|
+
} = require("./capture-state");
|
|
23
|
+
|
|
24
|
+
const NON_HEALTHY = new Set([
|
|
25
|
+
"catching_up",
|
|
26
|
+
"degraded_retrying",
|
|
27
|
+
"blocked_action_required",
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Count sessions whose per-session bucket is not healthy.
|
|
32
|
+
* @param {object} health
|
|
33
|
+
* @returns {number}
|
|
34
|
+
*/
|
|
35
|
+
function affectedSessionCount(health) {
|
|
36
|
+
const counts = (health && health.counts) || {};
|
|
37
|
+
return (
|
|
38
|
+
Number(counts.catching_up || 0) +
|
|
39
|
+
Number(counts.degraded_retrying || 0) +
|
|
40
|
+
Number(counts.blocked_action_required || 0)
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Human reason string for notices / CLI.
|
|
46
|
+
* @param {object} health
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
function reasonForHealth(health) {
|
|
50
|
+
if (!health || typeof health !== "object") return "unknown";
|
|
51
|
+
const status = health.status || "healthy";
|
|
52
|
+
if (status === "healthy") return "all sessions healthy";
|
|
53
|
+
if (status === "blocked_action_required") {
|
|
54
|
+
const reasons = Array.isArray(health.blocked_reasons)
|
|
55
|
+
? health.blocked_reasons.filter(Boolean)
|
|
56
|
+
: [];
|
|
57
|
+
if (reasons.length) return reasons.join("; ");
|
|
58
|
+
return "blocked — action required (ownership, datapack, or corrupt state)";
|
|
59
|
+
}
|
|
60
|
+
if (status === "degraded_retrying") {
|
|
61
|
+
const sessions = Array.isArray(health.sessions) ? health.sessions : [];
|
|
62
|
+
const classes = [];
|
|
63
|
+
for (const s of sessions) {
|
|
64
|
+
if (s && s.status === "degraded_retrying" && s.failure_class) {
|
|
65
|
+
if (!classes.includes(s.failure_class)) classes.push(s.failure_class);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (classes.length) {
|
|
69
|
+
return `retrying after ${classes.join(", ")} failure(s)`;
|
|
70
|
+
}
|
|
71
|
+
return "delivery retrying with backoff";
|
|
72
|
+
}
|
|
73
|
+
// catching_up
|
|
74
|
+
const queued = Number(health.queued_exchanges || 0);
|
|
75
|
+
if (queued > 0) {
|
|
76
|
+
return `pending delivery backlog (${queued} exchange(s) queued)`;
|
|
77
|
+
}
|
|
78
|
+
return "sessions catching up on pending delivery";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Short composable Claude Code statusline suffix.
|
|
83
|
+
* Healthy → empty string (no capture segment).
|
|
84
|
+
*
|
|
85
|
+
* @param {object|null} health
|
|
86
|
+
* @returns {string}
|
|
87
|
+
*/
|
|
88
|
+
function statuslineSuffixFromHealth(health) {
|
|
89
|
+
if (!health || typeof health !== "object") return "";
|
|
90
|
+
const status = health.status || "healthy";
|
|
91
|
+
if (!NON_HEALTHY.has(status)) return "";
|
|
92
|
+
const queued = Number(health.queued_exchanges || 0);
|
|
93
|
+
const short =
|
|
94
|
+
status === "blocked_action_required"
|
|
95
|
+
? "blocked"
|
|
96
|
+
: status === "degraded_retrying"
|
|
97
|
+
? "degraded"
|
|
98
|
+
: "catching_up";
|
|
99
|
+
return `capture:${short}(${queued})`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Build a SessionStart-injectable health notice.
|
|
104
|
+
*
|
|
105
|
+
* - When status ≠ healthy: always emit (exact affected-session + queued counts + reason).
|
|
106
|
+
* - When status === healthy and previousStatus was non-healthy: one-shot recovery notice.
|
|
107
|
+
* - When status === healthy and previous was already healthy / null: no notice.
|
|
108
|
+
*
|
|
109
|
+
* @param {{ health: object, previousStatus?: string|null }} opts
|
|
110
|
+
* @returns {{
|
|
111
|
+
* notice: string|null,
|
|
112
|
+
* nextNotifiedStatus: string,
|
|
113
|
+
* shouldPersistNotified: boolean,
|
|
114
|
+
* status: string,
|
|
115
|
+
* affectedSessions: number,
|
|
116
|
+
* queuedExchanges: number,
|
|
117
|
+
* reason: string,
|
|
118
|
+
* }}
|
|
119
|
+
*/
|
|
120
|
+
function buildHealthNotice({ health, previousStatus } = {}) {
|
|
121
|
+
const h = health && typeof health === "object" ? health : { status: "healthy" };
|
|
122
|
+
const status = h.status || "healthy";
|
|
123
|
+
const prev =
|
|
124
|
+
previousStatus === undefined || previousStatus === null || previousStatus === ""
|
|
125
|
+
? null
|
|
126
|
+
: String(previousStatus);
|
|
127
|
+
const affected = affectedSessionCount(h);
|
|
128
|
+
const queued = Number(h.queued_exchanges || 0);
|
|
129
|
+
const reason = reasonForHealth(h);
|
|
130
|
+
|
|
131
|
+
if (status === "healthy") {
|
|
132
|
+
if (prev && NON_HEALTHY.has(prev)) {
|
|
133
|
+
return {
|
|
134
|
+
notice:
|
|
135
|
+
"[Meko capture] Capture recovered — all sessions healthy. " +
|
|
136
|
+
"Queued backlog is clear. Automatic conversation capture is running normally again.",
|
|
137
|
+
nextNotifiedStatus: "healthy",
|
|
138
|
+
shouldPersistNotified: true,
|
|
139
|
+
status,
|
|
140
|
+
affectedSessions: 0,
|
|
141
|
+
queuedExchanges: queued,
|
|
142
|
+
reason,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
notice: null,
|
|
147
|
+
nextNotifiedStatus: "healthy",
|
|
148
|
+
shouldPersistNotified: prev !== "healthy",
|
|
149
|
+
status,
|
|
150
|
+
affectedSessions: 0,
|
|
151
|
+
queuedExchanges: queued,
|
|
152
|
+
reason,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const label =
|
|
157
|
+
status === "blocked_action_required"
|
|
158
|
+
? "blocked_action_required"
|
|
159
|
+
: status === "degraded_retrying"
|
|
160
|
+
? "degraded_retrying"
|
|
161
|
+
: "catching_up";
|
|
162
|
+
|
|
163
|
+
const notice =
|
|
164
|
+
`[Meko capture] Status: ${label} — ${affected} session(s) affected, ` +
|
|
165
|
+
`${queued} exchange(s) queued. Reason: ${reason}. ` +
|
|
166
|
+
`Run \`meko-mcp --capture-status\` for details.`;
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
notice,
|
|
170
|
+
nextNotifiedStatus: status,
|
|
171
|
+
shouldPersistNotified: true,
|
|
172
|
+
status,
|
|
173
|
+
affectedSessions: affected,
|
|
174
|
+
queuedExchanges: queued,
|
|
175
|
+
reason,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Refresh aggregate health from disk, build a notice, and optionally persist
|
|
181
|
+
* `last_notified_status` on the health cache.
|
|
182
|
+
*
|
|
183
|
+
* Integration (SessionStart): after building additionalContext,
|
|
184
|
+
* const { injectHealthNotice } = require("./capture-notices");
|
|
185
|
+
* context = injectHealthNotice(context);
|
|
186
|
+
*
|
|
187
|
+
* @param {string} [context]
|
|
188
|
+
* @param {{ persist?: boolean, refresh?: boolean }} [opts]
|
|
189
|
+
* @returns {string}
|
|
190
|
+
*/
|
|
191
|
+
function injectHealthNotice(context, opts) {
|
|
192
|
+
const options = opts && typeof opts === "object" ? opts : {};
|
|
193
|
+
const persist = options.persist !== false;
|
|
194
|
+
const refresh = options.refresh !== false;
|
|
195
|
+
const base = context == null ? "" : String(context);
|
|
196
|
+
|
|
197
|
+
let health = null;
|
|
198
|
+
if (refresh) {
|
|
199
|
+
const scan = scanStates({ includeBlocked: true });
|
|
200
|
+
health = computeAggregateHealth(scan.entries || []);
|
|
201
|
+
const existing = readHealthCache();
|
|
202
|
+
if (existing && existing.last_notified_status !== undefined) {
|
|
203
|
+
health.last_notified_status = existing.last_notified_status;
|
|
204
|
+
}
|
|
205
|
+
writeHealthCache(health);
|
|
206
|
+
} else {
|
|
207
|
+
health = readHealthCache() || {
|
|
208
|
+
schema_version: 1,
|
|
209
|
+
status: "healthy",
|
|
210
|
+
counts: {
|
|
211
|
+
healthy: 0,
|
|
212
|
+
catching_up: 0,
|
|
213
|
+
degraded_retrying: 0,
|
|
214
|
+
blocked_action_required: 0,
|
|
215
|
+
},
|
|
216
|
+
queued_exchanges: 0,
|
|
217
|
+
oldest_pending_age_seconds: null,
|
|
218
|
+
last_success_at: null,
|
|
219
|
+
blocked_reasons: [],
|
|
220
|
+
sessions: [],
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const previousStatus =
|
|
225
|
+
health.last_notified_status !== undefined
|
|
226
|
+
? health.last_notified_status
|
|
227
|
+
: null;
|
|
228
|
+
const built = buildHealthNotice({ health, previousStatus });
|
|
229
|
+
|
|
230
|
+
if (persist && built.shouldPersistNotified) {
|
|
231
|
+
writeHealthCache({
|
|
232
|
+
...health,
|
|
233
|
+
last_notified_status: built.nextNotifiedStatus,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!built.notice) return base;
|
|
238
|
+
if (!base) return built.notice;
|
|
239
|
+
return `${base}\n\n${built.notice}`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Format human-readable --capture-status report.
|
|
244
|
+
* @param {object} health
|
|
245
|
+
* @returns {string}
|
|
246
|
+
*/
|
|
247
|
+
function formatCaptureStatusText(health) {
|
|
248
|
+
const h = health && typeof health === "object" ? health : {};
|
|
249
|
+
const status = h.status || "healthy";
|
|
250
|
+
const counts = h.counts || {};
|
|
251
|
+
const lines = [
|
|
252
|
+
`Meko capture status: ${status}`,
|
|
253
|
+
` sessions: healthy=${Number(counts.healthy || 0)} ` +
|
|
254
|
+
`catching_up=${Number(counts.catching_up || 0)} ` +
|
|
255
|
+
`degraded_retrying=${Number(counts.degraded_retrying || 0)} ` +
|
|
256
|
+
`blocked_action_required=${Number(counts.blocked_action_required || 0)}`,
|
|
257
|
+
` queued exchanges: ${Number(h.queued_exchanges || 0)}`,
|
|
258
|
+
` oldest pending age (seconds): ${
|
|
259
|
+
h.oldest_pending_age_seconds == null ? "n/a" : h.oldest_pending_age_seconds
|
|
260
|
+
}`,
|
|
261
|
+
` last success: ${h.last_success_at || "n/a"}`,
|
|
262
|
+
` reason: ${reasonForHealth(h)}`,
|
|
263
|
+
];
|
|
264
|
+
const blocked = Array.isArray(h.blocked_reasons) ? h.blocked_reasons : [];
|
|
265
|
+
if (blocked.length) {
|
|
266
|
+
lines.push(` blocked reasons: ${blocked.join("; ")}`);
|
|
267
|
+
}
|
|
268
|
+
return lines.join("\n");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Exit code for aggregate health (contracts).
|
|
273
|
+
* @param {string} status
|
|
274
|
+
* @returns {number}
|
|
275
|
+
*/
|
|
276
|
+
function exitCodeForStatus(status) {
|
|
277
|
+
if (status === "blocked_action_required") return 3;
|
|
278
|
+
if (status === "degraded_retrying") return 2;
|
|
279
|
+
// healthy | catching_up
|
|
280
|
+
return 0;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Stable JSON shape for --capture-status --json (aggregate health fields).
|
|
285
|
+
* @param {object} health
|
|
286
|
+
* @returns {object}
|
|
287
|
+
*/
|
|
288
|
+
function captureStatusJson(health) {
|
|
289
|
+
const h = health && typeof health === "object" ? health : {};
|
|
290
|
+
return {
|
|
291
|
+
schema_version: 1,
|
|
292
|
+
status: h.status || "healthy",
|
|
293
|
+
updated_at: h.updated_at || null,
|
|
294
|
+
counts: {
|
|
295
|
+
healthy: Number((h.counts && h.counts.healthy) || 0),
|
|
296
|
+
catching_up: Number((h.counts && h.counts.catching_up) || 0),
|
|
297
|
+
degraded_retrying: Number((h.counts && h.counts.degraded_retrying) || 0),
|
|
298
|
+
blocked_action_required: Number(
|
|
299
|
+
(h.counts && h.counts.blocked_action_required) || 0,
|
|
300
|
+
),
|
|
301
|
+
},
|
|
302
|
+
queued_exchanges: Number(h.queued_exchanges || 0),
|
|
303
|
+
oldest_pending_age_seconds:
|
|
304
|
+
h.oldest_pending_age_seconds == null ? null : h.oldest_pending_age_seconds,
|
|
305
|
+
last_success_at: h.last_success_at || null,
|
|
306
|
+
blocked_reasons: Array.isArray(h.blocked_reasons) ? h.blocked_reasons : [],
|
|
307
|
+
sessions: Array.isArray(h.sessions) ? h.sessions : [],
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
module.exports = {
|
|
312
|
+
affectedSessionCount,
|
|
313
|
+
reasonForHealth,
|
|
314
|
+
statuslineSuffixFromHealth,
|
|
315
|
+
buildHealthNotice,
|
|
316
|
+
injectHealthNotice,
|
|
317
|
+
formatCaptureStatusText,
|
|
318
|
+
exitCodeForStatus,
|
|
319
|
+
captureStatusJson,
|
|
320
|
+
NON_HEALTHY,
|
|
321
|
+
};
|