@jmtrin/opencode-kevin 0.4.0 → 0.6.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 +580 -360
- package/dist/migrations/006_v05_glassbox.sql +118 -0
- package/dist/migrations/007_v06_pull.sql +145 -0
- package/dist/plugin/Archiver.d.ts +42 -0
- package/dist/plugin/Archiver.js +99 -0
- package/dist/plugin/Archiver.js.map +1 -0
- package/dist/plugin/ArtifactWriter.d.ts +50 -0
- package/dist/plugin/ArtifactWriter.js +240 -0
- package/dist/plugin/ArtifactWriter.js.map +1 -0
- package/dist/plugin/ContextInjector.d.ts +87 -0
- package/dist/plugin/ContextInjector.js +212 -44
- package/dist/plugin/ContextInjector.js.map +1 -1
- package/dist/plugin/Curator.d.ts +96 -0
- package/dist/plugin/Curator.js +252 -0
- package/dist/plugin/Curator.js.map +1 -0
- package/dist/plugin/Feedback.d.ts +67 -0
- package/dist/plugin/Feedback.js +138 -0
- package/dist/plugin/Feedback.js.map +1 -0
- package/dist/plugin/InjectionLedger.d.ts +9 -2
- package/dist/plugin/InjectionLedger.js +63 -9
- package/dist/plugin/InjectionLedger.js.map +1 -1
- package/dist/plugin/Materializer.d.ts +59 -0
- package/dist/plugin/Materializer.js +237 -0
- package/dist/plugin/Materializer.js.map +1 -0
- package/dist/plugin/MemoryService.d.ts +38 -0
- package/dist/plugin/MemoryService.js +265 -30
- package/dist/plugin/MemoryService.js.map +1 -1
- package/dist/plugin/Migrate.js +41 -0
- package/dist/plugin/Migrate.js.map +1 -1
- package/dist/plugin/QualityGate.d.ts +53 -0
- package/dist/plugin/QualityGate.js +50 -8
- package/dist/plugin/QualityGate.js.map +1 -1
- package/dist/plugin/Retrospective.d.ts +1 -0
- package/dist/plugin/Retrospective.js +24 -1
- package/dist/plugin/Retrospective.js.map +1 -1
- package/dist/plugin/capabilities.d.ts +15 -0
- package/dist/plugin/capabilities.js +42 -0
- package/dist/plugin/capabilities.js.map +1 -0
- package/dist/plugin/confidence.d.ts +3 -1
- package/dist/plugin/confidence.js +14 -2
- package/dist/plugin/confidence.js.map +1 -1
- package/dist/plugin/diff.d.ts +8 -0
- package/dist/plugin/diff.js +183 -0
- package/dist/plugin/diff.js.map +1 -0
- package/dist/plugin/index.d.ts +3 -1
- package/dist/plugin/index.js +371 -0
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/inferability.d.ts +32 -0
- package/dist/plugin/inferability.js +89 -0
- package/dist/plugin/inferability.js.map +1 -0
- package/dist/plugin/kevin_approve.d.ts +34 -0
- package/dist/plugin/kevin_approve.js +50 -0
- package/dist/plugin/kevin_approve.js.map +1 -0
- package/dist/plugin/kevin_audit.d.ts +95 -0
- package/dist/plugin/kevin_audit.js +233 -0
- package/dist/plugin/kevin_audit.js.map +1 -0
- package/dist/plugin/kevin_propose.d.ts +23 -0
- package/dist/plugin/kevin_propose.js +15 -0
- package/dist/plugin/kevin_propose.js.map +1 -0
- package/dist/plugin/kevin_publish.d.ts +38 -0
- package/dist/plugin/kevin_publish.js +19 -0
- package/dist/plugin/kevin_publish.js.map +1 -0
- package/dist/plugin/kevin_why.js +23 -2
- package/dist/plugin/kevin_why.js.map +1 -1
- package/dist/plugin/metrics.d.ts +32 -1
- package/dist/plugin/metrics.js +86 -2
- package/dist/plugin/metrics.js.map +1 -1
- package/dist/plugin/replay-types.d.ts +327 -0
- package/dist/plugin/replay-types.js +65 -0
- package/dist/plugin/replay-types.js.map +1 -0
- package/dist/plugin/replay.d.ts +36 -0
- package/dist/plugin/replay.js +203 -0
- package/dist/plugin/replay.js.map +1 -0
- package/migrations/006_v05_glassbox.sql +118 -0
- package/migrations/007_v06_pull.sql +145 -0
- package/package.json +3 -2
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { closeSync, fsyncSync, openSync, readFileSync, renameSync, unlinkSync, writeSync, } from "node:fs";
|
|
3
|
+
import { unifiedDiff } from "./diff.js";
|
|
4
|
+
import { uuidv7 } from "./uuid.js";
|
|
5
|
+
// v0.6.0 (K6-005 / plan §5.1, D6-02) — the frozen marker contract. The exact
|
|
6
|
+
// byte sequences are load-bearing: README, v1.0.0 plan C-01 and the round-trip
|
|
7
|
+
// test all depend on them.
|
|
8
|
+
export const MARKER_BEGIN = "<!-- kevin:begin — curated by opencode-kevin, safe to edit -->";
|
|
9
|
+
export const MARKER_END = "<!-- kevin:end -->";
|
|
10
|
+
function sha256(text) {
|
|
11
|
+
return createHash("sha256").update(text, "utf8").digest("hex");
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Rule 5 (K6-008 / plan §5.1, D6-02) — the line-ending style comes from the
|
|
15
|
+
* FIRST line ending in the existing file: CRLF if it is CRLF, otherwise LF.
|
|
16
|
+
* A CRLF file whose last line lacks a terminator, and a mixed-ending file,
|
|
17
|
+
* are both resolved by the same first-ending rule, deterministically.
|
|
18
|
+
*/
|
|
19
|
+
function detectEol(text) {
|
|
20
|
+
const nl = text.indexOf("\n");
|
|
21
|
+
if (nl === -1)
|
|
22
|
+
return "\n";
|
|
23
|
+
return nl > 0 && text[nl - 1] === "\r" ? "\r\n" : "\n";
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Rule 5 (K6-008) — normalize the generated body into the file's line-ending
|
|
27
|
+
* style so a CRLF file stays CRLF everywhere, including inside the block.
|
|
28
|
+
*/
|
|
29
|
+
function normalizeEol(body, eol) {
|
|
30
|
+
return body.replace(/\r\n/g, "\n").replace(/\n/g, eol);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
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 (`&`, `<`, `>`, `&#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 `&lt;` on
|
|
38
|
+
* every regeneration (K6-009 / plan §5.1 rule 9).
|
|
39
|
+
*/
|
|
40
|
+
function escapeIdempotent(text) {
|
|
41
|
+
return text
|
|
42
|
+
.replace(/&(?!(amp|lt|gt|#\d+);)/g, "&")
|
|
43
|
+
.replace(/</g, "<")
|
|
44
|
+
.replace(/>/g, ">");
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
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).
|
|
57
|
+
*/
|
|
58
|
+
export function sanitizeArtifactBody(body) {
|
|
59
|
+
const escaped = escapeIdempotent(body);
|
|
60
|
+
const kept = escaped
|
|
61
|
+
.split("\n")
|
|
62
|
+
.filter((line) => !/kevin:begin|kevin:end/i.test(line))
|
|
63
|
+
.join("\n");
|
|
64
|
+
return kept.replace(/-->/g, "");
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* v0.6.0 (K6-005 / plan §5.1) — the single write path to disk (D6-01).
|
|
68
|
+
*
|
|
69
|
+
* `plan()` is pure: it reads the target file, locates the marker pair, splices
|
|
70
|
+
* the body between the markers and returns a {@link WritePlan}. It performs no
|
|
71
|
+
* writes — rule 1. `apply()` is implemented by K6-007; until then it is a stub
|
|
72
|
+
* that throws, so no caller can accidentally write before the audit trail
|
|
73
|
+
* exists.
|
|
74
|
+
*
|
|
75
|
+
* `projectId` is a constructor argument rather than a per-call argument so that
|
|
76
|
+
* every audit row is attributed without the call site having to remember.
|
|
77
|
+
*/
|
|
78
|
+
export class ArtifactWriter {
|
|
79
|
+
store;
|
|
80
|
+
projectId;
|
|
81
|
+
metrics;
|
|
82
|
+
constructor(store, projectId, metrics) {
|
|
83
|
+
this.store = store;
|
|
84
|
+
this.projectId = projectId;
|
|
85
|
+
this.metrics = metrics ?? null;
|
|
86
|
+
}
|
|
87
|
+
plan(path, body) {
|
|
88
|
+
let before;
|
|
89
|
+
try {
|
|
90
|
+
// Read as Buffer, not as utf8 text: readFileSync's text decoding
|
|
91
|
+
// strips a leading BOM, which would silently drop it on the next
|
|
92
|
+
// write. Buffer.toString keeps \uFEFF as a character of `before`.
|
|
93
|
+
before = readFileSync(path).toString("utf8");
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
97
|
+
before = "";
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const eol = detectEol(before);
|
|
104
|
+
// Rule 9 — sanitation happens in plan(), before hashing, so the hashes
|
|
105
|
+
// describe what was actually written.
|
|
106
|
+
const bodyEol = normalizeEol(sanitizeArtifactBody(body), eol);
|
|
107
|
+
const firstBegin = before.indexOf(MARKER_BEGIN);
|
|
108
|
+
const firstEnd = before.indexOf(MARKER_END);
|
|
109
|
+
let after;
|
|
110
|
+
let outcome = "written";
|
|
111
|
+
let reason;
|
|
112
|
+
if (firstBegin === -1 && firstEnd === -1) {
|
|
113
|
+
// Rule 2 — create: the block is appended at the end of the content,
|
|
114
|
+
// preceded by a blank line. For an empty file (missing file treated
|
|
115
|
+
// as "") the result is exactly: blank line, MARKER_BEGIN, body,
|
|
116
|
+
// MARKER_END, trailing newline.
|
|
117
|
+
const separator = before === "" ? eol : before.endsWith(eol) ? eol : eol + eol;
|
|
118
|
+
after =
|
|
119
|
+
before +
|
|
120
|
+
separator +
|
|
121
|
+
MARKER_BEGIN +
|
|
122
|
+
eol +
|
|
123
|
+
bodyEol +
|
|
124
|
+
eol +
|
|
125
|
+
MARKER_END +
|
|
126
|
+
eol;
|
|
127
|
+
}
|
|
128
|
+
else if (firstBegin === -1 || firstEnd === -1) {
|
|
129
|
+
// Rule 3 — exactly one marker present.
|
|
130
|
+
after = before;
|
|
131
|
+
outcome = "refused";
|
|
132
|
+
reason =
|
|
133
|
+
firstBegin === -1
|
|
134
|
+
? "kevin:end marker present without kevin:begin"
|
|
135
|
+
: "kevin:begin marker present without kevin:end";
|
|
136
|
+
}
|
|
137
|
+
else if (firstEnd < firstBegin) {
|
|
138
|
+
// Rule 3 — MARKER_END precedes MARKER_BEGIN.
|
|
139
|
+
after = before;
|
|
140
|
+
outcome = "refused";
|
|
141
|
+
reason = "kevin:end appears before kevin:begin";
|
|
142
|
+
}
|
|
143
|
+
else if (before.indexOf(MARKER_BEGIN, firstBegin + MARKER_BEGIN.length) !== -1 ||
|
|
144
|
+
before.indexOf(MARKER_END, firstEnd + MARKER_END.length) !== -1) {
|
|
145
|
+
// Rule 3 — more than one pair (any additional marker occurrence).
|
|
146
|
+
after = before;
|
|
147
|
+
outcome = "refused";
|
|
148
|
+
reason = "more than one marker pair present";
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// Rule 4 — bytes outside the marker pair are byte-identical. The
|
|
152
|
+
// block between the markers (begin marker, old block, end marker)
|
|
153
|
+
// is replaced by the regenerated block.
|
|
154
|
+
const blockEnd = firstEnd + MARKER_END.length;
|
|
155
|
+
after =
|
|
156
|
+
before.slice(0, firstBegin) +
|
|
157
|
+
MARKER_BEGIN +
|
|
158
|
+
eol +
|
|
159
|
+
bodyEol +
|
|
160
|
+
eol +
|
|
161
|
+
MARKER_END +
|
|
162
|
+
before.slice(blockEnd);
|
|
163
|
+
// Rule 6 — unchanged content is a noop, never a write.
|
|
164
|
+
outcome = after === before ? "noop" : "written";
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
path,
|
|
168
|
+
before,
|
|
169
|
+
after,
|
|
170
|
+
// v0.6.0 (K6-006 / plan §5.2, D6-05) — approval prompts show bytes,
|
|
171
|
+
// never prose. Identical inputs yield "".
|
|
172
|
+
diff: unifiedDiff(path, before, after),
|
|
173
|
+
outcome,
|
|
174
|
+
...(reason !== undefined ? { reason } : {}),
|
|
175
|
+
hashBefore: sha256(before),
|
|
176
|
+
hashAfter: sha256(after),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
// v0.6.0 (K6-007 / plan §5.1, rules 7–8) — atomic write + audit row.
|
|
180
|
+
apply(plan, proposalId) {
|
|
181
|
+
// Rule 8 — refusals and noops still leave an audit trail; a refusal
|
|
182
|
+
// that leaves no trace is indistinguishable from a write that never
|
|
183
|
+
// happened. Rule 6 — a noop creates no temp file and writes nothing.
|
|
184
|
+
if (plan.outcome === "noop" || plan.outcome === "refused") {
|
|
185
|
+
if (plan.outcome === "noop") {
|
|
186
|
+
this.metrics?.incr("artifact_writes_noop", 1);
|
|
187
|
+
}
|
|
188
|
+
this.audit(plan, proposalId);
|
|
189
|
+
return plan.outcome;
|
|
190
|
+
}
|
|
191
|
+
// Rule 7 — write to `<path>.kevin.tmp` in the same directory (same
|
|
192
|
+
// filesystem, so rename is atomic), fsync, close, then rename over the
|
|
193
|
+
// target. Never write the target path directly, never truncate-then-write.
|
|
194
|
+
const tmpPath = `${plan.path}.kevin.tmp`;
|
|
195
|
+
let fd;
|
|
196
|
+
try {
|
|
197
|
+
fd = openSync(tmpPath, "w");
|
|
198
|
+
writeSync(fd, plan.after, null, "utf8");
|
|
199
|
+
fsyncSync(fd);
|
|
200
|
+
closeSync(fd);
|
|
201
|
+
fd = undefined;
|
|
202
|
+
this.renameTemp(tmpPath, plan.path);
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
// Never leave .kevin.tmp litter next to the user's file.
|
|
206
|
+
if (fd !== undefined) {
|
|
207
|
+
try {
|
|
208
|
+
closeSync(fd);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// already closed or unusable; unlink below is the cleanup
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
unlinkSync(tmpPath);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// nothing to clean up
|
|
219
|
+
}
|
|
220
|
+
throw err;
|
|
221
|
+
}
|
|
222
|
+
this.metrics?.incr("artifact_writes_total", 1);
|
|
223
|
+
this.audit(plan, proposalId);
|
|
224
|
+
return "written";
|
|
225
|
+
}
|
|
226
|
+
// Fault-injection seam for the atomicity test: the rename is the point
|
|
227
|
+
// where a failure must leave the target untouched and the temp file gone.
|
|
228
|
+
renameTemp(tmpPath, target) {
|
|
229
|
+
renameSync(tmpPath, target);
|
|
230
|
+
}
|
|
231
|
+
audit(plan, proposalId) {
|
|
232
|
+
this.store
|
|
233
|
+
.prepare(`INSERT INTO artifact_writes
|
|
234
|
+
(id, proposal_id, project_id, path, bytes_before, bytes_after,
|
|
235
|
+
hash_before, hash_after, outcome, reason)
|
|
236
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
237
|
+
.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);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=ArtifactWriter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ArtifactWriter.js","sourceRoot":"","sources":["../../plugin/ArtifactWriter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACN,SAAS,EACT,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,UAAU,EACV,SAAS,GACT,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC,6EAA6E;AAC7E,+EAA+E;AAC/E,2BAA2B;AAC3B,MAAM,CAAC,MAAM,YAAY,GACxB,gEAAgE,CAAC;AAClE,MAAM,CAAC,MAAM,UAAU,GAAG,oBAAoB,CAAC;AAe/C,SAAS,MAAM,CAAC,IAAY;IAC3B,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC;AAED;;;;;GAKG;AACH,SAAS,SAAS,CAAC,IAAY;IAC9B,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,EAAE,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3B,OAAO,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AACxD,CAAC;AAED;;;GAGG;AACH,SAAS,YAAY,CAAC,IAAY,EAAE,GAAkB;IACrD,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACxD,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,gBAAgB,CAAC,IAAY;IACrC,OAAO,IAAI;SACT,OAAO,CAAC,yBAAyB,EAAE,OAAO,CAAC;SAC3C,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACzB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAChD,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,OAAO;SAClB,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;SACtD,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,cAAc;IACT,KAAK,CAAQ;IACb,SAAS,CAAS;IAClB,OAAO,CAAiB;IAEzC,YAAY,KAAY,EAAE,SAAiB,EAAE,OAAwB;QACpE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,OAAO,GAAG,OAAO,IAAI,IAAI,CAAC;IAChC,CAAC;IAED,IAAI,CAAC,IAAY,EAAE,IAAY;QAC9B,IAAI,MAAc,CAAC;QACnB,IAAI,CAAC;YACJ,iEAAiE;YACjE,iEAAiE;YACjE,kEAAkE;YAClE,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC9C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,IAAI,GAAG,YAAY,KAAK,IAAI,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACpE,MAAM,GAAG,EAAE,CAAC;YACb,CAAC;iBAAM,CAAC;gBACP,MAAM,GAAG,CAAC;YACX,CAAC;QACF,CAAC;QAED,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;QAC9B,uEAAuE;QACvE,sCAAsC;QACtC,MAAM,OAAO,GAAG,YAAY,CAAC,oBAAoB,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;QAC9D,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAChD,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC5C,IAAI,KAAa,CAAC;QAClB,IAAI,OAAO,GAAiB,SAAS,CAAC;QACtC,IAAI,MAA0B,CAAC;QAE/B,IAAI,UAAU,KAAK,CAAC,CAAC,IAAI,QAAQ,KAAK,CAAC,CAAC,EAAE,CAAC;YAC1C,oEAAoE;YACpE,oEAAoE;YACpE,gEAAgE;YAChE,gCAAgC;YAChC,MAAM,SAAS,GACd,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC;YAC9D,KAAK;gBACJ,MAAM;oBACN,SAAS;oBACT,YAAY;oBACZ,GAAG;oBACH,OAAO;oBACP,GAAG;oBACH,UAAU;oBACV,GAAG,CAAC;QACN,CAAC;aAAM,IAAI,UAAU,KAAK,CAAC,CAAC,IAAI,QAAQ,KAAK,CAAC,CAAC,EAAE,CAAC;YACjD,uCAAuC;YACvC,KAAK,GAAG,MAAM,CAAC;YACf,OAAO,GAAG,SAAS,CAAC;YACpB,MAAM;gBACL,UAAU,KAAK,CAAC,CAAC;oBAChB,CAAC,CAAC,8CAA8C;oBAChD,CAAC,CAAC,8CAA8C,CAAC;QACpD,CAAC;aAAM,IAAI,QAAQ,GAAG,UAAU,EAAE,CAAC;YAClC,6CAA6C;YAC7C,KAAK,GAAG,MAAM,CAAC;YACf,OAAO,GAAG,SAAS,CAAC;YACpB,MAAM,GAAG,sCAAsC,CAAC;QACjD,CAAC;aAAM,IACN,MAAM,CAAC,OAAO,CAAC,YAAY,EAAE,UAAU,GAAG,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACrE,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAC9D,CAAC;YACF,kEAAkE;YAClE,KAAK,GAAG,MAAM,CAAC;YACf,OAAO,GAAG,SAAS,CAAC;YACpB,MAAM,GAAG,mCAAmC,CAAC;QAC9C,CAAC;aAAM,CAAC;YACP,iEAAiE;YACjE,kEAAkE;YAClE,wCAAwC;YACxC,MAAM,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC;YAC9C,KAAK;gBACJ,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC;oBAC3B,YAAY;oBACZ,GAAG;oBACH,OAAO;oBACP,GAAG;oBACH,UAAU;oBACV,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YACxB,uDAAuD;YACvD,OAAO,GAAG,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;QACjD,CAAC;QAED,OAAO;YACN,IAAI;YACJ,MAAM;YACN,KAAK;YACL,oEAAoE;YACpE,0CAA0C;YAC1C,IAAI,EAAE,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC;YACtC,OAAO;YACP,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3C,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC;YAC1B,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC;SACxB,CAAC;IACH,CAAC;IAED,qEAAqE;IACrE,KAAK,CAAC,IAAe,EAAE,UAAmB;QACzC,oEAAoE;QACpE,oEAAoE;QACpE,qEAAqE;QACrE,IAAI,IAAI,CAAC,OAAO,KAAK,MAAM,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAC3D,IAAI,IAAI,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;gBAC7B,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,CAAC,CAAC,CAAC;YAC/C,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;YAC7B,OAAO,IAAI,CAAC,OAAO,CAAC;QACrB,CAAC;QAED,mEAAmE;QACnE,uEAAuE;QACvE,2EAA2E;QAC3E,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,IAAI,YAAY,CAAC;QACzC,IAAI,EAAsB,CAAC;QAC3B,IAAI,CAAC;YACJ,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YAC5B,SAAS,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;YACxC,SAAS,CAAC,EAAE,CAAC,CAAC;YACd,SAAS,CAAC,EAAE,CAAC,CAAC;YACd,EAAE,GAAG,SAAS,CAAC;YACf,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,yDAAyD;YACzD,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;gBACtB,IAAI,CAAC;oBACJ,SAAS,CAAC,EAAE,CAAC,CAAC;gBACf,CAAC;gBAAC,MAAM,CAAC;oBACR,0DAA0D;gBAC3D,CAAC;YACF,CAAC;YACD,IAAI,CAAC;gBACJ,UAAU,CAAC,OAAO,CAAC,CAAC;YACrB,CAAC;YAAC,MAAM,CAAC;gBACR,sBAAsB;YACvB,CAAC;YACD,MAAM,GAAG,CAAC;QACX,CAAC;QACD,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,uBAAuB,EAAE,CAAC,CAAC,CAAC;QAC/C,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QAC7B,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,uEAAuE;IACvE,0EAA0E;IAClE,UAAU,CAAC,OAAe,EAAE,MAAc;QACjD,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC7B,CAAC;IAEO,KAAK,CAAC,IAAe,EAAE,UAA8B;QAC5D,IAAI,CAAC,KAAK;aACR,OAAO,CACP;;;2CAGuC,CACvC;aACA,GAAG,CACH,MAAM,EAAE,EACR,UAAU,IAAI,IAAI,EAClB,IAAI,CAAC,SAAS,EACd,IAAI,CAAC,IAAI,EACT,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EACtC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,EACrC,IAAI,CAAC,UAAU,EACf,IAAI,CAAC,SAAS,EACd,IAAI,CAAC,OAAO,EACZ,IAAI,CAAC,MAAM,IAAI,IAAI,CACnB,CAAC;IACJ,CAAC;CACD"}
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import type { InjectionLedger } from "./InjectionLedger.js";
|
|
2
2
|
import type { MemoryService } from "./MemoryService.js";
|
|
3
|
+
import { type GateReason } from "./QualityGate.js";
|
|
3
4
|
import { type Metrics } from "./metrics.js";
|
|
4
5
|
export declare const QUALITY_GATE_SETTING = "quality_gate_enabled";
|
|
5
6
|
export declare const SNIPPET_INJECTION_SETTING = "lesson_snippet_injection";
|
|
7
|
+
/**
|
|
8
|
+
* v0.6.0 (K6-023 / plan §5.8) — the effective pre-prompt budget shared by
|
|
9
|
+
* `ContextInjector.prePromptCap()` and `kevin_audit`'s channels block
|
|
10
|
+
* (budget_tokens). Single source of truth for the K6-021 clamp:
|
|
11
|
+
* default 400 when the raw value is missing/non-numeric, clamped to
|
|
12
|
+
* [0, 4000]; 0 means "push off".
|
|
13
|
+
*/
|
|
14
|
+
export declare function effectivePrePromptCap(raw: string | undefined): number;
|
|
6
15
|
export interface ChatMessage {
|
|
7
16
|
role: string;
|
|
8
17
|
content: string;
|
|
@@ -21,6 +30,26 @@ export interface CompactingInput {
|
|
|
21
30
|
export interface CompactingOutput {
|
|
22
31
|
context: string[];
|
|
23
32
|
}
|
|
33
|
+
export interface InjectionPlanItem {
|
|
34
|
+
id: string;
|
|
35
|
+
type: string;
|
|
36
|
+
decision: "admitted" | "blocked";
|
|
37
|
+
/** Gate reason when blocked; null for admitted items. */
|
|
38
|
+
reason?: GateReason;
|
|
39
|
+
/** Estimated tokens this memory would contribute to the block. */
|
|
40
|
+
tokens: number;
|
|
41
|
+
}
|
|
42
|
+
export interface InjectionPlan {
|
|
43
|
+
query: string;
|
|
44
|
+
tag: "context" | "memory";
|
|
45
|
+
cap: number;
|
|
46
|
+
would_inject: boolean;
|
|
47
|
+
/** Estimated tokens of the block that WOULD be produced (0 when
|
|
48
|
+
* nothing would be injected). */
|
|
49
|
+
total_tokens: number;
|
|
50
|
+
admitted: InjectionPlanItem[];
|
|
51
|
+
blocked: InjectionPlanItem[];
|
|
52
|
+
}
|
|
24
53
|
export declare class ContextInjector {
|
|
25
54
|
private memoryService;
|
|
26
55
|
private metrics;
|
|
@@ -76,13 +105,71 @@ export declare class ContextInjector {
|
|
|
76
105
|
* `QualityGate.canInject` (session seen-set + recurrence + strength),
|
|
77
106
|
* and each admitted memory is recorded in the `InjectionLedger`
|
|
78
107
|
* (plan §5.2 — one row per injected memory).
|
|
108
|
+
*
|
|
109
|
+
* v0.5.0 (K5-014 / plan §8.10) — the pipeline is decomposed into
|
|
110
|
+
* `fetchSlice` (ranked retrieval + budget overflow) + `evaluate`
|
|
111
|
+
* (pure gate verdicts) + this orchestrator, so the read-only `plan()`
|
|
112
|
+
* can mirror it without any side effect (D5-08).
|
|
79
113
|
*/
|
|
80
114
|
private inject;
|
|
115
|
+
/**
|
|
116
|
+
* v0.5.0 (K5-014 / plan §8.10) — the ranked-retrieval stage shared by
|
|
117
|
+
* `inject` and `plan`. With `dry = true` it is a strict read:
|
|
118
|
+
*
|
|
119
|
+
* - the probe fetch never bumps (BUG-016), like the live path;
|
|
120
|
+
* - the overflow retry ALSO fetches with `bump: false` (the live path
|
|
121
|
+
* lets the retry fetch bump once — that is the only difference);
|
|
122
|
+
* - the no-retry bump is skipped.
|
|
123
|
+
*
|
|
124
|
+
* This is what lets `plan()` predict the EXACT slice the live path
|
|
125
|
+
* would inject without mutating a single relevance score.
|
|
126
|
+
*/
|
|
127
|
+
private fetchSlice;
|
|
128
|
+
/**
|
|
129
|
+
* v0.5.0 (K5-014 / plan §8.10, D5-08) — PUBLIC read-only prediction of
|
|
130
|
+
* what `inject` WOULD do for a query: same retrieval, same gate, zero
|
|
131
|
+
* side effects. Never moves a counter, never writes the seen-set, never
|
|
132
|
+
* bumps relevance, never records ledger rows. `kevin_trace` (K5-015)
|
|
133
|
+
* surfaces this to the agent; tests freeze the clock + settings around
|
|
134
|
+
* it.
|
|
135
|
+
*/
|
|
136
|
+
plan(query: string, options?: {
|
|
137
|
+
tag?: "context" | "memory";
|
|
138
|
+
cap?: number;
|
|
139
|
+
sessionId?: string;
|
|
140
|
+
}): InjectionPlan;
|
|
141
|
+
/**
|
|
142
|
+
* v0.5.0 (K5-014 / plan §8.10) — PURE gate evaluation shared by `admit`
|
|
143
|
+
* (live path) and `plan` (read-only path): returns the verdict for every
|
|
144
|
+
* candidate plus the seen-set as it WOULD look afterwards. Never writes
|
|
145
|
+
* state — the caller decides whether to persist.
|
|
146
|
+
*/
|
|
147
|
+
private evaluate;
|
|
148
|
+
/**
|
|
149
|
+
* v0.5.0 (K5-017 / plan §8.11, D5-11) — the effective pre-prompt cap,
|
|
150
|
+
* read at call time from `pre_prompt_budget_tokens` (seeded "900" by
|
|
151
|
+
* migration 006). Clamped to [100, 4000]; a non-numeric value falls
|
|
152
|
+
* back to 900. `kevin_trace` reports the value used via `plan().cap`.
|
|
153
|
+
*
|
|
154
|
+
* v0.6.0 (K6-021 / plan §5.8) — default becomes "400" and the lower
|
|
155
|
+
* clamp bound drops to **0**: the roadmap's kill criterion K1 prescribes
|
|
156
|
+
* cutting the push budget to zero when coverage is poor, and v0.5's
|
|
157
|
+
* [100, 4000] clamp made that response unimplementable. A non-numeric
|
|
158
|
+
* value falls back to 400. `onSystemTransform` treats 0 as off and
|
|
159
|
+
* returns before any retrieval (see K6-021 acceptance).
|
|
160
|
+
*/
|
|
161
|
+
private prePromptCap;
|
|
81
162
|
/**
|
|
82
163
|
* v0.4.0 (K4-017) — QualityGate admission: filters the ranked slice to
|
|
83
164
|
* memories that may be injected this session, updating the session
|
|
84
165
|
* seen-set.
|
|
85
166
|
*
|
|
167
|
+
* v0.5.0 (K5-007 / plan §5.2, D5-04) — uses `canInjectVerdict` and
|
|
168
|
+
* increments the matching `injections_blocked_*` counter for every
|
|
169
|
+
* rejection, so gate policy becomes measurable. When `dryRun === true`
|
|
170
|
+
* the counters stay untouched (D5-08) — and so does the seen-set
|
|
171
|
+
* (a dry run is a strict read, K5-014).
|
|
172
|
+
*
|
|
86
173
|
* BUG-005 — strength/actionability now go through the REAL
|
|
87
174
|
* `QualityGate.evaluate` semantics (plan §5.1 rules 1-2), which this
|
|
88
175
|
* class had only re-derived from `metadata.dispatch`:
|