@jmtrin/opencode-kevin 1.4.0 → 1.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.
- package/dist/plugin/index.d.ts +1 -1
- package/dist/plugin/index.js +102 -9
- package/package.json +3 -3
package/dist/plugin/index.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface KevinPluginOptions {
|
|
|
17
17
|
* v0.4.0 (K4-021) — settings surfaced by `kevin_config` (plan §8.8).
|
|
18
18
|
* Unknown keys are rejected on `set` unless `strict: false`.
|
|
19
19
|
*/
|
|
20
|
-
export declare const KEVIN_CONFIG_KEYS: readonly ["quality_gate_enabled", "lesson_snippet_injection", "patternminer_enabled", "cross_project_enabled", "llm_reflection_enabled", "tool_calls_dedup_enabled", "deterministic_retrieval", "pre_prompt_budget_tokens", "archive_after_days", "curation_enabled", "agents_md_path", "skill_emission_enabled", "reference_emission_enabled", "injection_confidence_floor", "repo_truth_enabled", "convention_mining_enabled", "conflict_detection_enabled", "error_lesson_mode", "shared_layer_enabled", "okf_path", "share_requires_approval", "author_identity_mode", "shared_confidence_floor", "hook_liveness_enabled", "native_registration_enabled", "host_probe_history_enabled", "dead_hook_report_threshold", "perf_enabled", "perf_ring_capacity", "perf_flush_on_idle", "contract_report_enabled", "tui_snapshots_enabled", "mcp_write_enabled", "mcp_approve_enabled", "mcp_repo_override"];
|
|
20
|
+
export declare const KEVIN_CONFIG_KEYS: readonly ["quality_gate_enabled", "lesson_snippet_injection", "patternminer_enabled", "cross_project_enabled", "llm_reflection_enabled", "tool_calls_dedup_enabled", "deterministic_retrieval", "pre_prompt_budget_tokens", "archive_after_days", "curation_enabled", "agents_md_path", "skill_emission_enabled", "reference_emission_enabled", "injection_confidence_floor", "repo_truth_enabled", "convention_mining_enabled", "conflict_detection_enabled", "error_lesson_mode", "shared_layer_enabled", "okf_path", "share_requires_approval", "author_identity_mode", "shared_confidence_floor", "hook_liveness_enabled", "native_registration_enabled", "host_probe_history_enabled", "dead_hook_report_threshold", "perf_enabled", "perf_ring_capacity", "perf_flush_on_idle", "contract_report_enabled", "tui_snapshots_enabled", "mcp_write_enabled", "mcp_approve_enabled", "mcp_repo_override", "skills_canonical_dir", "skills_mirror_claude", "skills_mirror_cursor", "import_host_memory"];
|
|
21
21
|
export declare const ERROR_LESSON_MODE_VALUES: readonly ["all", "triage_only"];
|
|
22
22
|
/** Plugin release version — single source is @jmtrin/kevin-core (B-003 drift fix). */
|
|
23
23
|
export { KEVIN_VERSION };
|
package/dist/plugin/index.js
CHANGED
|
@@ -128,6 +128,12 @@ export const KEVIN_CONFIG_KEYS = [
|
|
|
128
128
|
"mcp_write_enabled",
|
|
129
129
|
"mcp_approve_enabled",
|
|
130
130
|
"mcp_repo_override",
|
|
131
|
+
// v1.5.0 (K15-001 / plan §4) — the four Diaspora settings (no migration this release)
|
|
132
|
+
// skills_canonical_dir is a path, others are TEXT flags compared with === "1".
|
|
133
|
+
"skills_canonical_dir",
|
|
134
|
+
"skills_mirror_claude",
|
|
135
|
+
"skills_mirror_cursor",
|
|
136
|
+
"import_host_memory",
|
|
131
137
|
];
|
|
132
138
|
// v1.1.0 (K11-007 / D11-08) — no new settings in 1.1.0; thresholds are constants (D11-03)
|
|
133
139
|
// v0.7.0 (K7-003 / plan §5.6, D7-12) — the explicit VALUE domain for
|
|
@@ -175,6 +181,8 @@ function resolveMigrationsDir() {
|
|
|
175
181
|
}
|
|
176
182
|
const REKEY_TABLES = ["memories", "shared_entries", "okf_imports"];
|
|
177
183
|
export function performRekey(store, toRepoId, opts) {
|
|
184
|
+
if (!/^[0-9a-f]{16}$/.test(toRepoId))
|
|
185
|
+
return { action: "rekey", ok: false, reason: "invalid repo_id" };
|
|
178
186
|
// The 009 migration carries the repo_id column AND the shared-layer
|
|
179
187
|
// tables; without it there is nothing to re-key.
|
|
180
188
|
if (!hasRepoIdColumn(store)) {
|
|
@@ -315,6 +323,17 @@ export const KevinPlugin = async (input, options) => {
|
|
|
315
323
|
catch {
|
|
316
324
|
// pre-003 DB without kevin_settings — nothing to seed
|
|
317
325
|
}
|
|
326
|
+
// v1.5.0 (K15-001) — Diaspora settings: ensure defaults on existing DBs.
|
|
327
|
+
try {
|
|
328
|
+
const ins = store.prepare("INSERT OR IGNORE INTO kevin_settings (key, value) VALUES (?, ?)");
|
|
329
|
+
ins.run("skills_canonical_dir", ".agents/skills");
|
|
330
|
+
ins.run("skills_mirror_claude", "0");
|
|
331
|
+
ins.run("skills_mirror_cursor", "0");
|
|
332
|
+
ins.run("import_host_memory", "0");
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
// pre-003 DB without kevin_settings — nothing to seed
|
|
336
|
+
}
|
|
318
337
|
const metrics = new Metrics(store);
|
|
319
338
|
// v0.4.0 (K4-019): the plugin hooks expose no project field, so the
|
|
320
339
|
// project id is derived once from the plugin host's working directory
|
|
@@ -1344,18 +1363,28 @@ export const KevinPlugin = async (input, options) => {
|
|
|
1344
1363
|
},
|
|
1345
1364
|
}),
|
|
1346
1365
|
kevin_export: tool({
|
|
1347
|
-
description: "Exporta memorias curadas (decision, rule, pattern) como markdown o
|
|
1366
|
+
description: "Exporta memorias curadas (decision, rule, pattern) como markdown, OKF o MIF (v1.5.0). v0.3.0.",
|
|
1348
1367
|
args: {
|
|
1349
1368
|
format: tool.schema
|
|
1350
|
-
.enum(["okf", "markdown"])
|
|
1369
|
+
.enum(["okf", "markdown", "mif"])
|
|
1351
1370
|
.default("okf")
|
|
1352
|
-
.describe("okf | markdown"),
|
|
1371
|
+
.describe("okf | markdown | mif"),
|
|
1372
|
+
redact_pii: tool.schema.boolean().optional().default(false).describe("Cuando true, aplica redaccion SECRET_PATTERNS sobre el contenido (MIF honour-when-flagged, defecto false para preservar 1.x)"),
|
|
1353
1373
|
},
|
|
1354
1374
|
async execute(args) {
|
|
1355
|
-
//
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1375
|
+
// v1.5.0 (K15-009) — MIF branch
|
|
1376
|
+
if (args.format === "mif") {
|
|
1377
|
+
const { toMif } = await import("@jmtrin/kevin-core");
|
|
1378
|
+
const rows = store.prepare("SELECT * FROM memories WHERE status='active'").all();
|
|
1379
|
+
// scope filter to projectId like okf-export does? Use same as exportOkf: filter by project_id = ?
|
|
1380
|
+
const scoped = projectId ? rows.filter((r) => r.project_id === projectId || r.project_id === projectId) : rows;
|
|
1381
|
+
const envelope = toMif(scoped, { redactPii: args.redact_pii === true });
|
|
1382
|
+
try {
|
|
1383
|
+
metrics.incr("mif_exports_total", 1);
|
|
1384
|
+
}
|
|
1385
|
+
catch { }
|
|
1386
|
+
return { title: "Export completado", output: JSON.stringify(envelope, null, 2) };
|
|
1387
|
+
}
|
|
1359
1388
|
const output = args.format === "markdown"
|
|
1360
1389
|
? exportMarkdown(store, projectId)
|
|
1361
1390
|
: exportOkf(store, projectId);
|
|
@@ -1366,11 +1395,53 @@ export const KevinPlugin = async (input, options) => {
|
|
|
1366
1395
|
},
|
|
1367
1396
|
}),
|
|
1368
1397
|
kevin_import: tool({
|
|
1369
|
-
description: "Importa un bundle markdown de conocimiento (v0.3.0)
|
|
1398
|
+
description: "Importa un bundle markdown de conocimiento (v0.3.0) o MIF / host memories (v1.5.0). Para host, requiere import_host_memory='1' (gate).",
|
|
1370
1399
|
args: {
|
|
1371
|
-
bundle: tool.schema.string().
|
|
1400
|
+
bundle: tool.schema.string().optional(),
|
|
1401
|
+
format: tool.schema.enum(["okf", "markdown", "mif"]).optional().describe("okf | markdown | mif (mif requiere bundle JSON)"),
|
|
1402
|
+
source: tool.schema.enum(["bundle", "claude-memory", "codex-memories"]).optional().describe("bundle (default) | claude-memory | codex-memories (requiere gate)"),
|
|
1372
1403
|
},
|
|
1373
1404
|
async execute(args) {
|
|
1405
|
+
const src = args.source ?? "bundle";
|
|
1406
|
+
if (src === "claude-memory" || src === "codex-memories") {
|
|
1407
|
+
const { importHostMemories } = await import("@jmtrin/kevin-core");
|
|
1408
|
+
const report = importHostMemories({ store, memoryService, metrics: metrics, env: kevinEnv, source: src });
|
|
1409
|
+
if (report.error === "disabled") {
|
|
1410
|
+
return { title: "Import no realizado (gate deshabilitado)", output: JSON.stringify({ error: "disabled", hint: "Ejecuta kevin_config set import_host_memory 1 para habilitar" }) };
|
|
1411
|
+
}
|
|
1412
|
+
return { title: "Import completado", output: JSON.stringify(report) };
|
|
1413
|
+
}
|
|
1414
|
+
if (args.format === "mif") {
|
|
1415
|
+
if (!args.bundle)
|
|
1416
|
+
return { title: "Import error", output: JSON.stringify({ error: "missing_bundle", hint: "bundle requerido para formato mif" }) };
|
|
1417
|
+
try {
|
|
1418
|
+
const env = JSON.parse(args.bundle);
|
|
1419
|
+
const { fromMif } = await import("@jmtrin/kevin-core");
|
|
1420
|
+
const { candidates } = fromMif(env);
|
|
1421
|
+
let imported = 0;
|
|
1422
|
+
let duplicates = 0;
|
|
1423
|
+
for (const c of candidates) {
|
|
1424
|
+
const fp = c.content ? (await import("@jmtrin/kevin-core")).fingerprint(c.content) : "";
|
|
1425
|
+
const exists = store.prepare("SELECT 1 FROM memories WHERE fingerprint = ? LIMIT 1").get(fp);
|
|
1426
|
+
if (exists) {
|
|
1427
|
+
duplicates++;
|
|
1428
|
+
continue;
|
|
1429
|
+
}
|
|
1430
|
+
memoryService.save({ type: c.type ?? "context", content: c.content, scope: "project", origin: "imported", fingerprint: fp, metadata: c.metadata });
|
|
1431
|
+
imported++;
|
|
1432
|
+
}
|
|
1433
|
+
try {
|
|
1434
|
+
metrics.incr("mif_imports_total", 1);
|
|
1435
|
+
}
|
|
1436
|
+
catch { }
|
|
1437
|
+
return { title: "Import completado", output: JSON.stringify({ imported, duplicates, unknownFieldsPreserved: candidates[0]?.unknownFields ? Object.keys(candidates[0].unknownFields) : [] }) };
|
|
1438
|
+
}
|
|
1439
|
+
catch (e) {
|
|
1440
|
+
return { title: "Import error", output: JSON.stringify({ error: "bad_json", message: e.message }) };
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
if (!args.bundle)
|
|
1444
|
+
return { title: "Import error", output: JSON.stringify({ error: "missing_bundle" }) };
|
|
1374
1445
|
const result = importOkf(args.bundle, memoryService);
|
|
1375
1446
|
return {
|
|
1376
1447
|
title: "Import completado",
|
|
@@ -2148,6 +2219,28 @@ export const KevinPlugin = async (input, options) => {
|
|
|
2148
2219
|
catch {
|
|
2149
2220
|
// best-effort — snapshot failure must not break idle
|
|
2150
2221
|
}
|
|
2222
|
+
// v1.5.0 (K15-007) — skill bundle refresh after snapshots flush
|
|
2223
|
+
try {
|
|
2224
|
+
const manifestPath = join(materializerRoot, "skills-manifest.json");
|
|
2225
|
+
const shouldRefresh = memoryService.getSetting("skills_mirror_claude", "0") === "1" || memoryService.getSetting("skills_mirror_cursor", "0") === "1" || memoryService.getSetting("skills_canonical_dir", ".agents/skills") !== "" || existsSync(manifestPath);
|
|
2226
|
+
// spec: run when ANY skill setting is on OR manifest exists; canonical_dir always set so we check manifest existence as primary gate plus mirror flags
|
|
2227
|
+
const hasManifest = existsSync(manifestPath);
|
|
2228
|
+
const mirrorActive = memoryService.getSetting("skills_mirror_claude", "0") === "1" || memoryService.getSetting("skills_mirror_cursor", "0") === "1";
|
|
2229
|
+
if (hasManifest || mirrorActive) {
|
|
2230
|
+
const { refreshSkillBundle } = await import("@jmtrin/kevin-core");
|
|
2231
|
+
const bundles = materializer.topicBundles();
|
|
2232
|
+
const canonicalDir = memoryService.getSetting("skills_canonical_dir", ".agents/skills");
|
|
2233
|
+
const mirrors = [];
|
|
2234
|
+
if (memoryService.getSetting("skills_mirror_claude", "0") === "1")
|
|
2235
|
+
mirrors.push("claude");
|
|
2236
|
+
if (memoryService.getSetting("skills_mirror_cursor", "0") === "1")
|
|
2237
|
+
mirrors.push("cursor");
|
|
2238
|
+
refreshSkillBundle({ projectRoot, canonicalDir, mirrors, topics: bundles, repoId: sessionIdentity.repoId, manifestPath, metrics: metrics });
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
catch {
|
|
2242
|
+
// best-effort
|
|
2243
|
+
}
|
|
2151
2244
|
// v1.0.0 (K10-013 / D10-08) — the session recorded work:
|
|
2152
2245
|
// arm the deferred dispose settlement. The ISO timestamp
|
|
2153
2246
|
// lets the next session.start compare it against
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jmtrin/opencode-kevin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Kevin — Observa y aprende: capa de aprendizaje para OpenCode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/plugin/index.js",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@opencode-ai/plugin": "^1.18.16",
|
|
28
|
-
"@jmtrin/kevin-core": "1.
|
|
29
|
-
"@jmtrin/opencode-kevin-tui": "1.
|
|
28
|
+
"@jmtrin/kevin-core": "1.5.0",
|
|
29
|
+
"@jmtrin/opencode-kevin-tui": "1.5.0"
|
|
30
30
|
},
|
|
31
31
|
"optionalDependencies": {
|
|
32
32
|
"better-sqlite3": "^12.11.0"
|