@jmtrin/opencode-kevin 0.8.0 → 1.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.
Files changed (131) hide show
  1. package/README.md +559 -771
  2. package/dist/migrations/010_v09_native.sql +78 -0
  3. package/dist/migrations/011_v10_proven.sql +69 -0
  4. package/dist/plugin/Archiver.js +0 -1
  5. package/dist/plugin/ArtifactWriter.d.ts +25 -10
  6. package/dist/plugin/ArtifactWriter.js +43 -25
  7. package/dist/plugin/CausalChain.js +0 -1
  8. package/dist/plugin/ConflictDetector.js +0 -1
  9. package/dist/plugin/ContextInjector.js +0 -1
  10. package/dist/plugin/ConventionMiner.js +0 -1
  11. package/dist/plugin/Curator.js +0 -1
  12. package/dist/plugin/Feedback.js +0 -1
  13. package/dist/plugin/HookLiveness.d.ts +87 -0
  14. package/dist/plugin/HookLiveness.js +338 -0
  15. package/dist/plugin/InjectionLedger.js +0 -1
  16. package/dist/plugin/LessonFixer.js +0 -1
  17. package/dist/plugin/Materializer.d.ts +25 -0
  18. package/dist/plugin/Materializer.js +39 -11
  19. package/dist/plugin/MemoryService.js +0 -1
  20. package/dist/plugin/Migrate.d.ts +1 -0
  21. package/dist/plugin/Migrate.js +69 -6
  22. package/dist/plugin/PatternMiner.js +0 -1
  23. package/dist/plugin/QualityGate.js +0 -1
  24. package/dist/plugin/Reflector.js +0 -1
  25. package/dist/plugin/RepoIdentity.d.ts +20 -9
  26. package/dist/plugin/RepoIdentity.js +44 -10
  27. package/dist/plugin/RepoTruth.js +0 -1
  28. package/dist/plugin/Retrospective.js +16 -1
  29. package/dist/plugin/SharedLayer.js +0 -1
  30. package/dist/plugin/Store.js +0 -1
  31. package/dist/plugin/ToolCallObserver.js +0 -1
  32. package/dist/plugin/capabilities.js +0 -1
  33. package/dist/plugin/confidence.js +0 -1
  34. package/dist/plugin/contract.d.ts +46 -0
  35. package/dist/plugin/contract.js +368 -0
  36. package/dist/plugin/diff.js +0 -1
  37. package/dist/plugin/escape.d.ts +39 -0
  38. package/dist/plugin/escape.js +60 -0
  39. package/dist/plugin/fingerprint.js +0 -1
  40. package/dist/plugin/host.d.ts +70 -0
  41. package/dist/plugin/host.js +250 -0
  42. package/dist/plugin/index.d.ts +2 -2
  43. package/dist/plugin/index.js +542 -222
  44. package/dist/plugin/inferability.js +0 -1
  45. package/dist/plugin/kevin_approve.js +0 -1
  46. package/dist/plugin/kevin_audit.d.ts +60 -0
  47. package/dist/plugin/kevin_audit.js +144 -1
  48. package/dist/plugin/kevin_bench.d.ts +10 -0
  49. package/dist/plugin/kevin_bench.js +82 -0
  50. package/dist/plugin/kevin_conflicts.js +0 -1
  51. package/dist/plugin/kevin_contract.d.ts +8 -0
  52. package/dist/plugin/kevin_contract.js +59 -0
  53. package/dist/plugin/kevin_doctor.d.ts +62 -0
  54. package/dist/plugin/kevin_doctor.js +196 -0
  55. package/dist/plugin/kevin_facts.js +0 -1
  56. package/dist/plugin/kevin_native.d.ts +29 -0
  57. package/dist/plugin/kevin_native.js +79 -0
  58. package/dist/plugin/kevin_propose.js +0 -1
  59. package/dist/plugin/kevin_publish.js +0 -1
  60. package/dist/plugin/kevin_why.js +0 -1
  61. package/dist/plugin/memory-format.js +0 -1
  62. package/dist/plugin/metrics.js +0 -1
  63. package/dist/plugin/native.d.ts +92 -0
  64. package/dist/plugin/native.js +190 -0
  65. package/dist/plugin/okf-export.js +0 -1
  66. package/dist/plugin/okf-import.js +0 -1
  67. package/dist/plugin/okf.js +0 -1
  68. package/dist/plugin/perf.d.ts +35 -0
  69. package/dist/plugin/perf.js +174 -0
  70. package/dist/plugin/query-tokenizer.js +0 -1
  71. package/dist/plugin/redact.js +0 -1
  72. package/dist/plugin/replay-types.js +0 -1
  73. package/dist/plugin/replay.js +0 -1
  74. package/dist/plugin/sqlite-adapter.js +0 -1
  75. package/dist/plugin/uuid.js +0 -1
  76. package/package.json +22 -7
  77. package/dist/plugin/Archiver.js.map +0 -1
  78. package/dist/plugin/ArtifactWriter.js.map +0 -1
  79. package/dist/plugin/CausalChain.js.map +0 -1
  80. package/dist/plugin/ConflictDetector.js.map +0 -1
  81. package/dist/plugin/ContextInjector.js.map +0 -1
  82. package/dist/plugin/ConventionMiner.js.map +0 -1
  83. package/dist/plugin/Curator.js.map +0 -1
  84. package/dist/plugin/Feedback.js.map +0 -1
  85. package/dist/plugin/InjectionLedger.js.map +0 -1
  86. package/dist/plugin/LessonFixer.js.map +0 -1
  87. package/dist/plugin/Materializer.js.map +0 -1
  88. package/dist/plugin/MemoryService.js.map +0 -1
  89. package/dist/plugin/Migrate.js.map +0 -1
  90. package/dist/plugin/PatternMiner.js.map +0 -1
  91. package/dist/plugin/QualityGate.js.map +0 -1
  92. package/dist/plugin/Reflector.js.map +0 -1
  93. package/dist/plugin/RepoIdentity.js.map +0 -1
  94. package/dist/plugin/RepoTruth.js.map +0 -1
  95. package/dist/plugin/Retrospective.js.map +0 -1
  96. package/dist/plugin/SharedLayer.js.map +0 -1
  97. package/dist/plugin/Store.js.map +0 -1
  98. package/dist/plugin/ToolCallObserver.js.map +0 -1
  99. package/dist/plugin/capabilities.js.map +0 -1
  100. package/dist/plugin/confidence.js.map +0 -1
  101. package/dist/plugin/diff.js.map +0 -1
  102. package/dist/plugin/fingerprint.js.map +0 -1
  103. package/dist/plugin/index.js.map +0 -1
  104. package/dist/plugin/inferability.js.map +0 -1
  105. package/dist/plugin/kevin_approve.js.map +0 -1
  106. package/dist/plugin/kevin_audit.js.map +0 -1
  107. package/dist/plugin/kevin_conflicts.js.map +0 -1
  108. package/dist/plugin/kevin_facts.js.map +0 -1
  109. package/dist/plugin/kevin_propose.js.map +0 -1
  110. package/dist/plugin/kevin_publish.js.map +0 -1
  111. package/dist/plugin/kevin_why.js.map +0 -1
  112. package/dist/plugin/memory-format.js.map +0 -1
  113. package/dist/plugin/metrics.js.map +0 -1
  114. package/dist/plugin/okf-export.js.map +0 -1
  115. package/dist/plugin/okf-import.js.map +0 -1
  116. package/dist/plugin/okf.js.map +0 -1
  117. package/dist/plugin/query-tokenizer.js.map +0 -1
  118. package/dist/plugin/redact.js.map +0 -1
  119. package/dist/plugin/replay-types.js.map +0 -1
  120. package/dist/plugin/replay.js.map +0 -1
  121. package/dist/plugin/sqlite-adapter.js.map +0 -1
  122. package/dist/plugin/uuid.js.map +0 -1
  123. package/migrations/001_initial.sql +0 -92
  124. package/migrations/002_indexes.sql +0 -14
  125. package/migrations/003_v02_signal.sql +0 -58
  126. package/migrations/004_v03_knowledge.sql +0 -138
  127. package/migrations/005_v04_signal.sql +0 -57
  128. package/migrations/006_v05_glassbox.sql +0 -118
  129. package/migrations/007_v06_pull.sql +0 -145
  130. package/migrations/008_v07_truth.sql +0 -124
  131. package/migrations/009_v08_team.sql +0 -100
