@jmtrin/opencode-kevin 1.2.0 → 1.3.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 +4 -3
- package/dist/plugin/index.js +122 -113
- package/dist/plugin/native.d.ts +2 -2
- package/dist/plugin/native.js +2 -2
- package/package.json +56 -68
- package/LICENSE +0 -21
- package/README.md +0 -605
- package/dist/migrations/001_initial.sql +0 -92
- package/dist/migrations/002_indexes.sql +0 -14
- package/dist/migrations/003_v02_signal.sql +0 -58
- package/dist/migrations/004_v03_knowledge.sql +0 -138
- package/dist/migrations/005_v04_signal.sql +0 -57
- package/dist/migrations/006_v05_glassbox.sql +0 -118
- package/dist/migrations/007_v06_pull.sql +0 -145
- package/dist/migrations/008_v07_truth.sql +0 -124
- package/dist/migrations/009_v08_team.sql +0 -100
- package/dist/migrations/010_v09_native.sql +0 -78
- package/dist/migrations/011_v10_proven.sql +0 -69
- package/dist/migrations/012_v11_drift.sql +0 -24
- package/dist/plugin/Archiver.d.ts +0 -42
- package/dist/plugin/Archiver.js +0 -83
- package/dist/plugin/ArtifactWriter.d.ts +0 -93
- package/dist/plugin/ArtifactWriter.js +0 -291
- package/dist/plugin/CausalChain.d.ts +0 -22
- package/dist/plugin/CausalChain.js +0 -197
- package/dist/plugin/ChatBridge.d.ts +0 -41
- package/dist/plugin/ChatBridge.js +0 -103
- package/dist/plugin/ConflictDetector.d.ts +0 -35
- package/dist/plugin/ConflictDetector.js +0 -260
- package/dist/plugin/ContextInjector.d.ts +0 -222
- package/dist/plugin/ContextInjector.js +0 -533
- package/dist/plugin/ConventionMiner.d.ts +0 -35
- package/dist/plugin/ConventionMiner.js +0 -242
- package/dist/plugin/Curator.d.ts +0 -116
- package/dist/plugin/Curator.js +0 -324
- package/dist/plugin/DashboardHtml.d.ts +0 -5
- package/dist/plugin/DashboardHtml.js +0 -180
- package/dist/plugin/Feedback.d.ts +0 -67
- package/dist/plugin/Feedback.js +0 -120
- package/dist/plugin/HookLiveness.d.ts +0 -88
- package/dist/plugin/HookLiveness.js +0 -322
- package/dist/plugin/InjectionLedger.d.ts +0 -98
- package/dist/plugin/InjectionLedger.js +0 -295
- package/dist/plugin/LessonFixer.d.ts +0 -44
- package/dist/plugin/LessonFixer.js +0 -45
- package/dist/plugin/Materializer.d.ts +0 -83
- package/dist/plugin/Materializer.js +0 -181
- package/dist/plugin/MemoryService.d.ts +0 -360
- package/dist/plugin/MemoryService.js +0 -1242
- package/dist/plugin/Migrate.d.ts +0 -17
- package/dist/plugin/Migrate.js +0 -271
- package/dist/plugin/PatternMiner.d.ts +0 -49
- package/dist/plugin/PatternMiner.js +0 -132
- package/dist/plugin/QualityGate.d.ts +0 -124
- package/dist/plugin/QualityGate.js +0 -119
- package/dist/plugin/Reflector.d.ts +0 -91
- package/dist/plugin/Reflector.js +0 -307
- package/dist/plugin/RepoIdentity.d.ts +0 -124
- package/dist/plugin/RepoIdentity.js +0 -300
- package/dist/plugin/RepoTruth.d.ts +0 -80
- package/dist/plugin/RepoTruth.js +0 -599
- package/dist/plugin/Retrospective.d.ts +0 -17
- package/dist/plugin/Retrospective.js +0 -216
- package/dist/plugin/SharedLayer.d.ts +0 -159
- package/dist/plugin/SharedLayer.js +0 -462
- package/dist/plugin/Store.d.ts +0 -29
- package/dist/plugin/Store.js +0 -50
- package/dist/plugin/ToolCallObserver.d.ts +0 -32
- package/dist/plugin/ToolCallObserver.js +0 -201
- package/dist/plugin/TuiActions.d.ts +0 -43
- package/dist/plugin/TuiActions.js +0 -181
- package/dist/plugin/TuiSnapshots.d.ts +0 -24
- package/dist/plugin/TuiSnapshots.js +0 -158
- package/dist/plugin/columns.d.ts +0 -11
- package/dist/plugin/columns.js +0 -54
- package/dist/plugin/confidence.d.ts +0 -8
- package/dist/plugin/confidence.js +0 -34
- package/dist/plugin/contract.d.ts +0 -54
- package/dist/plugin/contract.js +0 -386
- package/dist/plugin/diff.d.ts +0 -8
- package/dist/plugin/diff.js +0 -182
- package/dist/plugin/escape.d.ts +0 -39
- package/dist/plugin/escape.js +0 -60
- package/dist/plugin/fingerprint.d.ts +0 -27
- package/dist/plugin/fingerprint.js +0 -73
- package/dist/plugin/inferability.d.ts +0 -32
- package/dist/plugin/inferability.js +0 -88
- package/dist/plugin/kevin_approve.d.ts +0 -34
- package/dist/plugin/kevin_approve.js +0 -52
- package/dist/plugin/kevin_audit.d.ts +0 -229
- package/dist/plugin/kevin_audit.js +0 -631
- package/dist/plugin/kevin_bench.d.ts +0 -10
- package/dist/plugin/kevin_bench.js +0 -82
- package/dist/plugin/kevin_conflicts.d.ts +0 -9
- package/dist/plugin/kevin_conflicts.js +0 -50
- package/dist/plugin/kevin_contract.d.ts +0 -8
- package/dist/plugin/kevin_contract.js +0 -59
- package/dist/plugin/kevin_doctor.d.ts +0 -62
- package/dist/plugin/kevin_doctor.js +0 -196
- package/dist/plugin/kevin_facts.d.ts +0 -42
- package/dist/plugin/kevin_facts.js +0 -36
- package/dist/plugin/kevin_forget.d.ts +0 -33
- package/dist/plugin/kevin_forget.js +0 -260
- package/dist/plugin/kevin_native.d.ts +0 -29
- package/dist/plugin/kevin_native.js +0 -79
- package/dist/plugin/kevin_propose.d.ts +0 -23
- package/dist/plugin/kevin_propose.js +0 -14
- package/dist/plugin/kevin_publish.d.ts +0 -38
- package/dist/plugin/kevin_publish.js +0 -18
- package/dist/plugin/kevin_why.d.ts +0 -23
- package/dist/plugin/kevin_why.js +0 -111
- package/dist/plugin/memory-format.d.ts +0 -20
- package/dist/plugin/memory-format.js +0 -59
- package/dist/plugin/metrics.d.ts +0 -97
- package/dist/plugin/metrics.js +0 -288
- package/dist/plugin/okf-export.d.ts +0 -3
- package/dist/plugin/okf-export.js +0 -134
- package/dist/plugin/okf-import.d.ts +0 -76
- package/dist/plugin/okf-import.js +0 -271
- package/dist/plugin/okf.d.ts +0 -107
- package/dist/plugin/okf.js +0 -303
- package/dist/plugin/perf.d.ts +0 -35
- package/dist/plugin/perf.js +0 -174
- package/dist/plugin/query-tokenizer.d.ts +0 -13
- package/dist/plugin/query-tokenizer.js +0 -133
- package/dist/plugin/redact.d.ts +0 -2
- package/dist/plugin/redact.js +0 -18
- package/dist/plugin/replay-types.d.ts +0 -69
- package/dist/plugin/replay-types.js +0 -156
- package/dist/plugin/replay.d.ts +0 -36
- package/dist/plugin/replay.js +0 -202
- package/dist/plugin/sqlite-adapter.d.ts +0 -12
- package/dist/plugin/sqlite-adapter.js +0 -81
- package/dist/plugin/time-ms.d.ts +0 -1
- package/dist/plugin/time-ms.js +0 -16
- package/dist/plugin/tui-types.d.ts +0 -59
- package/dist/plugin/tui-types.js +0 -4
- package/dist/plugin/tui.d.ts +0 -18
- package/dist/plugin/tui.js +0 -198
- package/dist/plugin/uuid.d.ts +0 -1
- package/dist/plugin/uuid.js +0 -50
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
// v1.2.0 (K12-017 / plan §4.4 R2, D12-10) — static dashboard generator.
|
|
2
|
-
// Single self-contained file: inline CSS/JS, snapshot data embedded as const DATA.
|
|
3
|
-
// Zero network: no fetch, no XHR, no WebSocket, no external asset.
|
|
4
|
-
import { createHash } from "node:crypto";
|
|
5
|
-
import { mkdirSync, renameSync, writeFileSync } from "node:fs";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
const CAP_BYTES = 512 * 1024;
|
|
8
|
-
function byteLen(s) {
|
|
9
|
-
return Buffer.byteLength(s, "utf8");
|
|
10
|
-
}
|
|
11
|
-
// HTML escaping — mirrors escapeInjectedText discipline (C-09 family).
|
|
12
|
-
export function escapeHtml(text) {
|
|
13
|
-
return text
|
|
14
|
-
.replace(/&/g, "&")
|
|
15
|
-
.replace(/</g, "<")
|
|
16
|
-
.replace(/>/g, ">")
|
|
17
|
-
.replace(/"/g, """);
|
|
18
|
-
}
|
|
19
|
-
export function proposalToken(proposalId, proposedText) {
|
|
20
|
-
return createHash("sha256")
|
|
21
|
-
.update(`${proposalId}\0${proposedText}`, "utf8")
|
|
22
|
-
.digest("hex")
|
|
23
|
-
.slice(0, 16);
|
|
24
|
-
}
|
|
25
|
-
function truncateForHtml(views, cap) {
|
|
26
|
-
// Estimate html size; if over cap, truncate diffs progressively.
|
|
27
|
-
let html = renderDashboardInner(views, false);
|
|
28
|
-
if (byteLen(html) <= cap)
|
|
29
|
-
return views;
|
|
30
|
-
// First pass: cap diffs to 2000 chars
|
|
31
|
-
let truncated = {
|
|
32
|
-
...views,
|
|
33
|
-
proposals: views.proposals.map((p) => {
|
|
34
|
-
if (p.diff.length <= 2000)
|
|
35
|
-
return p;
|
|
36
|
-
return {
|
|
37
|
-
...p,
|
|
38
|
-
diff: `${p.diff.slice(0, 2000)}\n…[truncated]`,
|
|
39
|
-
truncated: true,
|
|
40
|
-
};
|
|
41
|
-
}),
|
|
42
|
-
};
|
|
43
|
-
html = renderDashboardInner(truncated, false);
|
|
44
|
-
if (byteLen(html) <= cap)
|
|
45
|
-
return truncated;
|
|
46
|
-
// Second: 800 chars
|
|
47
|
-
truncated = {
|
|
48
|
-
...views,
|
|
49
|
-
proposals: views.proposals.map((p) => ({
|
|
50
|
-
...p,
|
|
51
|
-
diff: p.diff.length > 800 ? `${p.diff.slice(0, 800)}\n…[truncated]` : p.diff,
|
|
52
|
-
truncated: p.diff.length > 800 ? true : p.truncated,
|
|
53
|
-
})),
|
|
54
|
-
conflicts: views.conflicts.map((c) => ({
|
|
55
|
-
...c,
|
|
56
|
-
a_summary: c.a_summary.slice(0, 200),
|
|
57
|
-
b_summary: c.b_summary.slice(0, 200),
|
|
58
|
-
})),
|
|
59
|
-
};
|
|
60
|
-
html = renderDashboardInner(truncated, false);
|
|
61
|
-
if (byteLen(html) <= cap)
|
|
62
|
-
return truncated;
|
|
63
|
-
// Third: drop diffs to 400 chars
|
|
64
|
-
truncated = {
|
|
65
|
-
...views,
|
|
66
|
-
proposals: views.proposals.map((p) => ({
|
|
67
|
-
...p,
|
|
68
|
-
diff: `${p.diff.slice(0, 400)}\n…[truncated]`,
|
|
69
|
-
truncated: true,
|
|
70
|
-
})),
|
|
71
|
-
};
|
|
72
|
-
return truncated;
|
|
73
|
-
}
|
|
74
|
-
function renderDashboardInner(views, _withTruncation) {
|
|
75
|
-
const css = "*{box-sizing:border-box}body{font-family:ui-monospace,monospace;margin:0;padding:16px;background:#0f1115;color:#e6e6e6}a{color:#8ab4ff}header{border-bottom:1px solid #2a2e39;padding-bottom:12px;margin-bottom:16px}h1{margin:0;font-size:20px}h2{font-size:16px;margin:24px 0 8px;border-bottom:1px solid #222;padding-bottom:4px}.card{border:1px solid #2a2e39;border-radius:8px;padding:12px;margin:8px 0;background:#151821}.muted{color:#9aa0b2;font-size:12px}.badge{display:inline-block;padding:2px 6px;border-radius:999px;font-size:11px;border:1px solid #2a2e39}.badge-healthy{background:#12331a;color:#8ef0a0}.badge-degraded{background:#331a1a;color:#f0a0a0}.badge-unknown{background:#2a2a33;color:#c0c0d0}pre{white-space:pre-wrap;word-break:break-word;background:#0b0d12;padding:8px;border-radius:6px;overflow:auto;max-height:320px;font-size:12px}table{width:100%;border-collapse:collapse;font-size:12px}th,td{border:1px solid #2a2e39;padding:4px 6px;text-align:left}button{cursor:pointer;background:#1f2330;color:#e6e6e6;border:1px solid #2a2e39;border-radius:6px;padding:4px 8px;font-size:12px}button:hover{background:#2a3045}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px}.cols{display:grid;grid-template-columns:1fr 1fr;gap:12px}.count{font-size:11px;color:#9aa0b2}";
|
|
76
|
-
// Build proposals HTML server-side
|
|
77
|
-
const proposalsHtml = views.proposals.length === 0
|
|
78
|
-
? `<div class="muted">No pending proposals.</div>`
|
|
79
|
-
: views.proposals
|
|
80
|
-
.map((p) => {
|
|
81
|
-
const token = p.token ?? proposalToken(p.id, p.diff);
|
|
82
|
-
const approveCmd = `/kevin-approve ${p.id} ${token}`;
|
|
83
|
-
const rejectCmd = `/kevin-reject ${p.id} ${token}`;
|
|
84
|
-
return `<div class="card">
|
|
85
|
-
<div><strong>${escapeHtml(p.kind)}</strong> <span class="muted">${escapeHtml(p.id)}</span> <span class="badge">${escapeHtml(p.target_path)}</span> <span class="muted">${escapeHtml(p.created_at)}</span>${p.truncated ? ` <span class="badge">truncated</span>` : ""}</div>
|
|
86
|
-
<div class="muted">memories: ${escapeHtml(p.memory_ids.join(", "))}</div>
|
|
87
|
-
<pre>${escapeHtml(p.diff)}</pre>
|
|
88
|
-
<div style="display:flex;gap:8px;margin-top:8px">
|
|
89
|
-
<button data-copy="${escapeHtml(approveCmd)}" onclick="copyCmd(this)">Copy approve</button>
|
|
90
|
-
<button data-copy="${escapeHtml(rejectCmd)}" onclick="copyCmd(this)">Copy reject</button>
|
|
91
|
-
</div>
|
|
92
|
-
<div class="muted copy-hint"></div>
|
|
93
|
-
</div>`;
|
|
94
|
-
})
|
|
95
|
-
.join("\n");
|
|
96
|
-
const conflictsHtml = views.conflicts.length === 0
|
|
97
|
-
? `<div class="muted">No open conflicts.</div>`
|
|
98
|
-
: views.conflicts
|
|
99
|
-
.map((c) => {
|
|
100
|
-
const ackCmd = `/kevin-ack ${c.id}`;
|
|
101
|
-
return `<div class="card">
|
|
102
|
-
<div><strong>${escapeHtml(c.kind)}</strong> <span class="muted">${escapeHtml(c.id)}</span> <span class="muted">${escapeHtml(c.opened_at)}</span></div>
|
|
103
|
-
<div class="cols"><div><div class="muted">A</div><pre>${escapeHtml(c.a_summary)}</pre></div><div><div class="muted">B</div><pre>${escapeHtml(c.b_summary)}</pre></div></div>
|
|
104
|
-
<div style="margin-top:8px"><button data-copy="${escapeHtml(ackCmd)}" onclick="copyCmd(this)">Copy ack</button> <span class="muted copy-hint"></span></div>
|
|
105
|
-
</div>`;
|
|
106
|
-
})
|
|
107
|
-
.join("\n");
|
|
108
|
-
const verdictClass = views.health.verdict === "healthy"
|
|
109
|
-
? "badge-healthy"
|
|
110
|
-
: views.health.verdict === "degraded"
|
|
111
|
-
? "badge-degraded"
|
|
112
|
-
: "badge-unknown";
|
|
113
|
-
const hooksRows = views.health.hooks.length === 0
|
|
114
|
-
? `<tr><td colspan="4" class="muted">No hooks</td></tr>`
|
|
115
|
-
: views.health.hooks
|
|
116
|
-
.map((h) => `<tr><td>${escapeHtml(h.hook)}</td><td>${escapeHtml(h.state)}</td><td>${h.fire_count}</td><td>${h.expected_count}</td></tr>`)
|
|
117
|
-
.join("\n");
|
|
118
|
-
const perfRows = views.health.perf.length === 0
|
|
119
|
-
? `<tr><td colspan="4" class="muted">No perf data</td></tr>`
|
|
120
|
-
: views.health.perf
|
|
121
|
-
.map((p) => `<tr><td>${escapeHtml(p.scope)}</td><td>${p.p95}</td><td>${p.budget_p95}</td><td>${p.within_budget ? "yes" : "no"}</td></tr>`)
|
|
122
|
-
.join("\n");
|
|
123
|
-
const countersHtml = Object.keys(views.health.counters).length === 0
|
|
124
|
-
? `<div class="muted">No counters</div>`
|
|
125
|
-
: `<div class="grid">${Object.entries(views.health.counters)
|
|
126
|
-
.map(([k, v]) => `<div class="card"><div class="muted">${escapeHtml(k)}</div><div><strong>${v}</strong></div></div>`)
|
|
127
|
-
.join("\n")}</div>`;
|
|
128
|
-
// Embedded DATA — escape every "<" to \u003c so hostile "<script>" can never appear verbatim inside the <script> block.
|
|
129
|
-
const dataJson = JSON.stringify(views).replace(/</g, "\\u003c");
|
|
130
|
-
const js = `function copyCmd(btn){var cmd=btn.getAttribute('data-copy');var hint=btn.parentElement.nextElementSibling;function done(t){if(hint)hint.textContent=t;setTimeout(function(){if(hint)hint.textContent='';},2500);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(cmd).then(function(){done('Copied: '+cmd+' — paste into your opencode session');},function(){fallback(cmd,done);});}else{fallback(cmd,done);}}function fallback(cmd,done){var ta=document.createElement('textarea');ta.value=cmd;ta.style.position='fixed';ta.style.opacity='0';document.body.appendChild(ta);ta.select();try{document.execCommand('copy');done('Copied: '+cmd+' — paste into your opencode session');}catch(e){done('Copy failed — manually copy: '+cmd);}document.body.removeChild(ta);}`;
|
|
131
|
-
return `<!doctype html>
|
|
132
|
-
<html lang="en">
|
|
133
|
-
<head>
|
|
134
|
-
<meta charset="utf-8">
|
|
135
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
136
|
-
<title>Kevin — Dashboard</title>
|
|
137
|
-
<style>${css}</style>
|
|
138
|
-
</head>
|
|
139
|
-
<body>
|
|
140
|
-
<header>
|
|
141
|
-
<h1>Kevin — Surface Dashboard</h1>
|
|
142
|
-
<div class="muted">Generated at ${escapeHtml(views.generatedAt)} · <span class="badge ${verdictClass}">${escapeHtml(views.health.verdict)}</span> ${escapeHtml(views.health.reason)} · contract ${escapeHtml(views.health.contract_digest)}</div>
|
|
143
|
-
<div class="muted">Proposals ${views.proposals.length} · Conflicts ${views.conflicts.length} · Paste copied <code>/kevin-*</code> commands into your opencode session (Desktop or CLI).</div>
|
|
144
|
-
</header>
|
|
145
|
-
|
|
146
|
-
<section>
|
|
147
|
-
<h2>Proposals</h2>
|
|
148
|
-
${proposalsHtml}
|
|
149
|
-
</section>
|
|
150
|
-
|
|
151
|
-
<section>
|
|
152
|
-
<h2>Conflicts</h2>
|
|
153
|
-
${conflictsHtml}
|
|
154
|
-
</section>
|
|
155
|
-
|
|
156
|
-
<section>
|
|
157
|
-
<h2>Health</h2>
|
|
158
|
-
<table><thead><tr><th>hook</th><th>state</th><th>fires</th><th>expected</th></tr></thead><tbody>${hooksRows}</tbody></table>
|
|
159
|
-
<table style="margin-top:12px"><thead><tr><th>scope</th><th>p95 ms</th><th>budget p95</th><th>within</th></tr></thead><tbody>${perfRows}</tbody></table>
|
|
160
|
-
<div style="margin-top:12px">${countersHtml}</div>
|
|
161
|
-
</section>
|
|
162
|
-
|
|
163
|
-
<script>const DATA=${dataJson};${js}</script>
|
|
164
|
-
</body>
|
|
165
|
-
</html>`;
|
|
166
|
-
}
|
|
167
|
-
export function renderDashboard(views) {
|
|
168
|
-
const capped = truncateForHtml(views, CAP_BYTES);
|
|
169
|
-
return renderDashboardInner(capped, true);
|
|
170
|
-
}
|
|
171
|
-
export function writeDashboard(root, views) {
|
|
172
|
-
const dir = join(root, "tui");
|
|
173
|
-
mkdirSync(dir, { recursive: true });
|
|
174
|
-
const html = renderDashboard(views);
|
|
175
|
-
const target = join(dir, "dashboard.html");
|
|
176
|
-
const tmp = `${target}.tmp`;
|
|
177
|
-
writeFileSync(tmp, html, "utf8");
|
|
178
|
-
renameSync(tmp, target);
|
|
179
|
-
return target;
|
|
180
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import type { Store } from "./Store.js";
|
|
2
|
-
import type { Metrics } from "./metrics.js";
|
|
3
|
-
/**
|
|
4
|
-
* v0.5.0 Feedback (K5-009 / plan §5.3, D5-02).
|
|
5
|
-
*
|
|
6
|
-
* The human-judgement half of the glassbox: the agent can report whether a
|
|
7
|
-
* memory was useful, wrong, outdated, or should be ignored. Verdicts are
|
|
8
|
-
* stored in `memory_feedback` (migration 006_v05_glassbox.sql) and folded
|
|
9
|
-
* into the memory's `feedback_positive` / `feedback_negative` counters —
|
|
10
|
-
* kept SEPARATE from `evidence_count` / `recurrence_count` by design: human
|
|
11
|
-
* judgement is evidence about the memory, causal counters are evidence
|
|
12
|
-
* about the world (the confidence-poisoning defect closed in v0.4.0).
|
|
13
|
-
*
|
|
14
|
-
* D5-07 — the `ignore` verdict is a hard lifecycle action, not a soft
|
|
15
|
-
* signal: the memory is stamped `ignored = 1`, which excludes it from
|
|
16
|
-
* retrieval (K5-008) and from the quality gate (K5-007).
|
|
17
|
-
*
|
|
18
|
-
* Schema:
|
|
19
|
-
* memory_feedback(id PK, memory_id, verdict, session_id, note, created_at)
|
|
20
|
-
* memories(feedback_positive, feedback_negative, ignored)
|
|
21
|
-
*/
|
|
22
|
-
export type FeedbackVerdict = "useful" | "wrong" | "outdated" | "ignore";
|
|
23
|
-
export interface FeedbackRecordInput {
|
|
24
|
-
memoryId: string;
|
|
25
|
-
verdict: FeedbackVerdict;
|
|
26
|
-
sessionId?: string | null;
|
|
27
|
-
note?: string | null;
|
|
28
|
-
}
|
|
29
|
-
export interface FeedbackRow {
|
|
30
|
-
id: string;
|
|
31
|
-
memoryId: string;
|
|
32
|
-
verdict: FeedbackVerdict;
|
|
33
|
-
sessionId: string | null;
|
|
34
|
-
note: string | null;
|
|
35
|
-
createdAt: string;
|
|
36
|
-
}
|
|
37
|
-
export interface FeedbackCounts {
|
|
38
|
-
positive: number;
|
|
39
|
-
negative: number;
|
|
40
|
-
}
|
|
41
|
-
export declare class Feedback {
|
|
42
|
-
private readonly store;
|
|
43
|
-
private readonly metrics;
|
|
44
|
-
private readonly now;
|
|
45
|
-
constructor(store: Store, metrics?: Metrics | null, now?: () => Date);
|
|
46
|
-
/**
|
|
47
|
-
* Records one human verdict and folds it into the memory's counters.
|
|
48
|
-
* Counters are RECOMPUTED from the table (never incremented) so rows
|
|
49
|
-
* deleted in tests or by hand cannot drift them.
|
|
50
|
-
*
|
|
51
|
-
* The `ignore` verdict also stamps `memories.ignored = 1` (D5-07).
|
|
52
|
-
* Returns the feedback row id.
|
|
53
|
-
*/
|
|
54
|
-
record(input: FeedbackRecordInput): string;
|
|
55
|
-
/** Human-judgement counters for one memory (from the row, not the table). */
|
|
56
|
-
countsFor(memoryId: string): FeedbackCounts;
|
|
57
|
-
/**
|
|
58
|
-
* Raw verdict history, newest first. Used by `kevin_feedback` (K5-011)
|
|
59
|
-
* and by the audit tool (K5-016).
|
|
60
|
-
*/
|
|
61
|
-
list(memoryId?: string, limit?: number): FeedbackRow[];
|
|
62
|
-
/**
|
|
63
|
-
* Recompute a memory's positive/negative counters straight from the
|
|
64
|
-
* verdict table. Kept private — `record` is the only mutation path.
|
|
65
|
-
*/
|
|
66
|
-
private recomputeCounters;
|
|
67
|
-
}
|
package/dist/plugin/Feedback.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { hasFeedbackTable } from "./columns.js";
|
|
2
|
-
import { uuidv7 } from "./uuid.js";
|
|
3
|
-
const POSITIVE_VERDICTS = ["useful"];
|
|
4
|
-
const NEGATIVE_VERDICTS = [
|
|
5
|
-
"wrong",
|
|
6
|
-
"outdated",
|
|
7
|
-
"ignore",
|
|
8
|
-
];
|
|
9
|
-
// v1.1.0 (K11-011) — table probe delegates to columns registry
|
|
10
|
-
export class Feedback {
|
|
11
|
-
store;
|
|
12
|
-
metrics;
|
|
13
|
-
now;
|
|
14
|
-
constructor(store, metrics, now = () => new Date()) {
|
|
15
|
-
this.store = store;
|
|
16
|
-
this.metrics = metrics ?? null;
|
|
17
|
-
this.now = now;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Records one human verdict and folds it into the memory's counters.
|
|
21
|
-
* Counters are RECOMPUTED from the table (never incremented) so rows
|
|
22
|
-
* deleted in tests or by hand cannot drift them.
|
|
23
|
-
*
|
|
24
|
-
* The `ignore` verdict also stamps `memories.ignored = 1` (D5-07).
|
|
25
|
-
* Returns the feedback row id.
|
|
26
|
-
*/
|
|
27
|
-
record(input) {
|
|
28
|
-
if (!hasFeedbackTable(this.store)) {
|
|
29
|
-
throw new Error("kevin_feedback requires migration 006_v05_glassbox.sql (missing table memory_feedback)");
|
|
30
|
-
}
|
|
31
|
-
const id = uuidv7();
|
|
32
|
-
this.store
|
|
33
|
-
.prepare(`INSERT INTO memory_feedback
|
|
34
|
-
(id, memory_id, verdict, session_id, note)
|
|
35
|
-
VALUES (?, ?, ?, ?, ?)`)
|
|
36
|
-
.run(id, input.memoryId, input.verdict, input.sessionId ?? null, input.note ?? null);
|
|
37
|
-
this.recomputeCounters(input.memoryId);
|
|
38
|
-
if (input.verdict === "ignore") {
|
|
39
|
-
this.store
|
|
40
|
-
.prepare("UPDATE memories SET ignored = 1 WHERE id = ?")
|
|
41
|
-
.run(input.memoryId);
|
|
42
|
-
}
|
|
43
|
-
// v0.5.0 (K5-023 / plan §5.3, D5-06) — lifecycle action of a
|
|
44
|
-
// negative verdict. `wrong` is an opinion and deserves a second
|
|
45
|
-
// opinion: it demotes only at feedback_negative >= 2. `outdated`
|
|
46
|
-
// is a self-verifying claim about the world and acts at once.
|
|
47
|
-
// `useful` confirms the memory and refreshes its verification.
|
|
48
|
-
if (input.verdict === "outdated") {
|
|
49
|
-
this.store
|
|
50
|
-
.prepare("UPDATE memories SET status = 'stale' WHERE id = ?")
|
|
51
|
-
.run(input.memoryId);
|
|
52
|
-
}
|
|
53
|
-
else if (input.verdict === "wrong") {
|
|
54
|
-
const counts = this.countsFor(input.memoryId);
|
|
55
|
-
if (counts.negative >= 2) {
|
|
56
|
-
this.store
|
|
57
|
-
.prepare("UPDATE memories SET status = 'stale' WHERE id = ?")
|
|
58
|
-
.run(input.memoryId);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
else if (input.verdict === "useful") {
|
|
62
|
-
this.store
|
|
63
|
-
.prepare("UPDATE memories SET last_verified_at = datetime('now') WHERE id = ?")
|
|
64
|
-
.run(input.memoryId);
|
|
65
|
-
}
|
|
66
|
-
if (POSITIVE_VERDICTS.includes(input.verdict)) {
|
|
67
|
-
this.metrics?.incr("feedback_positive_total", 1);
|
|
68
|
-
}
|
|
69
|
-
else if (NEGATIVE_VERDICTS.includes(input.verdict)) {
|
|
70
|
-
this.metrics?.incr("feedback_negative_total", 1);
|
|
71
|
-
}
|
|
72
|
-
return id;
|
|
73
|
-
}
|
|
74
|
-
/** Human-judgement counters for one memory (from the row, not the table). */
|
|
75
|
-
countsFor(memoryId) {
|
|
76
|
-
const row = this.store
|
|
77
|
-
.prepare("SELECT feedback_positive, feedback_negative FROM memories WHERE id = ?")
|
|
78
|
-
.get(memoryId);
|
|
79
|
-
if (!row)
|
|
80
|
-
return { positive: 0, negative: 0 };
|
|
81
|
-
return { positive: row.feedback_positive, negative: row.feedback_negative };
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Raw verdict history, newest first. Used by `kevin_feedback` (K5-011)
|
|
85
|
-
* and by the audit tool (K5-016).
|
|
86
|
-
*/
|
|
87
|
-
list(memoryId, limit = 50) {
|
|
88
|
-
const rows = this.store
|
|
89
|
-
.prepare(`SELECT id, memory_id, verdict, session_id, note, created_at
|
|
90
|
-
FROM memory_feedback
|
|
91
|
-
${memoryId ? "WHERE memory_id = ?" : ""}
|
|
92
|
-
ORDER BY created_at DESC, rowid DESC
|
|
93
|
-
LIMIT ?`)
|
|
94
|
-
.all(...(memoryId ? [memoryId, limit] : [limit]));
|
|
95
|
-
return rows.map((r) => ({
|
|
96
|
-
id: r.id,
|
|
97
|
-
memoryId: r.memory_id,
|
|
98
|
-
verdict: r.verdict,
|
|
99
|
-
sessionId: r.session_id,
|
|
100
|
-
note: r.note,
|
|
101
|
-
createdAt: r.created_at,
|
|
102
|
-
}));
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Recompute a memory's positive/negative counters straight from the
|
|
106
|
-
* verdict table. Kept private — `record` is the only mutation path.
|
|
107
|
-
*/
|
|
108
|
-
recomputeCounters(memoryId) {
|
|
109
|
-
this.store
|
|
110
|
-
.prepare(`UPDATE memories SET
|
|
111
|
-
feedback_positive = (
|
|
112
|
-
SELECT COUNT(*) FROM memory_feedback
|
|
113
|
-
WHERE memory_id = ? AND verdict = 'useful'),
|
|
114
|
-
feedback_negative = (
|
|
115
|
-
SELECT COUNT(*) FROM memory_feedback
|
|
116
|
-
WHERE memory_id = ? AND verdict IN ('wrong','outdated','ignore'))
|
|
117
|
-
WHERE id = ?`)
|
|
118
|
-
.run(memoryId, memoryId, memoryId);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
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
|
-
excessArityCount: number;
|
|
41
|
-
private readonly counters;
|
|
42
|
-
private readonly seenSessions;
|
|
43
|
-
private readonly suppressedSessions;
|
|
44
|
-
private readonly threshold;
|
|
45
|
-
private readonly pluginVersion;
|
|
46
|
-
constructor(store: Store, options: HookLivenessOptions);
|
|
47
|
-
/**
|
|
48
|
-
* Returns a new object with the same keys; function values are replaced
|
|
49
|
-
* by delegating wrappers, non-function values (the tool map) pass by
|
|
50
|
-
* reference. When liveness is disabled, returns the argument unchanged —
|
|
51
|
-
* the same object, not a copy.
|
|
52
|
-
*/
|
|
53
|
-
wrap<T>(hooks: T): T;
|
|
54
|
-
/**
|
|
55
|
-
* v0.9.0 (K9-010 / plan §5.3) — a checkpoint: the session reached a
|
|
56
|
-
* model turn (tool.execute.after fired), so the system prompt was
|
|
57
|
-
* assembled and `experimental.chat.system.transform` MUST have been
|
|
58
|
-
* offered. Deduped per session: 20 tool calls in one session count
|
|
59
|
-
* exactly one expectation.
|
|
60
|
-
*/
|
|
61
|
-
expect(hook: HookName, sessionID: string): void;
|
|
62
|
-
/**
|
|
63
|
-
* Per-hook liveness verdicts in canonical HOOK_NAMES order.
|
|
64
|
-
* A hook that fired once is live forever; dead requires both zero fires
|
|
65
|
-
* and `expectedCount >= threshold`; everything else is unknown — and
|
|
66
|
-
* unknown is never rounded to healthy (D9-09).
|
|
67
|
-
*/
|
|
68
|
-
report(): HookReport[];
|
|
69
|
-
/** Persists every counter row. Called on the metrics.flush() cadence. */
|
|
70
|
-
flush(): void;
|
|
71
|
-
private makeWrapper;
|
|
72
|
-
/**
|
|
73
|
-
* v1.0.0 (K10-013 / plan §5.3) — records the `dispose` fire and
|
|
74
|
-
* flushes immediately, from inside the dispose delegate: this is the
|
|
75
|
-
* last write of the process, and nothing after it can persist.
|
|
76
|
-
*/
|
|
77
|
-
recordDispose(): void;
|
|
78
|
-
private recordSuccess;
|
|
79
|
-
private recordError;
|
|
80
|
-
private loadFromDb;
|
|
81
|
-
}
|
|
82
|
-
export type HostVerdict = "healthy" | "degraded" | "unknown";
|
|
83
|
-
export interface HostVerdictResult {
|
|
84
|
-
verdict: HostVerdict;
|
|
85
|
-
reason: string;
|
|
86
|
-
}
|
|
87
|
-
export declare function reduceVerdict(reports: readonly HookReport[]): HostVerdictResult;
|
|
88
|
-
export {};
|