@jmtrin/opencode-kevin 0.8.0 → 0.9.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 (36) hide show
  1. package/README.md +63 -10
  2. package/dist/migrations/010_v09_native.sql +78 -0
  3. package/dist/plugin/HookLiveness.d.ts +81 -0
  4. package/dist/plugin/HookLiveness.js +324 -0
  5. package/dist/plugin/HookLiveness.js.map +1 -0
  6. package/dist/plugin/Materializer.d.ts +25 -0
  7. package/dist/plugin/Materializer.js +39 -10
  8. package/dist/plugin/Materializer.js.map +1 -1
  9. package/dist/plugin/Migrate.d.ts +1 -0
  10. package/dist/plugin/Migrate.js +46 -5
  11. package/dist/plugin/Migrate.js.map +1 -1
  12. package/dist/plugin/RepoIdentity.d.ts +20 -9
  13. package/dist/plugin/RepoIdentity.js +44 -9
  14. package/dist/plugin/RepoIdentity.js.map +1 -1
  15. package/dist/plugin/Retrospective.js +9 -0
  16. package/dist/plugin/Retrospective.js.map +1 -1
  17. package/dist/plugin/host.d.ts +70 -0
  18. package/dist/plugin/host.js +251 -0
  19. package/dist/plugin/host.js.map +1 -0
  20. package/dist/plugin/index.d.ts +2 -2
  21. package/dist/plugin/index.js +129 -7
  22. package/dist/plugin/index.js.map +1 -1
  23. package/dist/plugin/kevin_audit.d.ts +29 -0
  24. package/dist/plugin/kevin_audit.js +84 -0
  25. package/dist/plugin/kevin_audit.js.map +1 -1
  26. package/dist/plugin/kevin_doctor.d.ts +57 -0
  27. package/dist/plugin/kevin_doctor.js +168 -0
  28. package/dist/plugin/kevin_doctor.js.map +1 -0
  29. package/dist/plugin/kevin_native.d.ts +29 -0
  30. package/dist/plugin/kevin_native.js +80 -0
  31. package/dist/plugin/kevin_native.js.map +1 -0
  32. package/dist/plugin/native.d.ts +92 -0
  33. package/dist/plugin/native.js +191 -0
  34. package/dist/plugin/native.js.map +1 -0
  35. package/migrations/010_v09_native.sql +78 -0
  36. package/package.json +2 -2
