@jmtrin/opencode-kevin 1.4.0 → 2.0.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 +711 -667
- package/dist/plugin/index.d.ts +7 -1
- package/dist/plugin/index.js +177 -9
- package/package.json +48 -56
package/dist/plugin/index.d.ts
CHANGED
|
@@ -17,7 +17,13 @@ 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", "sources_enabled", "source_claude_memory", "source_codex_memories", "source_opencode_native", "okf_write_version"];
|
|
21
|
+
export declare const REMOVED_SETTINGS: {
|
|
22
|
+
readonly import_host_memory: {
|
|
23
|
+
readonly since: "2.0.0";
|
|
24
|
+
readonly replacement: "sources_enabled + source_claude_memory/source_codex_memories";
|
|
25
|
+
};
|
|
26
|
+
};
|
|
21
27
|
export declare const ERROR_LESSON_MODE_VALUES: readonly ["all", "triage_only"];
|
|
22
28
|
/** Plugin release version — single source is @jmtrin/kevin-core (B-003 drift fix). */
|
|
23
29
|
export { KEVIN_VERSION };
|
package/dist/plugin/index.js
CHANGED
|
@@ -128,7 +128,25 @@ 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
|
+
// v2.0.0 (K16-013 / plan §4.4) — Commonwealth settings (retirement of import_host_memory handled via removals)
|
|
137
|
+
"sources_enabled",
|
|
138
|
+
"source_claude_memory",
|
|
139
|
+
"source_codex_memories",
|
|
140
|
+
"source_opencode_native",
|
|
141
|
+
"okf_write_version",
|
|
131
142
|
];
|
|
143
|
+
// v2.0.0 (K16-004 / plan §5.1) — removed settings contract
|
|
144
|
+
export const REMOVED_SETTINGS = {
|
|
145
|
+
import_host_memory: {
|
|
146
|
+
since: "2.0.0",
|
|
147
|
+
replacement: "sources_enabled + source_claude_memory/source_codex_memories",
|
|
148
|
+
},
|
|
149
|
+
};
|
|
132
150
|
// v1.1.0 (K11-007 / D11-08) — no new settings in 1.1.0; thresholds are constants (D11-03)
|
|
133
151
|
// v0.7.0 (K7-003 / plan §5.6, D7-12) — the explicit VALUE domain for
|
|
134
152
|
// `error_lesson_mode`. The setting is TEXT and must be compared with
|
|
@@ -175,6 +193,8 @@ function resolveMigrationsDir() {
|
|
|
175
193
|
}
|
|
176
194
|
const REKEY_TABLES = ["memories", "shared_entries", "okf_imports"];
|
|
177
195
|
export function performRekey(store, toRepoId, opts) {
|
|
196
|
+
if (!/^[0-9a-f]{16}$/.test(toRepoId))
|
|
197
|
+
return { action: "rekey", ok: false, reason: "invalid repo_id" };
|
|
178
198
|
// The 009 migration carries the repo_id column AND the shared-layer
|
|
179
199
|
// tables; without it there is nothing to re-key.
|
|
180
200
|
if (!hasRepoIdColumn(store)) {
|
|
@@ -315,6 +335,17 @@ export const KevinPlugin = async (input, options) => {
|
|
|
315
335
|
catch {
|
|
316
336
|
// pre-003 DB without kevin_settings — nothing to seed
|
|
317
337
|
}
|
|
338
|
+
// v1.5.0 (K15-001) — Diaspora settings: ensure defaults on existing DBs.
|
|
339
|
+
// v2.0.0: import_host_memory retired — seeded via 014 translation then deleted; no longer seeded here.
|
|
340
|
+
try {
|
|
341
|
+
const ins = store.prepare("INSERT OR IGNORE INTO kevin_settings (key, value) VALUES (?, ?)");
|
|
342
|
+
ins.run("skills_canonical_dir", ".agents/skills");
|
|
343
|
+
ins.run("skills_mirror_claude", "0");
|
|
344
|
+
ins.run("skills_mirror_cursor", "0");
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
// pre-003 DB without kevin_settings — nothing to seed
|
|
348
|
+
}
|
|
318
349
|
const metrics = new Metrics(store);
|
|
319
350
|
// v0.4.0 (K4-019): the plugin hooks expose no project field, so the
|
|
320
351
|
// project id is derived once from the plugin host's working directory
|
|
@@ -1344,18 +1375,28 @@ export const KevinPlugin = async (input, options) => {
|
|
|
1344
1375
|
},
|
|
1345
1376
|
}),
|
|
1346
1377
|
kevin_export: tool({
|
|
1347
|
-
description: "Exporta memorias curadas (decision, rule, pattern) como markdown o
|
|
1378
|
+
description: "Exporta memorias curadas (decision, rule, pattern) como markdown, OKF o MIF (v1.5.0). v0.3.0.",
|
|
1348
1379
|
args: {
|
|
1349
1380
|
format: tool.schema
|
|
1350
|
-
.enum(["okf", "markdown"])
|
|
1381
|
+
.enum(["okf", "markdown", "mif"])
|
|
1351
1382
|
.default("okf")
|
|
1352
|
-
.describe("okf | markdown"),
|
|
1383
|
+
.describe("okf | markdown | mif"),
|
|
1384
|
+
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
1385
|
},
|
|
1354
1386
|
async execute(args) {
|
|
1355
|
-
//
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1387
|
+
// v1.5.0 (K15-009) — MIF branch
|
|
1388
|
+
if (args.format === "mif") {
|
|
1389
|
+
const { toMif } = await import("@jmtrin/kevin-core");
|
|
1390
|
+
const rows = store.prepare("SELECT * FROM memories WHERE status='active'").all();
|
|
1391
|
+
// scope filter to projectId like okf-export does? Use same as exportOkf: filter by project_id = ?
|
|
1392
|
+
const scoped = projectId ? rows.filter((r) => r.project_id === projectId || r.project_id === projectId) : rows;
|
|
1393
|
+
const envelope = toMif(scoped, { redactPii: args.redact_pii === true });
|
|
1394
|
+
try {
|
|
1395
|
+
metrics.incr("mif_exports_total", 1);
|
|
1396
|
+
}
|
|
1397
|
+
catch { }
|
|
1398
|
+
return { title: "Export completado", output: JSON.stringify(envelope, null, 2) };
|
|
1399
|
+
}
|
|
1359
1400
|
const output = args.format === "markdown"
|
|
1360
1401
|
? exportMarkdown(store, projectId)
|
|
1361
1402
|
: exportOkf(store, projectId);
|
|
@@ -1366,11 +1407,53 @@ export const KevinPlugin = async (input, options) => {
|
|
|
1366
1407
|
},
|
|
1367
1408
|
}),
|
|
1368
1409
|
kevin_import: tool({
|
|
1369
|
-
description: "Importa un bundle markdown de conocimiento (v0.3.0)
|
|
1410
|
+
description: "Importa un bundle markdown de conocimiento (v0.3.0) o MIF / host memories (v2.0.0 retira import_host_memory; requiere sources_enabled=1 + source_claude_memory/codex-memories=1).",
|
|
1370
1411
|
args: {
|
|
1371
|
-
bundle: tool.schema.string().
|
|
1412
|
+
bundle: tool.schema.string().optional(),
|
|
1413
|
+
format: tool.schema.enum(["okf", "markdown", "mif"]).optional().describe("okf | markdown | mif (mif requiere bundle JSON)"),
|
|
1414
|
+
source: tool.schema.enum(["bundle", "claude-memory", "codex-memories"]).optional().describe("bundle (default) | claude-memory | codex-memories (requiere gate)"),
|
|
1372
1415
|
},
|
|
1373
1416
|
async execute(args) {
|
|
1417
|
+
const src = args.source ?? "bundle";
|
|
1418
|
+
if (src === "claude-memory" || src === "codex-memories") {
|
|
1419
|
+
const { importHostMemories } = await import("@jmtrin/kevin-core");
|
|
1420
|
+
const report = importHostMemories({ store, memoryService, metrics: metrics, env: kevinEnv, source: src });
|
|
1421
|
+
if (report.error === "disabled") {
|
|
1422
|
+
return { title: "Import no realizado (gate deshabilitado)", output: JSON.stringify({ error: "disabled", hint: report.hint }) };
|
|
1423
|
+
}
|
|
1424
|
+
return { title: "Import completado", output: JSON.stringify(report) };
|
|
1425
|
+
}
|
|
1426
|
+
if (args.format === "mif") {
|
|
1427
|
+
if (!args.bundle)
|
|
1428
|
+
return { title: "Import error", output: JSON.stringify({ error: "missing_bundle", hint: "bundle requerido para formato mif" }) };
|
|
1429
|
+
try {
|
|
1430
|
+
const env = JSON.parse(args.bundle);
|
|
1431
|
+
const { fromMif } = await import("@jmtrin/kevin-core");
|
|
1432
|
+
const { candidates } = fromMif(env);
|
|
1433
|
+
let imported = 0;
|
|
1434
|
+
let duplicates = 0;
|
|
1435
|
+
for (const c of candidates) {
|
|
1436
|
+
const fp = c.content ? (await import("@jmtrin/kevin-core")).fingerprint(c.content) : "";
|
|
1437
|
+
const exists = store.prepare("SELECT 1 FROM memories WHERE fingerprint = ? LIMIT 1").get(fp);
|
|
1438
|
+
if (exists) {
|
|
1439
|
+
duplicates++;
|
|
1440
|
+
continue;
|
|
1441
|
+
}
|
|
1442
|
+
memoryService.save({ type: c.type ?? "context", content: c.content, scope: "project", origin: "imported", fingerprint: fp, metadata: c.metadata });
|
|
1443
|
+
imported++;
|
|
1444
|
+
}
|
|
1445
|
+
try {
|
|
1446
|
+
metrics.incr("mif_imports_total", 1);
|
|
1447
|
+
}
|
|
1448
|
+
catch { }
|
|
1449
|
+
return { title: "Import completado", output: JSON.stringify({ imported, duplicates, unknownFieldsPreserved: candidates[0]?.unknownFields ? Object.keys(candidates[0].unknownFields) : [] }) };
|
|
1450
|
+
}
|
|
1451
|
+
catch (e) {
|
|
1452
|
+
return { title: "Import error", output: JSON.stringify({ error: "bad_json", message: e.message }) };
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
if (!args.bundle)
|
|
1456
|
+
return { title: "Import error", output: JSON.stringify({ error: "missing_bundle" }) };
|
|
1374
1457
|
const result = importOkf(args.bundle, memoryService);
|
|
1375
1458
|
return {
|
|
1376
1459
|
title: "Import completado",
|
|
@@ -1378,6 +1461,56 @@ export const KevinPlugin = async (input, options) => {
|
|
|
1378
1461
|
};
|
|
1379
1462
|
},
|
|
1380
1463
|
}),
|
|
1464
|
+
kevin_sources: tool({
|
|
1465
|
+
description: "Fuentes de memoria (v2.0.0): lista estado de cada MemorySource (enabled, precedence, last_sync). Solo lectura.",
|
|
1466
|
+
args: {
|
|
1467
|
+
action: tool.schema.enum(["list"]).default("list").describe("list"),
|
|
1468
|
+
},
|
|
1469
|
+
async execute(_args) {
|
|
1470
|
+
const settings = {};
|
|
1471
|
+
try {
|
|
1472
|
+
const rows = store
|
|
1473
|
+
.prepare("SELECT key, value FROM kevin_settings WHERE key LIKE 'source%' OR key='sources_enabled' OR key='okf_write_version'")
|
|
1474
|
+
.all();
|
|
1475
|
+
for (const r of rows)
|
|
1476
|
+
settings[r.key] = r.value;
|
|
1477
|
+
}
|
|
1478
|
+
catch { }
|
|
1479
|
+
let sources = [];
|
|
1480
|
+
try {
|
|
1481
|
+
sources = store
|
|
1482
|
+
.prepare("SELECT name, enabled, precedence FROM memory_sources ORDER BY precedence")
|
|
1483
|
+
.all();
|
|
1484
|
+
}
|
|
1485
|
+
catch {
|
|
1486
|
+
sources = [
|
|
1487
|
+
{ name: "opencode-plugin", enabled: 1, precedence: 10 },
|
|
1488
|
+
{
|
|
1489
|
+
name: "claude-memory",
|
|
1490
|
+
enabled: Number(settings["source_claude_memory"] === "1" ? 1 : 0),
|
|
1491
|
+
precedence: 20,
|
|
1492
|
+
},
|
|
1493
|
+
{
|
|
1494
|
+
name: "codex-memories",
|
|
1495
|
+
enabled: Number(settings["source_codex_memories"] === "1" ? 1 : 0),
|
|
1496
|
+
precedence: 30,
|
|
1497
|
+
},
|
|
1498
|
+
{
|
|
1499
|
+
name: "opencode-native",
|
|
1500
|
+
enabled: Number(settings["source_opencode_native"] === "1" ? 1 : 0),
|
|
1501
|
+
precedence: 40,
|
|
1502
|
+
},
|
|
1503
|
+
];
|
|
1504
|
+
}
|
|
1505
|
+
return {
|
|
1506
|
+
title: "Fuentes de memoria",
|
|
1507
|
+
output: JSON.stringify({
|
|
1508
|
+
sources_enabled: settings["sources_enabled"] ?? "1",
|
|
1509
|
+
sources,
|
|
1510
|
+
}),
|
|
1511
|
+
};
|
|
1512
|
+
},
|
|
1513
|
+
}),
|
|
1381
1514
|
kevin_config: tool({
|
|
1382
1515
|
description: "Lee o modifica settings de Kevin sin SQL (v0.4.0). list: todas las settings de kevin_settings. set: upsert de una key conocida (rechaza keys desconocidas salvo strict:false).",
|
|
1383
1516
|
args: {
|
|
@@ -1418,6 +1551,19 @@ export const KevinPlugin = async (input, options) => {
|
|
|
1418
1551
|
}),
|
|
1419
1552
|
};
|
|
1420
1553
|
}
|
|
1554
|
+
// v2.0.0 (K16-004) — removed keys return structured removed_in_2.0.0 before unknown_key
|
|
1555
|
+
if (args.key in REMOVED_SETTINGS) {
|
|
1556
|
+
const meta = REMOVED_SETTINGS[args.key];
|
|
1557
|
+
return {
|
|
1558
|
+
title: "kevin_config",
|
|
1559
|
+
output: JSON.stringify({
|
|
1560
|
+
error: "removed_in_2.0.0",
|
|
1561
|
+
key: args.key,
|
|
1562
|
+
replacement: meta.replacement,
|
|
1563
|
+
since: meta.since,
|
|
1564
|
+
}),
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1421
1567
|
const known = KEVIN_CONFIG_KEYS.includes(args.key);
|
|
1422
1568
|
if (!known && args.strict !== false) {
|
|
1423
1569
|
return {
|
|
@@ -2148,6 +2294,28 @@ export const KevinPlugin = async (input, options) => {
|
|
|
2148
2294
|
catch {
|
|
2149
2295
|
// best-effort — snapshot failure must not break idle
|
|
2150
2296
|
}
|
|
2297
|
+
// v1.5.0 (K15-007) — skill bundle refresh after snapshots flush
|
|
2298
|
+
try {
|
|
2299
|
+
const manifestPath = join(materializerRoot, "skills-manifest.json");
|
|
2300
|
+
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);
|
|
2301
|
+
// 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
|
|
2302
|
+
const hasManifest = existsSync(manifestPath);
|
|
2303
|
+
const mirrorActive = memoryService.getSetting("skills_mirror_claude", "0") === "1" || memoryService.getSetting("skills_mirror_cursor", "0") === "1";
|
|
2304
|
+
if (hasManifest || mirrorActive) {
|
|
2305
|
+
const { refreshSkillBundle } = await import("@jmtrin/kevin-core");
|
|
2306
|
+
const bundles = materializer.topicBundles();
|
|
2307
|
+
const canonicalDir = memoryService.getSetting("skills_canonical_dir", ".agents/skills");
|
|
2308
|
+
const mirrors = [];
|
|
2309
|
+
if (memoryService.getSetting("skills_mirror_claude", "0") === "1")
|
|
2310
|
+
mirrors.push("claude");
|
|
2311
|
+
if (memoryService.getSetting("skills_mirror_cursor", "0") === "1")
|
|
2312
|
+
mirrors.push("cursor");
|
|
2313
|
+
refreshSkillBundle({ projectRoot, canonicalDir, mirrors, topics: bundles, repoId: sessionIdentity.repoId, manifestPath, metrics: metrics });
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
catch {
|
|
2317
|
+
// best-effort
|
|
2318
|
+
}
|
|
2151
2319
|
// v1.0.0 (K10-013 / D10-08) — the session recorded work:
|
|
2152
2320
|
// arm the deferred dispose settlement. The ISO timestamp
|
|
2153
2321
|
// lets the next session.start compare it against
|
package/package.json
CHANGED
|
@@ -1,58 +1,50 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"learning",
|
|
51
|
-
"ai"
|
|
52
|
-
],
|
|
53
|
-
"author": "jmtrin",
|
|
54
|
-
"publishConfig": {
|
|
55
|
-
"access": "public"
|
|
56
|
-
},
|
|
57
|
-
"license": "MIT"
|
|
2
|
+
"name": "@jmtrin/opencode-kevin",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Kevin — Observa y aprende: capa de aprendizaje para OpenCode",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/plugin/index.js",
|
|
7
|
+
"types": "dist/plugin/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/plugin/index.d.ts",
|
|
11
|
+
"import": "./dist/plugin/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./tui": {
|
|
14
|
+
"types": "@jmtrin/opencode-kevin-tui/dist/index.d.ts",
|
|
15
|
+
"import": "@jmtrin/opencode-kevin-tui/dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": ["dist/plugin"],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -p tsconfig.json",
|
|
21
|
+
"prepublishOnly": "npm run build",
|
|
22
|
+
"typecheck": "tsc --noEmit"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@opencode-ai/plugin": "^1.18.16",
|
|
26
|
+
"@jmtrin/kevin-core": "2.0.0",
|
|
27
|
+
"@jmtrin/opencode-kevin-tui": "2.0.0"
|
|
28
|
+
},
|
|
29
|
+
"optionalDependencies": {
|
|
30
|
+
"better-sqlite3": "^12.11.0"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=22.5.0",
|
|
34
|
+
"opencode": "^1.18.0"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/jmtrin/opencode-kevin.git"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/jmtrin/opencode-kevin#readme",
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/jmtrin/opencode-kevin/issues"
|
|
43
|
+
},
|
|
44
|
+
"keywords": ["opencode", "plugin", "memory", "learning", "ai"],
|
|
45
|
+
"author": "jmtrin",
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"license": "MIT"
|
|
58
50
|
}
|