@@ -0,0 +1,78 @@
1
+ -- ============================================================
2
+ -- Kevin v0.9.0 "Native" — host surface and hook liveness
3
+ -- Migration 010. Additive only. No table rebuild.
4
+ -- ============================================================
5
+
6
+ -- 1. Hook liveness. Deliberately NOT project-scoped: a hook is a
7
+ -- property of the host binary, not of a checkout. One row per
8
+ -- hook name, updated in place. See D9-08.
9
+ CREATE TABLE IF NOT EXISTS hook_liveness (
10
+ hook TEXT PRIMARY KEY,
11
+ experimental INTEGER NOT NULL DEFAULT 0,
12
+ fire_count INTEGER NOT NULL DEFAULT 0,
13
+ error_count INTEGER NOT NULL DEFAULT 0,
14
+ expected_count INTEGER NOT NULL DEFAULT 0,
15
+ first_seen_at TEXT,
16
+ last_seen_at TEXT,
17
+ dead_since TEXT,
18
+ plugin_version TEXT
19
+ );
20
+
21
+ CREATE INDEX IF NOT EXISTS idx_hook_liveness_dead
22
+ ON hook_liveness(dead_since);
23
+
24
+ -- 2. Host probe history. Append-only. Off by default; exists so a
25
+ -- user chasing an intermittent fault can turn it on and get a
26
+ -- timeline instead of a single current value.
27
+ CREATE TABLE IF NOT EXISTS host_probes (
28
+ id TEXT PRIMARY KEY,
29
+ probed_at TEXT NOT NULL DEFAULT (datetime('now')),
30
+ plugin_version TEXT,
31
+ flavour TEXT NOT NULL,
32
+ has_shell INTEGER NOT NULL DEFAULT 0,
33
+ v2_skill INTEGER NOT NULL DEFAULT 0,
34
+ v2_reference INTEGER NOT NULL DEFAULT 0,
35
+ notes TEXT
36
+ );
37
+
38
+ CREATE INDEX IF NOT EXISTS idx_host_probes_at
39
+ ON host_probes(probed_at);
40
+
41
+ -- 3. Native registration outcomes. One row per attach attempt, so
42
+ -- "registered but unverified" is a queryable state rather than a
43
+ -- log line.
44
+ CREATE TABLE IF NOT EXISTS native_registrations (
45
+ id TEXT PRIMARY KEY,
46
+ attached_at TEXT NOT NULL DEFAULT (datetime('now')),
47
+ surface TEXT NOT NULL CHECK (surface IN ('skill', 'reference')),
48
+ registered INTEGER NOT NULL DEFAULT 0,
49
+ verified INTEGER NOT NULL DEFAULT 0,
50
+ note TEXT
51
+ );
52
+
53
+ CREATE INDEX IF NOT EXISTS idx_native_registrations_surface
54
+ ON native_registrations(surface, attached_at);
55
+
56
+ -- 4. Metric seeds (39 -> 45).
57
+ INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES
58
+ ('hook_fires_total', 0),
59
+ ('hook_errors_total', 0),
60
+ ('hooks_dead_total', 0),
61
+ ('injections_suppressed_dead_hook', 0),
62
+ ('native_registrations_total', 0),
63
+ ('native_registration_failures', 0);
64
+
65
+ -- 5. Setting seeds (23 -> 27).
66
+ -- hook_liveness_enabled defaults ON: it is a read-only instrument
67
+ -- on the success path, and an instrument nobody switches on is an
68
+ -- instrument nobody has.
69
+ -- native_registration_enabled defaults OFF: it changes where a
70
+ -- curated skill comes from, and that is a change a user opts into.
71
+ INSERT OR IGNORE INTO kevin_settings (key, value) VALUES
72
+ ('hook_liveness_enabled', '1'),
73
+ ('native_registration_enabled', '0'),
74
+ ('host_probe_history_enabled', '0'),
75
+ ('dead_hook_report_threshold', '3');
76
+
77
+ -- 6. Version marker.
78
+ INSERT OR IGNORE INTO schema_version (version) VALUES ('010');
@@ -0,0 +1,69 @@
1
+ -- =============================================================
2
+ -- Kevin v1.0.0 "Proven" — migration 011
3
+ -- Adds: perf sampling, benchmark results.
4
+ -- No ALTER TABLE (second release running); no column is retyped.
5
+ -- =============================================================
6
+
7
+ -- 1. Per-scope latency aggregates, flushed at session.idle.
8
+ -- Machine-scoped: no project_id, for the same reason hook_liveness
9
+ -- has none (v0.9.0 D9-08) — latency is a property of the install.
10
+ CREATE TABLE IF NOT EXISTS perf_samples (
11
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
12
+ scope TEXT NOT NULL,
13
+ sample_count INTEGER NOT NULL,
14
+ p50_ms REAL NOT NULL,
15
+ p95_ms REAL NOT NULL,
16
+ max_ms REAL NOT NULL,
17
+ budget_p95_ms REAL NOT NULL,
18
+ within_budget INTEGER NOT NULL DEFAULT 1,
19
+ recorded_at TEXT NOT NULL DEFAULT (datetime('now'))
20
+ );
21
+
22
+ CREATE INDEX IF NOT EXISTS idx_perf_samples_scope
23
+ ON perf_samples(scope, recorded_at DESC);
24
+ CREATE INDEX IF NOT EXISTS idx_perf_samples_breach
25
+ ON perf_samples(within_budget) WHERE within_budget = 0;
26
+
27
+ -- 2. Benchmark results. Append-only. One row per arm per run.
28
+ -- arm CHECK is closed by the plan; widening requires a table rebuild (intentional friction).
29
+ -- scope CHECK is intentionally absent: the scope union may gain members in a 1.x minor.
30
+ CREATE TABLE IF NOT EXISTS bench_runs (
31
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
32
+ corpus_digest TEXT NOT NULL,
33
+ contract_digest TEXT NOT NULL,
34
+ package_version TEXT NOT NULL,
35
+ runtime TEXT NOT NULL,
36
+ arm TEXT NOT NULL,
37
+ k INTEGER NOT NULL,
38
+ precision_at_k REAL NOT NULL,
39
+ recall_at_k REAL NOT NULL,
40
+ mrr REAL NOT NULL,
41
+ ran_at TEXT NOT NULL DEFAULT (datetime('now')),
42
+ CHECK (arm IN ('none', 'recent-k', 'random-k', 'kevin'))
43
+ );
44
+
45
+ CREATE INDEX IF NOT EXISTS idx_bench_runs_corpus
46
+ ON bench_runs(corpus_digest, ran_at DESC);
47
+ CREATE INDEX IF NOT EXISTS idx_bench_runs_arm
48
+ ON bench_runs(arm, ran_at DESC);
49
+
50
+ -- 3. Metric seeds (45 -> 51).
51
+ INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES ('perf_samples_recorded', 0);
52
+ INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES ('perf_budget_breaches', 0);
53
+ INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES ('dispose_fires_total', 0);
54
+ INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES ('dispose_misses_total', 0);
55
+ INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES ('contract_digest_changes', 0);
56
+ INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES ('bench_runs_total', 0);
57
+
58
+ -- 4. Setting seeds (27 -> 31). All TEXT; '0'/'1' are strings.
59
+ INSERT OR IGNORE INTO kevin_settings (key, value) VALUES ('perf_enabled', '1');
60
+ INSERT OR IGNORE INTO kevin_settings (key, value) VALUES ('perf_ring_capacity', '512');
61
+ INSERT OR IGNORE INTO kevin_settings (key, value) VALUES ('perf_flush_on_idle', '1');
62
+ INSERT OR IGNORE INTO kevin_settings (key, value) VALUES ('contract_report_enabled', '1');
63
+
64
+ -- 5. `dispose` becomes the seventh instrumented hook (v0.9.0 migration 010
65
+ -- seeded six rows; this is a data seed, not a schema change).
66
+ INSERT OR IGNORE INTO hook_liveness (hook) VALUES ('dispose');
67
+
68
+ -- 6. Schema version.
69
+ INSERT OR IGNORE INTO schema_version (version) VALUES ('011');
@@ -96,4 +96,3 @@ function formatSqliteUtc(d) {
96
96
  const pad = (n) => String(n).padStart(2, "0");
97
97
  return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`;
98
98
  }
99
- //# sourceMappingURL=Archiver.js.map
@@ -35,18 +35,33 @@ export interface WritePlan {
35
35
  readonly hashAfter: string;
36
36
  }
37
37
  /**
38
- * Rule 9 (K6-009 / plan §5.1, D6-02) — three layers applied to the body
39
- * before splicing:
40
- * (a) the escaping discipline of `plugin/memory-format.ts`;
41
- * (b) strip any line containing `kevin:begin` or `kevin:end`, in any
42
- * casing, anywhere in the line;
43
- * (c) strip HTML comment terminators (`-->`).
44
- * Without this, a memory containing a literal `<!-- kevin:end -->` line would
45
- * close the marker comment early and let subsequent content escape the
46
- * curated region on the next regeneration a marker-injection variant of the
47
- * v0.1.5 prompt-injection defect (plan §3.5).
38
+ * Rule 9, layer (a) — superseded in v1.0.0 by `escapeForMarkerBlock`
39
+ * in `plugin/escape.ts` (K10-027), which carries the same idempotent
40
+ * entity discipline.
41
+ */
42
+ /**
43
+ * v1.0.0 (K10-027 / plan §5.7) the boundary functions now live in
44
+ * `plugin/escape.ts`; this composer applies them in fence-then-marker
45
+ * order. Layer (b) strip any line containing `kevin:begin` or
46
+ * `kevin:end`, in any casing is a filter rather than an escape and
47
+ * stays here. Without layer (b), a memory containing a literal
48
+ * `<!-- kevin:end -->` line would close the marker comment early and
49
+ * let subsequent content escape the curated region on the next
50
+ * regeneration — a marker-injection variant of the v0.1.5
51
+ * prompt-injection defect (plan §3.5). The trailing `-->` strip is kept
52
+ * as defence in depth although `escapeForMarkerBlock` already escapes
53
+ * every `>` that could form one.
48
54
  */
49
55
  export declare function sanitizeArtifactBody(body: string): string;
56
+ /**
57
+ * v1.0.0 (K10-027 / plan §5.7, rule 2) — whole-file writes escape by
58
+ * container. Only `.okf` files are line-oriented JSON: each line gets
59
+ * `escapeForOkfLine`, which is the identity on a well-formed OKF line
60
+ * (canonical JSON never contains raw control characters) and therefore
61
+ * preserves the re-render `noop`. Kevin-owned markdown paths keep their
62
+ * bytes — fences there are legitimate content.
63
+ */
64
+ export declare function escapeForContainer(path: string, content: string): string;
50
65
  /**
51
66
  * v0.6.0 (K6-005 / plan §5.1) — the single write path to disk (D6-01).
52
67
  *
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { closeSync, fsyncSync, openSync, readFileSync, renameSync, unlinkSync, writeSync, } from "node:fs";
3
3
  import { unifiedDiff } from "./diff.js";
4
+ import { escapeForFence, escapeForMarkerBlock, escapeForOkfLine, } from "./escape.js";
4
5
  import { uuidv7 } from "./uuid.js";
5
6
  // v0.6.0 (K6-005 / plan §5.1, D6-02) — the frozen marker contract. The exact
6
7
  // byte sequences are load-bearing: README, v1.0.0 plan C-01 and the round-trip
@@ -30,39 +31,55 @@ function normalizeEol(body, eol) {
30
31
  return body.replace(/\r\n/g, "\n").replace(/\n/g, eol);
31
32
  }
32
33
  /**
33
- * Rule 9, layer (a) — the escaping discipline of `plugin/memory-format.ts`
34
- * (`&`, `<`, `>`) made idempotent: an ampersand that already heads a known
35
- * entity (`&amp;`, `&lt;`, `&gt;`, `&#NNN;`) is left alone, so sanitizing
36
- * sanitized output is a fixed point. The round-trip property of the marker
37
- * block depends on this: a non-idempotent escape would grow `&amp;lt;` on
38
- * every regeneration (K6-009 / plan §5.1 rule 9).
34
+ * Rule 9, layer (a) — superseded in v1.0.0 by `escapeForMarkerBlock`
35
+ * in `plugin/escape.ts` (K10-027), which carries the same idempotent
36
+ * entity discipline.
39
37
  */
40
- function escapeIdempotent(text) {
41
- return text
42
- .replace(/&(?!(amp|lt|gt|#\d+);)/g, "&amp;")
43
- .replace(/</g, "&lt;")
44
- .replace(/>/g, "&gt;");
45
- }
46
38
  /**
47
- * Rule 9 (K6-009 / plan §5.1, D6-02) — three layers applied to the body
48
- * before splicing:
49
- * (a) the escaping discipline of `plugin/memory-format.ts`;
50
- * (b) strip any line containing `kevin:begin` or `kevin:end`, in any
51
- * casing, anywhere in the line;
52
- * (c) strip HTML comment terminators (`-->`).
53
- * Without this, a memory containing a literal `<!-- kevin:end -->` line would
54
- * close the marker comment early and let subsequent content escape the
55
- * curated region on the next regeneration a marker-injection variant of the
56
- * v0.1.5 prompt-injection defect (plan §3.5).
39
+ * v1.0.0 (K10-027 / plan §5.7) — the boundary functions now live in
40
+ * `plugin/escape.ts`; this composer applies them in fence-then-marker
41
+ * order. Layer (b) strip any line containing `kevin:begin` or
42
+ * `kevin:end`, in any casing is a filter rather than an escape and
43
+ * stays here. Without layer (b), a memory containing a literal
44
+ * `<!-- kevin:end -->` line would close the marker comment early and
45
+ * let subsequent content escape the curated region on the next
46
+ * regeneration a marker-injection variant of the v0.1.5
47
+ * prompt-injection defect (plan §3.5). The trailing `-->` strip is kept
48
+ * as defence in depth although `escapeForMarkerBlock` already escapes
49
+ * every `>` that could form one.
57
50
  */
58
51
  export function sanitizeArtifactBody(body) {
59
- const escaped = escapeIdempotent(body);
52
+ const escaped = escapeForMarkerBlock(escapeForFence(body));
60
53
  const kept = escaped
61
54
  .split("\n")
62
55
  .filter((line) => !/kevin:begin|kevin:end/i.test(line))
63
56
  .join("\n");
64
57
  return kept.replace(/-->/g, "");
65
58
  }
59
+ /**
60
+ * v1.0.0 (K10-027 / plan §5.7, rule 2) — whole-file writes escape by
61
+ * container. Only `.okf` files are line-oriented JSON: each line gets
62
+ * `escapeForOkfLine`, which is the identity on a well-formed OKF line
63
+ * (canonical JSON never contains raw control characters) and therefore
64
+ * preserves the re-render `noop`. Kevin-owned markdown paths keep their
65
+ * bytes — fences there are legitimate content.
66
+ */
67
+ export function escapeForContainer(path, content) {
68
+ if (!path.endsWith(".okf"))
69
+ return content;
70
+ return content
71
+ .split("\n")
72
+ .map((line) => {
73
+ // A CRLF file (healHeader preserves the original EOL) leaves a
74
+ // trailing \r on each line after the split — it is line-ending
75
+ // bytes, not statement content, so it must survive untouched.
76
+ if (line.endsWith("\r")) {
77
+ return `${escapeForOkfLine(line.slice(0, -1))}\r`;
78
+ }
79
+ return escapeForOkfLine(line);
80
+ })
81
+ .join("\n");
82
+ }
66
83
  /**
67
84
  * v0.6.0 (K6-005 / plan §5.1) — the single write path to disk (D6-01).
68
85
  *
@@ -113,7 +130,9 @@ export class ArtifactWriter {
113
130
  // like any other refusal: after = before, both hashes recorded.
114
131
  const refusal = request.refusal;
115
132
  const refused = refusal !== undefined;
116
- const after = refused ? before : content;
133
+ // K10-027 the container boundary: OKF lines are escaped at the
134
+ // single write path, never by callers.
135
+ const after = refused ? before : escapeForContainer(path, content);
117
136
  return {
118
137
  path,
119
138
  before,
@@ -270,4 +289,3 @@ export class ArtifactWriter {
270
289
  .run(uuidv7(), proposalId ?? null, this.projectId, plan.path, Buffer.byteLength(plan.before, "utf8"), Buffer.byteLength(plan.after, "utf8"), plan.hashBefore, plan.hashAfter, plan.outcome, plan.reason ?? null);
271
290
  }
272
291
  }
273
- //# sourceMappingURL=ArtifactWriter.js.map
@@ -176,4 +176,3 @@ export class CausalChain {
176
176
  return row?.value === "1";
177
177
  }
178
178
  }
179
- //# sourceMappingURL=CausalChain.js.map
@@ -280,4 +280,3 @@ function oppositePair(a, b) {
280
280
  }
281
281
  return null;
282
282
  }
283
- //# sourceMappingURL=ConflictDetector.js.map
@@ -531,4 +531,3 @@ Consider adding this convention to AGENTS.md:
531
531
  output.context.push(block);
532
532
  }
533
533
  }
534
- //# sourceMappingURL=ContextInjector.js.map
@@ -240,4 +240,3 @@ function statementForSequence(a, b) {
240
240
  function statementForSequence3(a, b, c) {
241
241
  return `every ${a} is followed by ${b} and then ${c}`;
242
242
  }
243
- //# sourceMappingURL=ConventionMiner.js.map
@@ -322,4 +322,3 @@ function normalizeBullet(line) {
322
322
  .replace(/\s+\((?:verified|feedback)[^)]*\)\s*$/i, "");
323
323
  return normalize(statement);
324
324
  }
325
- //# sourceMappingURL=Curator.js.map
@@ -135,4 +135,3 @@ export class Feedback {
135
135
  .run(memoryId, memoryId, memoryId);
136
136
  }
137
137
  }
138
- //# sourceMappingURL=Feedback.js.map
@@ -0,0 +1,87 @@
1
+ import { HOOK_NAMES } from "./Migrate.js";
2
+ import type { Store } from "./Store.js";
3
+ export type HookName = (typeof HOOK_NAMES)[number];
4
+ export type LivenessState = "live" | "dead" | "unknown";
5
+ export interface HookReport {
6
+ hook: HookName;
7
+ experimental: boolean;
8
+ state: LivenessState;
9
+ firstSeenAt: string | null;
10
+ lastSeenAt: string | null;
11
+ fireCount: number;
12
+ expectedCount: number;
13
+ /** v0.9.0 (K9-012) — set once when the hook went dead; never cleared. */
14
+ deadSince: string | null;
15
+ }
16
+ /**
17
+ * v0.9.0 (K9-010 / plan §5.3, D9-09) — parse the TEXT setting
18
+ * `dead_hook_report_threshold` into the [1, 1000] clamp. NaN and empty
19
+ * fall back to 3 (never 0: a zero threshold would report every hook dead
20
+ * before a single session completes a checkpoint).
21
+ */
22
+ export declare function parseThreshold(text: string | null | undefined): number;
23
+ interface HookLivenessOptions {
24
+ /** `hook_liveness_enabled === "1"` (TEXT comparison, decided at construction). */
25
+ enabled: boolean;
26
+ /** Raw TEXT of `dead_hook_report_threshold`; parsed+clamped in report(). */
27
+ thresholdText: string | null | undefined;
28
+ /** `host.pluginVersion` at construction, stored on every flushed row. */
29
+ pluginVersion: string | null;
30
+ }
31
+ /**
32
+ * v0.9.0 (K9-009/K9-010 / plan §5.3) — wraps the plugin's hooks with a
33
+ * success-path recorder, dedups per-session checkpoints, and reports each
34
+ * hook's liveness state. Persistence is machine-scoped: the hook_liveness
35
+ * table carries no project_id or repo_id (D9-08).
36
+ */
37
+ export declare class HookLiveness {
38
+ private readonly store;
39
+ private readonly options;
40
+ private readonly counters;
41
+ private readonly seenSessions;
42
+ private readonly suppressedSessions;
43
+ private readonly threshold;
44
+ private readonly pluginVersion;
45
+ constructor(store: Store, options: HookLivenessOptions);
46
+ /**
47
+ * Returns a new object with the same keys; function values are replaced
48
+ * by delegating wrappers, non-function values (the tool map) pass by
49
+ * reference. When liveness is disabled, returns the argument unchanged —
50
+ * the same object, not a copy.
51
+ */
52
+ wrap<T>(hooks: T): T;
53
+ /**
54
+ * v0.9.0 (K9-010 / plan §5.3) — a checkpoint: the session reached a
55
+ * model turn (tool.execute.after fired), so the system prompt was
56
+ * assembled and `experimental.chat.system.transform` MUST have been
57
+ * offered. Deduped per session: 20 tool calls in one session count
58
+ * exactly one expectation.
59
+ */
60
+ expect(hook: HookName, sessionID: string): void;
61
+ /**
62
+ * Per-hook liveness verdicts in canonical HOOK_NAMES order.
63
+ * A hook that fired once is live forever; dead requires both zero fires
64
+ * and `expectedCount >= threshold`; everything else is unknown — and
65
+ * unknown is never rounded to healthy (D9-09).
66
+ */
67
+ report(): HookReport[];
68
+ /** Persists every counter row. Called on the metrics.flush() cadence. */
69
+ flush(): void;
70
+ private makeWrapper;
71
+ /**
72
+ * v1.0.0 (K10-013 / plan §5.3) — records the `dispose` fire and
73
+ * flushes immediately, from inside the dispose delegate: this is the
74
+ * last write of the process, and nothing after it can persist.
75
+ */
76
+ recordDispose(): void;
77
+ private recordSuccess;
78
+ private recordError;
79
+ private loadFromDb;
80
+ }
81
+ export type HostVerdict = "healthy" | "degraded" | "unknown";
82
+ export interface HostVerdictResult {
83
+ verdict: HostVerdict;
84
+ reason: string;
85
+ }
86
+ export declare function reduceVerdict(reports: readonly HookReport[]): HostVerdictResult;
87
+ export {};