@@ -0,0 +1,251 @@
1
+ /**
2
+ * K9-004 — v0.9.0 native — host probe (plan §5.1, D9-12).
3
+ *
4
+ * Kevin stops assuming the host and asks it instead. `probeHost()` inspects the
5
+ * plugin input and the resolved `@opencode-ai/plugin` package and answers four
6
+ * questions once per process: what plugin version is actually installed, which
7
+ * v1+v2 surface exists, what project context the host already knows, and
8
+ * whether a shell was handed over.
9
+ *
10
+ * Contract (plan §5.1 / §4 traps 24-26):
11
+ * - Never throws: every read is guarded, every failure appends to `notes` and
12
+ * yields a conservative `false`. A throw during construction takes down the
13
+ * host's plugin load — strictly worse than any missing feature.
14
+ * - Duck-typed: only `typeof` and `in` checks, never `instanceof`, never a cast
15
+ * to a host class.
16
+ * - The v2 probe is a dynamic `await import()` of the v2/promise subpath inside
17
+ * a try. Rejection is the answer (a pre-v2 package). When it resolves we record
18
+ * which domains are actually exposed rather than assuming both — the promise
19
+ * subpath exports `define` at runtime and its `skill`/`reference` domains are
20
+ * compile-time types, so domain availability is verified against the resolved
21
+ * package's own declaration files.
22
+ * - Runs once: the result is cached and frozen. A capability that appears
23
+ * mid-session is indistinguishable from a bug (D9-12).
24
+ * - `pluginVersion` comes from the resolved package's `package.json` — never
25
+ * from the declared range, which plan §3.4 showed to be untrustworthy.
26
+ * - The v2 subpath string itself lives in native.ts (K9-013 containment): this
27
+ * module only ever names it through the imported symbol.
28
+ */
29
+ import { existsSync, readFileSync } from "node:fs";
30
+ import { createRequire } from "node:module";
31
+ import { dirname, join } from "node:path";
32
+ import { fileURLToPath } from "node:url";
33
+ // v0.9.0 (K9-013 / plan §5.4, D9-11) — the only file allowed to name the v2
34
+ // subpath is plugin/native.ts; this module uses the exported symbol.
35
+ import { V2_SPECIFIER } from "./native.js";
36
+ let cachedSurface = null;
37
+ /**
38
+ * v0.9.0 (K9-004 / plan §5.1, D9-12)
39
+ * `importV2` is a test seam; production callers omit it and the real
40
+ * `await import()` runs inside the try.
41
+ */
42
+ export async function probeHost(input, options) {
43
+ if (cachedSurface !== null) {
44
+ return cachedSurface;
45
+ }
46
+ const notes = [];
47
+ const project = readProject(input, notes);
48
+ const hasShell = readShell(input, notes);
49
+ const v2 = await probeV2(notes, options?.importV2);
50
+ const pluginVersion = readPluginVersion(notes);
51
+ const surface = Object.freeze({
52
+ pluginVersion,
53
+ flavour: v2 === null ? "v1-only" : "v1+v2",
54
+ project: Object.freeze(project),
55
+ hasShell,
56
+ v2: Object.freeze({
57
+ skill: v2?.skill ?? false,
58
+ reference: v2?.reference ?? false,
59
+ }),
60
+ notes: Object.freeze(notes),
61
+ });
62
+ cachedSurface = surface;
63
+ return surface;
64
+ }
65
+ /**
66
+ * v0.9.0 (K9-004) — test hook. probeHost caches its result (probe once, freeze,
67
+ * restart to re-probe); tests reset between cases.
68
+ */
69
+ export function resetHostProbeCache() {
70
+ cachedSurface = null;
71
+ }
72
+ /**
73
+ * v0.9.0 (K9-004 / plan §5.1) — one stable paragraph: version, flavour and the
74
+ * domain flags. No paths, no identifiers; safe to paste into an issue report.
75
+ */
76
+ export function summarize(s) {
77
+ const version = s.pluginVersion ?? "unknown";
78
+ const shell = s.hasShell ? "yes" : "no";
79
+ const skill = s.v2.skill ? "yes" : "no";
80
+ const reference = s.v2.reference ? "yes" : "no";
81
+ return `host plugin ${version}, flavour ${s.flavour}, shell ${shell}, v2 skill ${skill}, v2 reference ${reference}`;
82
+ }
83
+ function readProject(input, notes) {
84
+ let id = null;
85
+ let worktree = null;
86
+ let directory = null;
87
+ if (typeof input === "object" && input !== null) {
88
+ const record = input;
89
+ const project = record.project;
90
+ if (typeof project === "object" && project !== null) {
91
+ const p = project;
92
+ id = typeof p.id === "string" ? p.id : null;
93
+ worktree = typeof p.worktree === "string" ? p.worktree : null;
94
+ directory =
95
+ typeof p.directory === "string" ? p.directory : null;
96
+ }
97
+ // PluginInput also carries top-level worktree/directory; fall back to
98
+ // them when the project object does not.
99
+ if (worktree === null && typeof record.worktree === "string") {
100
+ worktree = record.worktree;
101
+ }
102
+ if (directory === null && typeof record.directory === "string") {
103
+ directory = record.directory;
104
+ }
105
+ if (id === null && worktree === null && directory === null) {
106
+ notes.push("no project context on the plugin input (D9-12)");
107
+ }
108
+ }
109
+ else {
110
+ notes.push("plugin input is not an object — host fields unavailable (D9-12)");
111
+ }
112
+ return { id, worktree, directory };
113
+ }
114
+ function readShell(input, notes) {
115
+ if (typeof input !== "object" || input === null) {
116
+ return false;
117
+ }
118
+ const shell = input.$;
119
+ const present = (typeof shell === "object" && shell !== null) ||
120
+ typeof shell === "function";
121
+ if (!present) {
122
+ notes.push("no shell ($) handed over — zero process-launching stays a decision (D9-12)");
123
+ }
124
+ return present;
125
+ }
126
+ /**
127
+ * v0.9.0 (K9-004 / plan §5.1, D9-11/D9-12)
128
+ * Returns null when the v2 surface is absent; otherwise the domain flags
129
+ * verified against the resolved package's own declaration files.
130
+ */
131
+ async function probeV2(notes, importV2) {
132
+ let mod;
133
+ try {
134
+ mod = importV2
135
+ ? await importV2()
136
+ : await import(/* @vite-ignore */ V2_SPECIFIER);
137
+ }
138
+ catch {
139
+ notes.push(`${V2_SPECIFIER} import rejected — this host package predates the v2 subpath (D9-11)`);
140
+ return null;
141
+ }
142
+ if (typeof mod !== "object" || mod === null) {
143
+ notes.push("v2/promise resolved to a non-object module — treated as v1-only (D9-12)");
144
+ return null;
145
+ }
146
+ if (typeof mod.define !== "function") {
147
+ notes.push("v2/promise resolved but exposes no define — treated as v1-only (D9-12)");
148
+ return null;
149
+ }
150
+ const root = resolvePluginRoot(notes);
151
+ if (root === null) {
152
+ notes.push("v2/promise resolved but the package root is unreachable — v2 domains unverified (D9-12)");
153
+ return { skill: false, reference: false };
154
+ }
155
+ // The promise subpath ships skill/reference as declaration files; record
156
+ // what is actually there rather than assuming both (plan §5.1).
157
+ return {
158
+ skill: existsSync(join(root, "dist", "v2", "promise", "skill.d.ts")),
159
+ reference: existsSync(join(root, "dist", "v2", "promise", "reference.d.ts")),
160
+ };
161
+ }
162
+ function readPluginVersion(notes) {
163
+ const root = resolvePluginRoot(notes);
164
+ if (root === null) {
165
+ return null;
166
+ }
167
+ try {
168
+ const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
169
+ if (typeof pkg.version === "string" && pkg.version.length > 0) {
170
+ return pkg.version;
171
+ }
172
+ notes.push("resolved plugin package.json declares no version (K9-004)");
173
+ return null;
174
+ }
175
+ catch {
176
+ notes.push("resolved plugin package.json unreadable (K9-004)");
177
+ return null;
178
+ }
179
+ }
180
+ /**
181
+ * v0.9.0 (K9-004 / plan §5.1) — the installed package's directory, found by
182
+ * the most reliable strategy that works in this runtime. Never throws.
183
+ */
184
+ function resolvePluginRoot(notes) {
185
+ // 1. import.meta.resolve: the true resolved copy (Node >= 20.6, Bun).
186
+ const resolveMeta = import.meta.resolve;
187
+ if (typeof resolveMeta === "function") {
188
+ try {
189
+ const url = resolveMeta("@opencode-ai/plugin");
190
+ const root = packageRootOf(url, notes);
191
+ if (root !== null) {
192
+ return root;
193
+ }
194
+ }
195
+ catch {
196
+ // unsupported in this runtime — next strategy
197
+ }
198
+ }
199
+ // 2. createRequire: hosts that ship a "require" condition (this package
200
+ // exports only "import", so this is a forward-compat strategy).
201
+ try {
202
+ const resolve = createRequire(import.meta.url).resolve(`${V2_SPECIFIER}/index.js`);
203
+ const root = packageRootOf(resolve, notes);
204
+ if (root !== null) {
205
+ return root;
206
+ }
207
+ }
208
+ catch {
209
+ // not resolvable via require — next strategy
210
+ }
211
+ // 3. Walk up from cwd looking for a node_modules copy (works in the repo
212
+ // and in typical host layouts; best effort, guarded).
213
+ let dir = process.cwd();
214
+ for (let depth = 0; depth < 10; depth += 1) {
215
+ const candidate = join(dir, "node_modules", "@opencode-ai", "plugin");
216
+ if (existsSync(join(candidate, "package.json"))) {
217
+ return candidate;
218
+ }
219
+ const parent = dirname(dir);
220
+ if (parent === dir) {
221
+ break;
222
+ }
223
+ dir = parent;
224
+ }
225
+ notes.push("resolved plugin package root not reachable (K9-004)");
226
+ return null;
227
+ }
228
+ /** Accepts a file URL or a plain path to a file inside the package. */
229
+ function packageRootOf(file, notes) {
230
+ try {
231
+ const path = file.startsWith("file:")
232
+ ? fileURLToPath(file)
233
+ : file.replace(/\\/g, "/");
234
+ // dist/index.js → dist → root; dist/v2/promise/index.js → three levels.
235
+ const candidates = [
236
+ dirname(path),
237
+ dirname(dirname(path)),
238
+ dirname(dirname(dirname(path))),
239
+ ];
240
+ for (const candidate of candidates) {
241
+ if (existsSync(join(candidate, "package.json"))) {
242
+ return candidate;
243
+ }
244
+ }
245
+ }
246
+ catch {
247
+ notes.push("could not map the resolved plugin file to its package root (K9-004)");
248
+ }
249
+ return null;
250
+ }
251
+ //# sourceMappingURL=host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.js","sourceRoot":"","sources":["../../plugin/host.ts"],"names":[],"mappings":"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"}
@@ -16,10 +16,10 @@ export interface KevinPluginOptions {
16
16
  * v0.4.0 (K4-021) — settings surfaced by `kevin_config` (plan §8.8).
17
17
  * Unknown keys are rejected on `set` unless `strict: false`.
18
18
  */
19
- 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"];
19
+ 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"];
20
20
  export declare const ERROR_LESSON_MODE_VALUES: readonly ["all", "triage_only"];
21
21
  /** Plugin release version — stamped into generated files (K8-021/027). */
22
- export declare const KEVIN_VERSION = "0.8.0";
22
+ export declare const KEVIN_VERSION = "0.9.0";
23
23
  export interface RekeyCounts {
24
24
  memories: number;
25
25
  shared_entries: number;
@@ -11,6 +11,7 @@ import { ContextInjector } from "./ContextInjector.js";
11
11
  import { ConventionMiner } from "./ConventionMiner.js";
12
12
  import { Curator } from "./Curator.js";
13
13
  import { Feedback } from "./Feedback.js";
14
+ import { HookLiveness } from "./HookLiveness.js";
14
15
  import { InjectionLedger } from "./InjectionLedger.js";
15
16
  import { Materializer, SKILL_TOPIC } from "./Materializer.js";
16
17
  import { MemoryService, hasRepoIdColumn, } from "./MemoryService.js";
@@ -25,16 +26,21 @@ import { Store } from "./Store.js";
25
26
  import { ToolCallObserver } from "./ToolCallObserver.js";
26
27
  import { probe } from "./capabilities.js";
27
28
  import { computeConfidence } from "./confidence.js";
29
+ import { probeHost, summarize } from "./host.js";
28
30
  import { kevinApprove } from "./kevin_approve.js";
29
31
  import { buildAudit } from "./kevin_audit.js";
30
32
  import { executeKevinConflicts } from "./kevin_conflicts.js";
33
+ import { buildDoctor } from "./kevin_doctor.js";
31
34
  import { buildKevinFacts } from "./kevin_facts.js";
35
+ import { handleNative } from "./kevin_native.js";
32
36
  import { kevinPropose } from "./kevin_propose.js";
33
37
  import { kevinPublish } from "./kevin_publish.js";
34
38
  import { kevinWhy } from "./kevin_why.js";
35
39
  import { Metrics } from "./metrics.js";
40
+ import { attachNative } from "./native.js";
36
41
  import { exportMarkdown, exportOkf } from "./okf-export.js";
37
42
  import { importOkf } from "./okf-import.js";
43
+ import { uuidv7 } from "./uuid.js";
38
44
  // v0.6.0 (K6-019) — process-global set of reference topics already
39
45
  // registered with the host this process. Keeps re-registration idempotent
40
46
  // across sessions within one process: a topic registered once is never
@@ -84,6 +90,17 @@ export const KEVIN_CONFIG_KEYS = [
84
90
  "share_requires_approval",
85
91
  "author_identity_mode",
86
92
  "shared_confidence_floor",
93
+ // v0.9.0 (K9-003 / plan §8.10) — the four keys seeded by migration 010
94
+ // section 5. Omitting these makes `kevin_config set` return
95
+ // { error: "unknown_key" } while `kevin_config list` still shows them.
96
+ // hook_liveness_enabled and the two registration/history flags must be
97
+ // compared with === "1" (they are TEXT, and '0' is truthy);
98
+ // dead_hook_report_threshold is a string read with Number.parseInt and
99
+ // clamped to [1, 1000], NaN defaulting to 3 (conventions, §2; D9-09).
100
+ "hook_liveness_enabled",
101
+ "native_registration_enabled",
102
+ "host_probe_history_enabled",
103
+ "dead_hook_report_threshold",
87
104
  ];
88
105
  // v0.7.0 (K7-003 / plan §5.6, D7-12) — the explicit VALUE domain for
89
106
  // `error_lesson_mode`. The setting is TEXT and must be compared with
@@ -93,7 +110,7 @@ export const KEVIN_CONFIG_KEYS = [
93
110
  // behaviour on the next reflection.
94
111
  export const ERROR_LESSON_MODE_VALUES = ["all", "triage_only"];
95
112
  /** Plugin release version — stamped into generated files (K8-021/027). */
96
- export const KEVIN_VERSION = "0.8.0";
113
+ export const KEVIN_VERSION = "0.9.0";
97
114
  function resolveMigrationsDir() {
98
115
  const here = dirname(fileURLToPath(import.meta.url));
99
116
  return join(here, "..", "migrations");
@@ -235,7 +252,12 @@ export const KevinPlugin = async (input, options) => {
235
252
  // (plan §5.7 fallback; D2-11 project scoping wired into the live path).
236
253
  // v0.8.0 (K8-006 / plan §5.1): the repository identity resolves once at
237
254
  // init, next to the project id, and never on a hot path.
238
- const identity = RepoIdentity.resolve(process.cwd());
255
+ // v0.9.0 (K9-004/K9-006 / plan §5.1-5.2, D9-12/D9-13): the host surface
256
+ // is probed once at construction — frozen, never re-probed — and feeds
257
+ // the identity chain as the third source, above `path` and below
258
+ // `declared`/`remote`.
259
+ const host = await probeHost(input);
260
+ const identity = RepoIdentity.resolve(process.cwd(), host);
239
261
  const projectId = identity.projectId;
240
262
  const repoId = identity.repoId;
241
263
  const identitySource = identity.source;
@@ -249,6 +271,27 @@ export const KevinPlugin = async (input, options) => {
249
271
  // not what the session is scoped on.
250
272
  let sessionIdentity = identity;
251
273
  const memoryService = new MemoryService(store, metrics, repoId);
274
+ // v0.9.0 (K9-008 / plan §5.1, D9-08) — host probe history: one
275
+ // host_probes row per construction when host_probe_history_enabled
276
+ // is explicitly '1' (TEXT comparison — truthiness would fire on
277
+ // '0'). Append-only and unbounded by design: off by default, one
278
+ // row per process start, no retention policy.
279
+ if (memoryService.getSetting("host_probe_history_enabled", "0") === "1") {
280
+ store
281
+ .prepare(`INSERT INTO host_probes
282
+ (id, plugin_version, flavour, has_shell, v2_skill, v2_reference, notes)
283
+ VALUES (?, ?, ?, ?, ?, ?, ?)`)
284
+ .run(uuidv7(), host.pluginVersion, host.flavour, host.hasShell ? 1 : 0, host.v2.skill ? 1 : 0, host.v2.reference ? 1 : 0, host.notes.join("\n"));
285
+ }
286
+ // v0.9.0 (K9-009 / plan §5.3, D9-07/D9-08) — hook liveness. Wraps every
287
+ // hook with a success-path recorder; counters persist on the
288
+ // metrics.flush() cadence. Explicit === "1" TEXT comparison
289
+ // (kevin_settings.value is TEXT).
290
+ const liveness = new HookLiveness(store, {
291
+ enabled: memoryService.getSetting("hook_liveness_enabled", "1") === "1",
292
+ thresholdText: memoryService.getSetting("dead_hook_report_threshold", "3"),
293
+ pluginVersion: host.pluginVersion,
294
+ });
252
295
  // v0.7.0 (K7-009 / plan §5.1, D7-13) — the repository truth scanner reads
253
296
  // the JSON project files. Runs once at init, gated by repo_truth_enabled.
254
297
  const projectRoot = opts.projectRoot ?? process.cwd();
@@ -334,6 +377,24 @@ export const KevinPlugin = async (input, options) => {
334
377
  mkdirSync(join(materializerRoot, "refs"), { recursive: true });
335
378
  const capabilities = probe(input);
336
379
  const materializer = new Materializer(store, { root: materializerRoot });
380
+ // v0.9.0 (K9-016 / plan §5.4, D9-10) — native registration replaces
381
+ // file emission. When attachNative returns a registration for a
382
+ // surface, the corresponding *_emission_enabled path is skipped for
383
+ // that surface only. The guard lives in Materializer.
384
+ try {
385
+ const nativeReg = await attachNative(host, {
386
+ materializer,
387
+ settings: memoryService,
388
+ store,
389
+ });
390
+ if (nativeReg) {
391
+ materializer.markNativeRegistered("skill", nativeReg.registered.skill);
392
+ materializer.markNativeRegistered("reference", nativeReg.registered.reference);
393
+ }
394
+ }
395
+ catch {
396
+ // attachNative never throws (D9-12) — guard against unexpected rejection.
397
+ }
337
398
  // v0.6.0 (K6-018 / plan §8.14, D6-13) — skill emission at session
338
399
  // start, behind the capability probe AND `skill_emission_enabled`
339
400
  // (default '0'). Both no-op paths are silent: "unavailable" (v1 host)
@@ -445,7 +506,11 @@ export const KevinPlugin = async (input, options) => {
445
506
  projectId,
446
507
  }));
447
508
  }
448
- return {
509
+ // v0.9.0 (K9-009 / plan §5.3) — the hooks object is wrapped here at the
510
+ // point of return: transparent (identical keys/arity/returns/errors),
511
+ // recording fires on the success path only. With hook_liveness_enabled
512
+ // '0' the wrapper returns this same object untouched.
513
+ return liveness.wrap({
449
514
  tool: {
450
515
  kevin_save: tool({
451
516
  description: "Guarda una memoria en el conocimiento persistente de Kevin (error, pattern, decision o context).",
@@ -783,8 +848,8 @@ export const KevinPlugin = async (input, options) => {
783
848
  // monotone across releases; K6-024 extends this
784
849
  // block with the remaining v0.6 fields. v0.8.0
785
850
  // (K8-025) — 18 v0.7 + kevin_project +
786
- // kevin_share + kevin_sync = 21.
787
- tool_count: 21,
851
+ // kevin_native = 23.
852
+ tool_count: 23,
788
853
  v07,
789
854
  memories_reflector: memoriesReflector,
790
855
  memories_agent: memoriesAgent,
@@ -819,6 +884,10 @@ export const KevinPlugin = async (input, options) => {
819
884
  v06,
820
885
  // v0.8.0 (K8-025) — omitted on pre-009 DBs.
821
886
  v08,
887
+ // v0.9.0 (K9-008 / plan §5.1) — one-line host
888
+ // summary: version, flavour, shell, v2 flags;
889
+ // no paths, no ids (charset-safe for log lines).
890
+ host_summary: summarize(host),
822
891
  }),
823
892
  };
824
893
  },
@@ -846,7 +915,7 @@ export const KevinPlugin = async (input, options) => {
846
915
  path: res.path,
847
916
  id: res.id,
848
917
  created_at: res.createdAt,
849
- generator: "opencode-kevin/0.8.0",
918
+ generator: "opencode-kevin/0.9.0",
850
919
  }
851
920
  : {
852
921
  action: "init",
@@ -968,6 +1037,48 @@ export const KevinPlugin = async (input, options) => {
968
1037
  };
969
1038
  },
970
1039
  }),
1040
+ // v0.9.0 (K9-018 / plan §5.5, D9-09) — kevin_doctor: pure
1041
+ // reads, no writes, no probe re-run, no model call. The host
1042
+ // block comes from the frozen init-time probe, hooks from the
1043
+ // persisted hook_liveness table, native from the last
1044
+ // registration per surface, verdict from reduceVerdict.
1045
+ kevin_doctor: tool({
1046
+ description: "Doctor de Kevin (v0.9.0): salud del host, hooks, dependencias y registros nativos. Solo lectura, sin LLM ni writes, invocable en cualquier momento. Devuelve host (version, flavour, shell, v2), hooks ordenados dead primero, dependencies (zod_copies), native (enabled, registered/verified por surface), verdict (healthy|degraded|unknown) y reason. Output pensado para pegar en un issue: sin filesystem paths ni session ids.",
1047
+ args: {},
1048
+ async execute() {
1049
+ const report = buildDoctor(store, host, memoryService);
1050
+ return {
1051
+ title: "Doctor de Kevin",
1052
+ output: JSON.stringify(report),
1053
+ };
1054
+ },
1055
+ }),
1056
+ // v0.9.0 (K9-019 / plan §5.5, D9-12) — kevin_native: inspect
1057
+ // and toggle native_registration_enabled. enable/disable write
1058
+ // kevin_settings only and never re-attach — the probe is
1059
+ // frozen for the process lifetime, so a restart is the
1060
+ // requirement for the change to take effect. `enable` on a
1061
+ // host without the v2 subpath succeeds and reports the
1062
+ // registration as inert: the setting is a statement of intent.
1063
+ kevin_native: tool({
1064
+ description: "Registros nativos v2 (v0.9.0): show reporta el setting native_registration_enabled, si el host resuelto expone el subpath v2 (effective) y las ultimas filas de native_registrations por surface; enable/disable escriben el setting ('1'/'0' TEXT) y NO re-adjuntan — el probe es frozen para la vida del proceso, un restart del host es el requisito para que el cambio tenga efecto; enable en un host sin subpath v2 exito con registration inert.",
1065
+ args: {
1066
+ action: tool.schema
1067
+ .enum(["show", "enable", "disable"])
1068
+ .default("show"),
1069
+ },
1070
+ async execute(args) {
1071
+ const report = handleNative(args.action, {
1072
+ host,
1073
+ store,
1074
+ settings: memoryService,
1075
+ });
1076
+ return {
1077
+ title: "Kevin native",
1078
+ output: JSON.stringify(report),
1079
+ };
1080
+ },
1081
+ }),
971
1082
  kevin_retrospective: tool({
972
1083
  description: "Genera un retrospective markdown para una sesion (resume tools que fallaron y lecciones aprendidas).",
973
1084
  args: {
@@ -1418,6 +1529,13 @@ export const KevinPlugin = async (input, options) => {
1418
1529
  }, {});
1419
1530
  },
1420
1531
  "tool.execute.after": async (hookInput, output) => {
1532
+ // v0.9.0 (K9-010 / plan §5.3) — checkpoint: the session reached a
1533
+ // model turn, so the system prompt was assembled and
1534
+ // `experimental.chat.system.transform` MUST have been offered.
1535
+ // Deduped per session inside HookLiveness.expect.
1536
+ if (hookInput.sessionID) {
1537
+ liveness.expect("experimental.chat.system.transform", hookInput.sessionID);
1538
+ }
1421
1539
  const meta = (output.metadata ?? {});
1422
1540
  const outputText = String(output.output ?? "");
1423
1541
  const stderr = String(meta.stderr ?? "");
@@ -1637,6 +1755,9 @@ export const KevinPlugin = async (input, options) => {
1637
1755
  }
1638
1756
  }
1639
1757
  metrics.flush();
1758
+ // v0.9.0 (K9-009 / plan §5.3) — liveness counters persist on
1759
+ // the same cadence as the metrics.
1760
+ liveness.flush();
1640
1761
  }
1641
1762
  else if (type === "session.next.tool.failed") {
1642
1763
  const callID = props.callID;
@@ -1654,10 +1775,11 @@ export const KevinPlugin = async (input, options) => {
1654
1775
  },
1655
1776
  dispose: async () => {
1656
1777
  await Promise.allSettled([...pending]);
1778
+ liveness.flush();
1657
1779
  metrics.close();
1658
1780
  store.close();
1659
1781
  },
1660
- };
1782
+ });
1661
1783
  };
1662
1784
  export default KevinPlugin;
1663
1785
  //# sourceMappingURL=index.js.map