@jmtrin/opencode-kevin 0.3.0 → 0.5.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 +117 -9
- package/dist/migrations/005_v04_signal.sql +57 -0
- package/dist/migrations/006_v05_glassbox.sql +118 -0
- package/dist/plugin/Archiver.d.ts +42 -0
- package/dist/plugin/Archiver.js +98 -0
- package/dist/plugin/Archiver.js.map +1 -0
- package/dist/plugin/CausalChain.d.ts +13 -2
- package/dist/plugin/CausalChain.js +83 -13
- package/dist/plugin/CausalChain.js.map +1 -1
- package/dist/plugin/ContextInjector.d.ts +152 -4
- package/dist/plugin/ContextInjector.js +400 -93
- package/dist/plugin/ContextInjector.js.map +1 -1
- package/dist/plugin/Feedback.d.ts +67 -0
- package/dist/plugin/Feedback.js +137 -0
- package/dist/plugin/Feedback.js.map +1 -0
- package/dist/plugin/InjectionLedger.d.ts +92 -0
- package/dist/plugin/InjectionLedger.js +243 -0
- package/dist/plugin/InjectionLedger.js.map +1 -0
- package/dist/plugin/LessonFixer.d.ts +44 -0
- package/dist/plugin/LessonFixer.js +46 -0
- package/dist/plugin/LessonFixer.js.map +1 -0
- package/dist/plugin/MemoryService.d.ts +81 -1
- package/dist/plugin/MemoryService.js +321 -54
- package/dist/plugin/MemoryService.js.map +1 -1
- package/dist/plugin/Migrate.js +31 -0
- package/dist/plugin/Migrate.js.map +1 -1
- package/dist/plugin/QualityGate.d.ts +110 -0
- package/dist/plugin/QualityGate.js +108 -0
- package/dist/plugin/QualityGate.js.map +1 -0
- package/dist/plugin/Reflector.d.ts +17 -0
- package/dist/plugin/Reflector.js +81 -26
- package/dist/plugin/Reflector.js.map +1 -1
- package/dist/plugin/Retrospective.js +22 -1
- package/dist/plugin/Retrospective.js.map +1 -1
- package/dist/plugin/ToolCallObserver.d.ts +1 -0
- package/dist/plugin/ToolCallObserver.js +15 -8
- package/dist/plugin/ToolCallObserver.js.map +1 -1
- package/dist/plugin/confidence.d.ts +8 -0
- package/dist/plugin/confidence.js +35 -0
- package/dist/plugin/confidence.js.map +1 -0
- package/dist/plugin/index.d.ts +5 -0
- package/dist/plugin/index.js +368 -44
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/kevin_audit.d.ts +49 -0
- package/dist/plugin/kevin_audit.js +141 -0
- package/dist/plugin/kevin_audit.js.map +1 -0
- package/dist/plugin/kevin_why.d.ts +4 -0
- package/dist/plugin/kevin_why.js +72 -35
- package/dist/plugin/kevin_why.js.map +1 -1
- package/dist/plugin/memory-format.d.ts +12 -0
- package/dist/plugin/memory-format.js +45 -5
- package/dist/plugin/memory-format.js.map +1 -1
- package/dist/plugin/metrics.d.ts +27 -1
- package/dist/plugin/metrics.js +61 -0
- package/dist/plugin/metrics.js.map +1 -1
- package/dist/plugin/okf-export.js +63 -22
- package/dist/plugin/okf-export.js.map +1 -1
- package/dist/plugin/okf-import.d.ts +4 -1
- package/dist/plugin/okf-import.js +45 -12
- package/dist/plugin/okf-import.js.map +1 -1
- package/dist/plugin/query-tokenizer.d.ts +13 -0
- package/dist/plugin/query-tokenizer.js +86 -0
- package/dist/plugin/query-tokenizer.js.map +1 -0
- 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 +193 -0
- package/dist/plugin/replay.js.map +1 -0
- package/migrations/005_v04_signal.sql +57 -0
- package/migrations/006_v05_glassbox.sql +118 -0
- package/package.json +3 -2
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { uuidv7 } from "./uuid.js";
|
|
2
|
+
const POSITIVE_VERDICTS = ["useful"];
|
|
3
|
+
const NEGATIVE_VERDICTS = [
|
|
4
|
+
"wrong",
|
|
5
|
+
"outdated",
|
|
6
|
+
"ignore",
|
|
7
|
+
];
|
|
8
|
+
// v0.5.0 — pre-006 DBs lack `memory_feedback`; record() fails loudly with a
|
|
9
|
+
// descriptive error instead of a bare "no such table" (the tool layer turns
|
|
10
|
+
// it into a graceful message).
|
|
11
|
+
const feedbackTableCache = new WeakMap();
|
|
12
|
+
function hasFeedbackTable(store) {
|
|
13
|
+
const cached = feedbackTableCache.get(store);
|
|
14
|
+
if (cached !== undefined)
|
|
15
|
+
return cached;
|
|
16
|
+
try {
|
|
17
|
+
store.prepare("SELECT COUNT(*) FROM memory_feedback").get();
|
|
18
|
+
feedbackTableCache.set(store, true);
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
feedbackTableCache.set(store, false);
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export class Feedback {
|
|
27
|
+
store;
|
|
28
|
+
metrics;
|
|
29
|
+
now;
|
|
30
|
+
constructor(store, metrics, now = () => new Date()) {
|
|
31
|
+
this.store = store;
|
|
32
|
+
this.metrics = metrics ?? null;
|
|
33
|
+
this.now = now;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Records one human verdict and folds it into the memory's counters.
|
|
37
|
+
* Counters are RECOMPUTED from the table (never incremented) so rows
|
|
38
|
+
* deleted in tests or by hand cannot drift them.
|
|
39
|
+
*
|
|
40
|
+
* The `ignore` verdict also stamps `memories.ignored = 1` (D5-07).
|
|
41
|
+
* Returns the feedback row id.
|
|
42
|
+
*/
|
|
43
|
+
record(input) {
|
|
44
|
+
if (!hasFeedbackTable(this.store)) {
|
|
45
|
+
throw new Error("kevin_feedback requires migration 006_v05_glassbox.sql (missing table memory_feedback)");
|
|
46
|
+
}
|
|
47
|
+
const id = uuidv7();
|
|
48
|
+
this.store
|
|
49
|
+
.prepare(`INSERT INTO memory_feedback
|
|
50
|
+
(id, memory_id, verdict, session_id, note)
|
|
51
|
+
VALUES (?, ?, ?, ?, ?)`)
|
|
52
|
+
.run(id, input.memoryId, input.verdict, input.sessionId ?? null, input.note ?? null);
|
|
53
|
+
this.recomputeCounters(input.memoryId);
|
|
54
|
+
if (input.verdict === "ignore") {
|
|
55
|
+
this.store
|
|
56
|
+
.prepare("UPDATE memories SET ignored = 1 WHERE id = ?")
|
|
57
|
+
.run(input.memoryId);
|
|
58
|
+
}
|
|
59
|
+
// v0.5.0 (K5-023 / plan §5.3, D5-06) — lifecycle action of a
|
|
60
|
+
// negative verdict. `wrong` is an opinion and deserves a second
|
|
61
|
+
// opinion: it demotes only at feedback_negative >= 2. `outdated`
|
|
62
|
+
// is a self-verifying claim about the world and acts at once.
|
|
63
|
+
// `useful` confirms the memory and refreshes its verification.
|
|
64
|
+
if (input.verdict === "outdated") {
|
|
65
|
+
this.store
|
|
66
|
+
.prepare("UPDATE memories SET status = 'stale' WHERE id = ?")
|
|
67
|
+
.run(input.memoryId);
|
|
68
|
+
}
|
|
69
|
+
else if (input.verdict === "wrong") {
|
|
70
|
+
const counts = this.countsFor(input.memoryId);
|
|
71
|
+
if (counts.negative >= 2) {
|
|
72
|
+
this.store
|
|
73
|
+
.prepare("UPDATE memories SET status = 'stale' WHERE id = ?")
|
|
74
|
+
.run(input.memoryId);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else if (input.verdict === "useful") {
|
|
78
|
+
this.store
|
|
79
|
+
.prepare("UPDATE memories SET last_verified_at = datetime('now') WHERE id = ?")
|
|
80
|
+
.run(input.memoryId);
|
|
81
|
+
}
|
|
82
|
+
if (POSITIVE_VERDICTS.includes(input.verdict)) {
|
|
83
|
+
this.metrics?.incr("feedback_positive_total", 1);
|
|
84
|
+
}
|
|
85
|
+
else if (NEGATIVE_VERDICTS.includes(input.verdict)) {
|
|
86
|
+
this.metrics?.incr("feedback_negative_total", 1);
|
|
87
|
+
}
|
|
88
|
+
return id;
|
|
89
|
+
}
|
|
90
|
+
/** Human-judgement counters for one memory (from the row, not the table). */
|
|
91
|
+
countsFor(memoryId) {
|
|
92
|
+
const row = this.store
|
|
93
|
+
.prepare("SELECT feedback_positive, feedback_negative FROM memories WHERE id = ?")
|
|
94
|
+
.get(memoryId);
|
|
95
|
+
if (!row)
|
|
96
|
+
return { positive: 0, negative: 0 };
|
|
97
|
+
return { positive: row.feedback_positive, negative: row.feedback_negative };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Raw verdict history, newest first. Used by `kevin_feedback` (K5-011)
|
|
101
|
+
* and by the audit tool (K5-016).
|
|
102
|
+
*/
|
|
103
|
+
list(memoryId, limit = 50) {
|
|
104
|
+
const rows = this.store
|
|
105
|
+
.prepare(`SELECT id, memory_id, verdict, session_id, note, created_at
|
|
106
|
+
FROM memory_feedback
|
|
107
|
+
${memoryId ? "WHERE memory_id = ?" : ""}
|
|
108
|
+
ORDER BY created_at DESC, rowid DESC
|
|
109
|
+
LIMIT ?`)
|
|
110
|
+
.all(...(memoryId ? [memoryId, limit] : [limit]));
|
|
111
|
+
return rows.map((r) => ({
|
|
112
|
+
id: r.id,
|
|
113
|
+
memoryId: r.memory_id,
|
|
114
|
+
verdict: r.verdict,
|
|
115
|
+
sessionId: r.session_id,
|
|
116
|
+
note: r.note,
|
|
117
|
+
createdAt: r.created_at,
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Recompute a memory's positive/negative counters straight from the
|
|
122
|
+
* verdict table. Kept private — `record` is the only mutation path.
|
|
123
|
+
*/
|
|
124
|
+
recomputeCounters(memoryId) {
|
|
125
|
+
this.store
|
|
126
|
+
.prepare(`UPDATE memories SET
|
|
127
|
+
feedback_positive = (
|
|
128
|
+
SELECT COUNT(*) FROM memory_feedback
|
|
129
|
+
WHERE memory_id = ? AND verdict = 'useful'),
|
|
130
|
+
feedback_negative = (
|
|
131
|
+
SELECT COUNT(*) FROM memory_feedback
|
|
132
|
+
WHERE memory_id = ? AND verdict IN ('wrong','outdated','ignore'))
|
|
133
|
+
WHERE id = ?`)
|
|
134
|
+
.run(memoryId, memoryId, memoryId);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=Feedback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Feedback.js","sourceRoot":"","sources":["../../plugin/Feedback.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { Store } from "./Store.js";
|
|
2
|
+
import type { Metrics } from "./metrics.js";
|
|
3
|
+
/**
|
|
4
|
+
* v0.4.0 InjectionLedger (K4-006/K4-007, plan §5.2, D4-04).
|
|
5
|
+
*
|
|
6
|
+
* The measurement half of the closed feedback loop: every lesson injected
|
|
7
|
+
* into a prompt is recorded in `kevin_injections`, and at `session.idle` the
|
|
8
|
+
* ledger settles each unmeasured row — if the same fingerprint failed again
|
|
9
|
+
* after the injection, the lesson is `ineffective`; otherwise `effective`.
|
|
10
|
+
* `precision_rate = effective / total` is the honest, measured value of the
|
|
11
|
+
* injection system (replacing Kevin_Token_Impact-style estimates).
|
|
12
|
+
*
|
|
13
|
+
* Schema (migration 005_v04_signal.sql):
|
|
14
|
+
* kevin_injections(id PK, memory_id, fingerprint, session_id, hook,
|
|
15
|
+
* tokens, injected_at, outcome)
|
|
16
|
+
*
|
|
17
|
+
* BUG-015 — a ledger row is only meaningful when its fingerprint can be
|
|
18
|
+
* matched against failing tool_calls (`COALESCE(error_fingerprint,
|
|
19
|
+
* fingerprint)`). Memories WITHOUT a fingerprint (agent-saved notes) can
|
|
20
|
+
* never match, so they are NOT recorded: settle() would otherwise mark
|
|
21
|
+
* them `effective` forever and inflate `precision_rate`. The caller
|
|
22
|
+
* (ContextInjector.recordInjections) skips them.
|
|
23
|
+
*/
|
|
24
|
+
export type InjectionHook = "pre_prompt" | "compacting";
|
|
25
|
+
export type InjectionOutcome = "unmeasured" | "effective" | "ineffective" | "inconclusive";
|
|
26
|
+
export interface InjectionRecordInput {
|
|
27
|
+
memoryId: string;
|
|
28
|
+
fingerprint: string;
|
|
29
|
+
sessionId: string;
|
|
30
|
+
hook: InjectionHook;
|
|
31
|
+
tokens: number;
|
|
32
|
+
}
|
|
33
|
+
interface InjectionRow {
|
|
34
|
+
id: string;
|
|
35
|
+
memory_id: string;
|
|
36
|
+
fingerprint: string;
|
|
37
|
+
session_id: string;
|
|
38
|
+
hook: InjectionHook;
|
|
39
|
+
tokens: number;
|
|
40
|
+
injected_at: string;
|
|
41
|
+
outcome: InjectionOutcome;
|
|
42
|
+
}
|
|
43
|
+
export declare class InjectionLedger {
|
|
44
|
+
private readonly store;
|
|
45
|
+
private readonly metrics;
|
|
46
|
+
constructor(store: Store, metrics?: Metrics | null);
|
|
47
|
+
/**
|
|
48
|
+
* Records one injected memory. Idempotent at the row level (UUID PK);
|
|
49
|
+
* duplicates are expected only via the caller's per-session seen-set.
|
|
50
|
+
*/
|
|
51
|
+
record(input: InjectionRecordInput): void;
|
|
52
|
+
/**
|
|
53
|
+
* Settles every unmeasured injection of the session: a fingerprint that
|
|
54
|
+
* failed again (as a failing tool_call) after the injection is
|
|
55
|
+
* `ineffective`, otherwise `effective`. Idempotent — only
|
|
56
|
+
* `outcome = 'unmeasured'` rows are flipped, and the recurrence charge
|
|
57
|
+
* is `MAX(recurrence_count, n)` where n = all failing calls of the
|
|
58
|
+
* fingerprint after `injected_at` (a later idle re-computes n and
|
|
59
|
+
* catches up — plan §5.1 rule 4: 3 recurrences → stale).
|
|
60
|
+
*
|
|
61
|
+
* An ineffective injection also bumps the target memory's
|
|
62
|
+
* `recurrence_count` (negative evidence, plan §5.3) and stamps
|
|
63
|
+
* `last_injected_at` (monotonically — an older row can never
|
|
64
|
+
* regress a newer injection's timestamp; the column means "most
|
|
65
|
+
* recent injection").
|
|
66
|
+
*/
|
|
67
|
+
settle(sessionId: string): void;
|
|
68
|
+
/**
|
|
69
|
+
* Per-fingerprint failing tool-call counts for the session. Feeds
|
|
70
|
+
* QualityGate.canInject and the HITL suggestion block.
|
|
71
|
+
*/
|
|
72
|
+
recurrencesFor(sessionId: string): Map<string, number>;
|
|
73
|
+
/**
|
|
74
|
+
* v0.4.0 (K4-017) — recurrence counts for the QualityGate at
|
|
75
|
+
* injection time: only failing calls that happened AFTER the
|
|
76
|
+
* fingerprint was already injected this session count. The failure
|
|
77
|
+
* that *created* a lesson is not a recurrence — it precedes any
|
|
78
|
+
* injection (plan §5.1 rule 4, same `ts >= injected_at` semantics
|
|
79
|
+
* `settle` uses).
|
|
80
|
+
*/
|
|
81
|
+
postInjectionRecurrencesFor(sessionId: string): Map<string, number>;
|
|
82
|
+
/** Number of rows for the session not yet settled (drives tests and settle). */
|
|
83
|
+
unsettledForSession(sessionId: string): number;
|
|
84
|
+
/** Latest ledger rows for a session, newest first (used by tests/tools). */
|
|
85
|
+
rowsForSession(sessionId: string): InjectionRow[];
|
|
86
|
+
/**
|
|
87
|
+
* v0.5.0 (K5-005 / plan §8.4) — one grouped rollup over the whole
|
|
88
|
+
* ledger, zero-filled for every outcome. Consumed by `kevin_audit`.
|
|
89
|
+
*/
|
|
90
|
+
outcomeCounts(): Record<InjectionOutcome, number>;
|
|
91
|
+
}
|
|
92
|
+
export {};
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { uuidv7 } from "./uuid.js";
|
|
2
|
+
export class InjectionLedger {
|
|
3
|
+
store;
|
|
4
|
+
metrics;
|
|
5
|
+
constructor(store, metrics) {
|
|
6
|
+
this.store = store;
|
|
7
|
+
this.metrics = metrics ?? null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Records one injected memory. Idempotent at the row level (UUID PK);
|
|
11
|
+
* duplicates are expected only via the caller's per-session seen-set.
|
|
12
|
+
*/
|
|
13
|
+
record(input) {
|
|
14
|
+
this.store
|
|
15
|
+
.prepare(`INSERT INTO kevin_injections
|
|
16
|
+
(id, memory_id, fingerprint, session_id, hook, tokens, outcome)
|
|
17
|
+
VALUES (?, ?, ?, ?, ?, ?, 'unmeasured')`)
|
|
18
|
+
.run(uuidv7(), input.memoryId, input.fingerprint, input.sessionId, input.hook, input.tokens);
|
|
19
|
+
this.metrics?.incr("injections_total", 1);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Settles every unmeasured injection of the session: a fingerprint that
|
|
23
|
+
* failed again (as a failing tool_call) after the injection is
|
|
24
|
+
* `ineffective`, otherwise `effective`. Idempotent — only
|
|
25
|
+
* `outcome = 'unmeasured'` rows are flipped, and the recurrence charge
|
|
26
|
+
* is `MAX(recurrence_count, n)` where n = all failing calls of the
|
|
27
|
+
* fingerprint after `injected_at` (a later idle re-computes n and
|
|
28
|
+
* catches up — plan §5.1 rule 4: 3 recurrences → stale).
|
|
29
|
+
*
|
|
30
|
+
* An ineffective injection also bumps the target memory's
|
|
31
|
+
* `recurrence_count` (negative evidence, plan §5.3) and stamps
|
|
32
|
+
* `last_injected_at` (monotonically — an older row can never
|
|
33
|
+
* regress a newer injection's timestamp; the column means "most
|
|
34
|
+
* recent injection").
|
|
35
|
+
*/
|
|
36
|
+
settle(sessionId) {
|
|
37
|
+
const injections = this.store
|
|
38
|
+
.prepare(`SELECT id, memory_id, fingerprint, injected_at, outcome
|
|
39
|
+
FROM kevin_injections
|
|
40
|
+
WHERE session_id = ?
|
|
41
|
+
ORDER BY injected_at ASC, id ASC`)
|
|
42
|
+
.all(sessionId);
|
|
43
|
+
for (const inj of injections) {
|
|
44
|
+
// Same identity dimension CausalChain uses: the failing call's
|
|
45
|
+
// `error_fingerprint` (stamped by Reflector) or the legacy
|
|
46
|
+
// `fingerprint` hash. `ts` and `injected_at` are both
|
|
47
|
+
// `datetime('now')` text → lexicographic comparison is valid.
|
|
48
|
+
// COUNT (not LIMIT 1): every failing call after the injection
|
|
49
|
+
// is a recurrence — the charge must reach 3 so D4-06 expels
|
|
50
|
+
// the lesson.
|
|
51
|
+
//
|
|
52
|
+
// BUG-003 — the exemption is now bounded to the lesson's OWN
|
|
53
|
+
// creating call (memories.metadata.origin_call_id, stamped by
|
|
54
|
+
// Reflector). The old code excluded the session's FIRST failing
|
|
55
|
+
// call of the fingerprint, which is only the creating call when
|
|
56
|
+
// the lesson was born in THIS session; a lesson created in an
|
|
57
|
+
// earlier session had its first in-session failure (a genuine
|
|
58
|
+
// post-injection recurrence) wrongly exempted, inflating
|
|
59
|
+
// precision. Memories without a tracked creating call (agent-
|
|
60
|
+
// saved, test fixtures) get no exemption: only the `ts >=
|
|
61
|
+
// injected_at` bound applies.
|
|
62
|
+
const originCallId = readOriginCallId(this.store, inj.memory_id);
|
|
63
|
+
const countRow = this.store
|
|
64
|
+
.prepare(`SELECT COUNT(*) AS n FROM tool_calls
|
|
65
|
+
WHERE session_id = ?
|
|
66
|
+
AND success = 0
|
|
67
|
+
AND COALESCE(error_fingerprint, fingerprint) = ?
|
|
68
|
+
AND ts >= ?
|
|
69
|
+
AND (? IS NULL OR id != ?)
|
|
70
|
+
LIMIT 1`)
|
|
71
|
+
.get(sessionId, inj.fingerprint, inj.injected_at, originCallId, originCallId);
|
|
72
|
+
const n = countRow.n;
|
|
73
|
+
// v0.5.0 (K5-005 / plan §5.1, D5-01) — three-way settlement:
|
|
74
|
+
// recurrences >= 1 → ineffective (existing side effects unchanged)
|
|
75
|
+
// else fixes >= 1 → effective (a linked fix was OBSERVED)
|
|
76
|
+
// else → inconclusive (new majority bucket; excluded
|
|
77
|
+
// from the precision denominator)
|
|
78
|
+
if (n >= 1) {
|
|
79
|
+
if (inj.outcome === "unmeasured") {
|
|
80
|
+
this.store
|
|
81
|
+
.prepare(`UPDATE kevin_injections SET outcome = 'ineffective'
|
|
82
|
+
WHERE id = ?`)
|
|
83
|
+
.run(inj.id);
|
|
84
|
+
this.metrics?.incr("injections_ineffective", 1);
|
|
85
|
+
}
|
|
86
|
+
this.store
|
|
87
|
+
.prepare(`UPDATE memories
|
|
88
|
+
SET recurrence_count = MAX(recurrence_count, ?),
|
|
89
|
+
last_injected_at = CASE
|
|
90
|
+
WHEN last_injected_at IS NULL
|
|
91
|
+
OR ? > last_injected_at
|
|
92
|
+
THEN ? ELSE last_injected_at END
|
|
93
|
+
WHERE fingerprint = ? AND id = ?`)
|
|
94
|
+
.run(countRow.n, inj.injected_at, inj.injected_at, inj.fingerprint, inj.memory_id);
|
|
95
|
+
// v0.4.0 (K4-025 / plan §5.1 rule 4, D4-06) — recurrence
|
|
96
|
+
// expels: a fingerprint at `recurrence_count >= 3` is
|
|
97
|
+
// demoted to `status='stale'` and never injected again
|
|
98
|
+
// (only a new causal pattern — from a linked fix —
|
|
99
|
+
// re-admits the lesson, not the stale error row).
|
|
100
|
+
this.store
|
|
101
|
+
.prepare(`UPDATE memories SET status = 'stale'
|
|
102
|
+
WHERE id = ? AND recurrence_count >= 3`)
|
|
103
|
+
.run(inj.memory_id);
|
|
104
|
+
}
|
|
105
|
+
else if (inj.outcome === "unmeasured") {
|
|
106
|
+
// Mirror of the recurrence predicate with the success flag
|
|
107
|
+
// inverted and the fingerprint matched on
|
|
108
|
+
// `fix_for_fingerprint` (populated by CausalChain.onSuccess,
|
|
109
|
+
// indexed by idx_tool_calls_fix_fp since migration 004). The
|
|
110
|
+
// `ts >= injected_at` bound and `session_id = ?` filter are
|
|
111
|
+
// kept; there is no `origin_call_id` exemption for fixes —
|
|
112
|
+
// a fix is not the creating call.
|
|
113
|
+
const fixRow = this.store
|
|
114
|
+
.prepare(`SELECT COUNT(*) AS m FROM tool_calls
|
|
115
|
+
WHERE session_id = ?
|
|
116
|
+
AND success = 1
|
|
117
|
+
AND fix_for_fingerprint = ?
|
|
118
|
+
AND ts >= ?
|
|
119
|
+
LIMIT 1`)
|
|
120
|
+
.get(sessionId, inj.fingerprint, inj.injected_at);
|
|
121
|
+
if (fixRow.m >= 1) {
|
|
122
|
+
this.store
|
|
123
|
+
.prepare(`UPDATE kevin_injections SET outcome = 'effective'
|
|
124
|
+
WHERE id = ?`)
|
|
125
|
+
.run(inj.id);
|
|
126
|
+
this.metrics?.incr("injections_effective", 1);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
this.store
|
|
130
|
+
.prepare(`UPDATE kevin_injections SET outcome = 'inconclusive'
|
|
131
|
+
WHERE id = ?`)
|
|
132
|
+
.run(inj.id);
|
|
133
|
+
this.metrics?.incr("injections_inconclusive", 1);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Per-fingerprint failing tool-call counts for the session. Feeds
|
|
140
|
+
* QualityGate.canInject and the HITL suggestion block.
|
|
141
|
+
*/
|
|
142
|
+
recurrencesFor(sessionId) {
|
|
143
|
+
const rows = this.store
|
|
144
|
+
.prepare(`SELECT COALESCE(error_fingerprint, fingerprint) AS fp
|
|
145
|
+
FROM tool_calls
|
|
146
|
+
WHERE session_id = ? AND success = 0
|
|
147
|
+
AND (error_fingerprint IS NOT NULL OR fingerprint IS NOT NULL)`)
|
|
148
|
+
.all(sessionId);
|
|
149
|
+
const out = new Map();
|
|
150
|
+
for (const r of rows) {
|
|
151
|
+
if (!r.fp)
|
|
152
|
+
continue;
|
|
153
|
+
out.set(r.fp, (out.get(r.fp) ?? 0) + 1);
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* v0.4.0 (K4-017) — recurrence counts for the QualityGate at
|
|
159
|
+
* injection time: only failing calls that happened AFTER the
|
|
160
|
+
* fingerprint was already injected this session count. The failure
|
|
161
|
+
* that *created* a lesson is not a recurrence — it precedes any
|
|
162
|
+
* injection (plan §5.1 rule 4, same `ts >= injected_at` semantics
|
|
163
|
+
* `settle` uses).
|
|
164
|
+
*/
|
|
165
|
+
postInjectionRecurrencesFor(sessionId) {
|
|
166
|
+
const rows = this.store
|
|
167
|
+
.prepare(`SELECT COALESCE(t.error_fingerprint, t.fingerprint) AS fp
|
|
168
|
+
FROM tool_calls t
|
|
169
|
+
WHERE t.session_id = ? AND t.success = 0
|
|
170
|
+
AND (t.error_fingerprint IS NOT NULL OR t.fingerprint IS NOT NULL)
|
|
171
|
+
AND t.ts >= COALESCE(
|
|
172
|
+
(SELECT MAX(injected_at) FROM kevin_injections
|
|
173
|
+
WHERE session_id = t.session_id
|
|
174
|
+
AND fingerprint = COALESCE(t.error_fingerprint, t.fingerprint)),
|
|
175
|
+
'9999-12-31')`)
|
|
176
|
+
.all(sessionId);
|
|
177
|
+
const out = new Map();
|
|
178
|
+
for (const r of rows) {
|
|
179
|
+
if (!r.fp)
|
|
180
|
+
continue;
|
|
181
|
+
out.set(r.fp, (out.get(r.fp) ?? 0) + 1);
|
|
182
|
+
}
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
/** Number of rows for the session not yet settled (drives tests and settle). */
|
|
186
|
+
unsettledForSession(sessionId) {
|
|
187
|
+
const row = this.store
|
|
188
|
+
.prepare(`SELECT COUNT(*) AS n FROM kevin_injections
|
|
189
|
+
WHERE session_id = ? AND outcome = 'unmeasured'`)
|
|
190
|
+
.get(sessionId);
|
|
191
|
+
return row.n;
|
|
192
|
+
}
|
|
193
|
+
/** Latest ledger rows for a session, newest first (used by tests/tools). */
|
|
194
|
+
rowsForSession(sessionId) {
|
|
195
|
+
return this.store
|
|
196
|
+
.prepare(`SELECT id, memory_id, fingerprint, session_id, hook, tokens,
|
|
197
|
+
injected_at, outcome
|
|
198
|
+
FROM kevin_injections
|
|
199
|
+
WHERE session_id = ?
|
|
200
|
+
ORDER BY injected_at ASC, id ASC`)
|
|
201
|
+
.all(sessionId);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* v0.5.0 (K5-005 / plan §8.4) — one grouped rollup over the whole
|
|
205
|
+
* ledger, zero-filled for every outcome. Consumed by `kevin_audit`.
|
|
206
|
+
*/
|
|
207
|
+
outcomeCounts() {
|
|
208
|
+
const rows = this.store
|
|
209
|
+
.prepare("SELECT outcome, COUNT(*) AS n FROM kevin_injections GROUP BY outcome")
|
|
210
|
+
.all();
|
|
211
|
+
const out = {
|
|
212
|
+
unmeasured: 0,
|
|
213
|
+
effective: 0,
|
|
214
|
+
ineffective: 0,
|
|
215
|
+
inconclusive: 0,
|
|
216
|
+
};
|
|
217
|
+
for (const r of rows) {
|
|
218
|
+
out[r.outcome] = r.n;
|
|
219
|
+
}
|
|
220
|
+
return out;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* BUG-003 — read `origin_call_id` (the failing tool_call that CREATED the
|
|
225
|
+
* memory) from memories.metadata, mirroring the feedback loop's
|
|
226
|
+
* `readOriginCallId` in MemoryService. Returns null when absent/malformed.
|
|
227
|
+
*/
|
|
228
|
+
function readOriginCallId(store, memoryId) {
|
|
229
|
+
const row = store
|
|
230
|
+
.prepare("SELECT metadata FROM memories WHERE id = ?")
|
|
231
|
+
.get(memoryId);
|
|
232
|
+
if (!row?.metadata)
|
|
233
|
+
return null;
|
|
234
|
+
try {
|
|
235
|
+
const parsed = JSON.parse(row.metadata);
|
|
236
|
+
const id = parsed?.origin_call_id;
|
|
237
|
+
return typeof id === "string" && id.length > 0 ? id : null;
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=InjectionLedger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InjectionLedger.js","sourceRoot":"","sources":["../../plugin/InjectionLedger.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAoDnC,MAAM,OAAO,eAAe;IACV,KAAK,CAAQ;IACb,OAAO,CAAiB;IAEzC,YAAY,KAAY,EAAE,OAAwB;QACjD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,OAAO,GAAG,OAAO,IAAI,IAAI,CAAC;IAChC,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,KAA2B;QACjC,IAAI,CAAC,KAAK;aACR,OAAO,CACP;;6CAEyC,CACzC;aACA,GAAG,CACH,MAAM,EAAE,EACR,KAAK,CAAC,QAAQ,EACd,KAAK,CAAC,WAAW,EACjB,KAAK,CAAC,SAAS,EACf,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,MAAM,CACZ,CAAC;QACH,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC;IAC3C,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,SAAiB;QACvB,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK;aAC3B,OAAO,CACP;;;uCAGmC,CACnC;aACA,GAAG,CAAC,SAAS,CAMZ,CAAC;QAEJ,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;YAC9B,+DAA+D;YAC/D,2DAA2D;YAC3D,sDAAsD;YACtD,8DAA8D;YAC9D,8DAA8D;YAC9D,4DAA4D;YAC5D,cAAc;YACd,EAAE;YACF,6DAA6D;YAC7D,8DAA8D;YAC9D,gEAAgE;YAChE,gEAAgE;YAChE,8DAA8D;YAC9D,8DAA8D;YAC9D,yDAAyD;YACzD,8DAA8D;YAC9D,0DAA0D;YAC1D,8BAA8B;YAC9B,MAAM,YAAY,GAAG,gBAAgB,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC;YACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK;iBACzB,OAAO,CACP;;;;;;cAMS,CACT;iBACA,GAAG,CACH,SAAS,EACT,GAAG,CAAC,WAAW,EACf,GAAG,CAAC,WAAW,EACf,YAAY,EACZ,YAAY,CACK,CAAC;YAEpB,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;YAErB,6DAA6D;YAC7D,sEAAsE;YACtE,gEAAgE;YAChE,mEAAmE;YACnE,qEAAqE;YACrE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACZ,IAAI,GAAG,CAAC,OAAO,KAAK,YAAY,EAAE,CAAC;oBAClC,IAAI,CAAC,KAAK;yBACR,OAAO,CACP;sBACe,CACf;yBACA,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;oBACd,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,wBAAwB,EAAE,CAAC,CAAC,CAAC;gBACjD,CAAC;gBACD,IAAI,CAAC,KAAK;qBACR,OAAO,CACP;;;;;;yCAMmC,CACnC;qBACA,GAAG,CACH,QAAQ,CAAC,CAAC,EACV,GAAG,CAAC,WAAW,EACf,GAAG,CAAC,WAAW,EACf,GAAG,CAAC,WAAW,EACf,GAAG,CAAC,SAAS,CACb,CAAC;gBACH,yDAAyD;gBACzD,sDAAsD;gBACtD,uDAAuD;gBACvD,mDAAmD;gBACnD,kDAAkD;gBAClD,IAAI,CAAC,KAAK;qBACR,OAAO,CACP;+CACyC,CACzC;qBACA,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACtB,CAAC;iBAAM,IAAI,GAAG,CAAC,OAAO,KAAK,YAAY,EAAE,CAAC;gBACzC,2DAA2D;gBAC3D,0CAA0C;gBAC1C,6DAA6D;gBAC7D,6DAA6D;gBAC7D,4DAA4D;gBAC5D,2DAA2D;gBAC3D,kCAAkC;gBAClC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK;qBACvB,OAAO,CACP;;;;;eAKS,CACT;qBACA,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,WAAW,CAEhD,CAAC;gBACF,IAAI,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;oBACnB,IAAI,CAAC,KAAK;yBACR,OAAO,CACP;sBACe,CACf;yBACA,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;oBACd,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,CAAC,CAAC,CAAC;gBAC/C,CAAC;qBAAM,CAAC;oBACP,IAAI,CAAC,KAAK;yBACR,OAAO,CACP;sBACe,CACf;yBACA,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;oBACd,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,yBAAyB,EAAE,CAAC,CAAC,CAAC;gBAClD,CAAC;YACF,CAAC;QACF,CAAC;IACF,CAAC;IAED;;;OAGG;IACH,cAAc,CAAC,SAAiB;QAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK;aACrB,OAAO,CACP;;;uEAGmE,CACnE;aACA,GAAG,CAAC,SAAS,CAA4B,CAAC;QAC5C,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;QACtC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACtB,IAAI,CAAC,CAAC,CAAC,EAAE;gBAAE,SAAS;YACpB,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACzC,CAAC;QACD,OAAO,GAAG,CAAC;IACZ,CAAC;IAED;;;;;;;OAOG;IACH,2BAA2B,CAAC,SAAiB;QAC5C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK;aACrB,OAAO,CACP;;;;;;;;4BAQwB,CACxB;aACA,GAAG,CAAC,SAAS,CAA4B,CAAC;QAC5C,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;QACtC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACtB,IAAI,CAAC,CAAC,CAAC,EAAE;gBAAE,SAAS;YACpB,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACzC,CAAC;QACD,OAAO,GAAG,CAAC;IACZ,CAAC;IAED,gFAAgF;IAChF,mBAAmB,CAAC,SAAiB;QACpC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK;aACpB,OAAO,CACP;sDACkD,CAClD;aACA,GAAG,CAAC,SAAS,CAAkB,CAAC;QAClC,OAAO,GAAG,CAAC,CAAC,CAAC;IACd,CAAC;IAED,4EAA4E;IAC5E,cAAc,CAAC,SAAiB;QAC/B,OAAO,IAAI,CAAC,KAAK;aACf,OAAO,CACP;;;;uCAImC,CACnC;aACA,GAAG,CAAC,SAAS,CAAmB,CAAC;IACpC,CAAC;IAED;;;OAGG;IACH,aAAa;QACZ,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK;aACrB,OAAO,CACP,sEAAsE,CACtE;aACA,GAAG,EAAgD,CAAC;QACtD,MAAM,GAAG,GAAqC;YAC7C,UAAU,EAAE,CAAC;YACb,SAAS,EAAE,CAAC;YACZ,WAAW,EAAE,CAAC;YACd,YAAY,EAAE,CAAC;SACf,CAAC;QACF,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACtB,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACtB,CAAC;QACD,OAAO,GAAG,CAAC;IACZ,CAAC;CACD;AAED;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,KAAY,EAAE,QAAgB;IACvD,MAAM,GAAG,GAAG,KAAK;SACf,OAAO,CAAC,4CAA4C,CAAC;SACrD,GAAG,CAAC,QAAQ,CAA4C,CAAC;IAC3D,IAAI,CAAC,GAAG,EAAE,QAAQ;QAAE,OAAO,IAAI,CAAC;IAChC,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAA4B,CAAC;QACnE,MAAM,EAAE,GAAG,MAAM,EAAE,cAAc,CAAC;QAClC,OAAO,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAC;IACb,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.4.0 (K4-014) — Deterministic fix capture (plan §5.4 / D4-07).
|
|
3
|
+
*
|
|
4
|
+
* Zero-cost "Fixed by:" raw material from local data: when CausalChain
|
|
5
|
+
* links a success to a failing fingerprint, the linked tool_call's
|
|
6
|
+
* `args_summary` becomes a deterministic fix string stored in
|
|
7
|
+
* `memories.fix_args`. The default promotion path is pure local data —
|
|
8
|
+
* no LLM calls (principle 14).
|
|
9
|
+
*/
|
|
10
|
+
/** Max length of the `args_summary` embedded in a fix_args string. */
|
|
11
|
+
export declare const FIX_ARGS_TRUNCATE = 120;
|
|
12
|
+
export interface LinkedToolCall {
|
|
13
|
+
tool: string;
|
|
14
|
+
args_summary: string | null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* `"bash" with args "npm i -g rg"` style. Returns null when the linked
|
|
18
|
+
* call carries no args_summary (nothing to say).
|
|
19
|
+
*/
|
|
20
|
+
export declare function extractFixArgs(call: LinkedToolCall): string | null;
|
|
21
|
+
export interface EnrichInput {
|
|
22
|
+
lesson: string;
|
|
23
|
+
fixArgs: string | null;
|
|
24
|
+
originalError: string | null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Opt-in LLM phrasing hook (K4-015): receives the lesson, the
|
|
28
|
+
* deterministic fix_args and the original error; returns a one-line
|
|
29
|
+
* `Fix:` phrasing, or null to fall back to the deterministic text.
|
|
30
|
+
* May be async; never runs on the failure hot path.
|
|
31
|
+
*/
|
|
32
|
+
export type EnrichFn = (input: EnrichInput) => Promise<string | null>;
|
|
33
|
+
export interface PatternLike {
|
|
34
|
+
content: string;
|
|
35
|
+
fixArgs: string | null;
|
|
36
|
+
}
|
|
37
|
+
/** Deterministic `Fixed by: {fix_args}` line (or "" when there is none). */
|
|
38
|
+
export declare function deterministicFixLine(pattern: PatternLike): string;
|
|
39
|
+
/**
|
|
40
|
+
* Promotion-time fix phrasing. Default path returns the deterministic
|
|
41
|
+
* `Fixed by: {fix_args}` line (or "" when there is none). When an
|
|
42
|
+
* `enrichFn` is supplied and returns a phrase, that phrase wins.
|
|
43
|
+
*/
|
|
44
|
+
export declare function enrichAtPromotion(pattern: PatternLike, enrichFn?: EnrichFn): Promise<string>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.4.0 (K4-014) — Deterministic fix capture (plan §5.4 / D4-07).
|
|
3
|
+
*
|
|
4
|
+
* Zero-cost "Fixed by:" raw material from local data: when CausalChain
|
|
5
|
+
* links a success to a failing fingerprint, the linked tool_call's
|
|
6
|
+
* `args_summary` becomes a deterministic fix string stored in
|
|
7
|
+
* `memories.fix_args`. The default promotion path is pure local data —
|
|
8
|
+
* no LLM calls (principle 14).
|
|
9
|
+
*/
|
|
10
|
+
/** Max length of the `args_summary` embedded in a fix_args string. */
|
|
11
|
+
export const FIX_ARGS_TRUNCATE = 120;
|
|
12
|
+
/**
|
|
13
|
+
* `"bash" with args "npm i -g rg"` style. Returns null when the linked
|
|
14
|
+
* call carries no args_summary (nothing to say).
|
|
15
|
+
*/
|
|
16
|
+
export function extractFixArgs(call) {
|
|
17
|
+
const raw = call.args_summary?.trim();
|
|
18
|
+
if (!raw)
|
|
19
|
+
return null;
|
|
20
|
+
const truncated = raw.length > FIX_ARGS_TRUNCATE
|
|
21
|
+
? `${raw.slice(0, FIX_ARGS_TRUNCATE)}…`
|
|
22
|
+
: raw;
|
|
23
|
+
return `${call.tool} with args "${truncated}"`;
|
|
24
|
+
}
|
|
25
|
+
/** Deterministic `Fixed by: {fix_args}` line (or "" when there is none). */
|
|
26
|
+
export function deterministicFixLine(pattern) {
|
|
27
|
+
return pattern.fixArgs ? `Fixed by: ${pattern.fixArgs}` : "";
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Promotion-time fix phrasing. Default path returns the deterministic
|
|
31
|
+
* `Fixed by: {fix_args}` line (or "" when there is none). When an
|
|
32
|
+
* `enrichFn` is supplied and returns a phrase, that phrase wins.
|
|
33
|
+
*/
|
|
34
|
+
export async function enrichAtPromotion(pattern, enrichFn) {
|
|
35
|
+
if (enrichFn) {
|
|
36
|
+
const phrased = await enrichFn({
|
|
37
|
+
lesson: pattern.content,
|
|
38
|
+
fixArgs: pattern.fixArgs,
|
|
39
|
+
originalError: null,
|
|
40
|
+
});
|
|
41
|
+
if (phrased)
|
|
42
|
+
return phrased;
|
|
43
|
+
}
|
|
44
|
+
return deterministicFixLine(pattern);
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=LessonFixer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LessonFixer.js","sourceRoot":"","sources":["../../plugin/LessonFixer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,sEAAsE;AACtE,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAOrC;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAAoB;IAClD,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC;IACtC,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,MAAM,SAAS,GACd,GAAG,CAAC,MAAM,GAAG,iBAAiB;QAC7B,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,CAAC,GAAG;QACvC,CAAC,CAAC,GAAG,CAAC;IACR,OAAO,GAAG,IAAI,CAAC,IAAI,eAAe,SAAS,GAAG,CAAC;AAChD,CAAC;AAqBD,4EAA4E;AAC5E,MAAM,UAAU,oBAAoB,CAAC,OAAoB;IACxD,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC9D,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACtC,OAAoB,EACpB,QAAmB;IAEnB,IAAI,QAAQ,EAAE,CAAC;QACd,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC;YAC9B,MAAM,EAAE,OAAO,CAAC,OAAO;YACvB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,aAAa,EAAE,IAAI;SACnB,CAAC,CAAC;QACH,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC;IAC7B,CAAC;IACD,OAAO,oBAAoB,CAAC,OAAO,CAAC,CAAC;AACtC,CAAC"}
|