@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,35 +0,0 @@
|
|
|
1
|
-
import type { MemoryService } from "./MemoryService.js";
|
|
2
|
-
import type { RepoTruth } from "./RepoTruth.js";
|
|
3
|
-
import type { Store } from "./Store.js";
|
|
4
|
-
import type { Metrics } from "./metrics.js";
|
|
5
|
-
export type ConflictKind = "repo_truth" | "decision_pair" | "temporal";
|
|
6
|
-
export interface Conflict {
|
|
7
|
-
readonly id: string;
|
|
8
|
-
readonly kind: ConflictKind;
|
|
9
|
-
readonly memoryA: string;
|
|
10
|
-
readonly memoryB?: string;
|
|
11
|
-
readonly factId?: string;
|
|
12
|
-
readonly detail: string;
|
|
13
|
-
}
|
|
14
|
-
export interface RepoTruthConflictInput {
|
|
15
|
-
readonly memoryId: string;
|
|
16
|
-
readonly factId?: string | null;
|
|
17
|
-
readonly reasons: readonly string[];
|
|
18
|
-
}
|
|
19
|
-
export declare class ConflictDetector {
|
|
20
|
-
private readonly store;
|
|
21
|
-
private readonly projectId;
|
|
22
|
-
private readonly repoTruth?;
|
|
23
|
-
private readonly memoryService?;
|
|
24
|
-
private readonly metrics;
|
|
25
|
-
constructor(store: Store, projectId: string, metrics?: Metrics | null, repoTruth?: RepoTruth | undefined, memoryService?: MemoryService | undefined);
|
|
26
|
-
detect(repoTruthInput?: readonly RepoTruthConflictInput[]): Conflict[];
|
|
27
|
-
acknowledge(id: string): void;
|
|
28
|
-
/** Human-only operation. It resolves the conflict row, never a memory. */
|
|
29
|
-
resolve(id: string, keepMemoryId: string): void;
|
|
30
|
-
private detectRepoTruth;
|
|
31
|
-
private repoTruthInputs;
|
|
32
|
-
private detectDecisionPairs;
|
|
33
|
-
private detectTemporal;
|
|
34
|
-
private insertIfNew;
|
|
35
|
-
}
|
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
import { mapRow } from "./MemoryService.js";
|
|
2
|
-
import { normalize } from "./fingerprint.js";
|
|
3
|
-
import { STOP_WORDS } from "./query-tokenizer.js";
|
|
4
|
-
import { uuidv7 } from "./uuid.js";
|
|
5
|
-
const POLARITY_RULES = [
|
|
6
|
-
{
|
|
7
|
-
positive: ["use"],
|
|
8
|
-
negative: [
|
|
9
|
-
["do", "not", "use"],
|
|
10
|
-
["don't", "use"],
|
|
11
|
-
["never", "use"],
|
|
12
|
-
],
|
|
13
|
-
},
|
|
14
|
-
{ positive: ["always"], negative: [["never"]] },
|
|
15
|
-
{ positive: ["required"], negative: [["forbidden"], ["not", "required"]] },
|
|
16
|
-
{ positive: ["enable"], negative: [["disable"]] },
|
|
17
|
-
{ positive: ["prefer"], negative: [["avoid"]] },
|
|
18
|
-
];
|
|
19
|
-
const POLARITY_WORDS = new Set([
|
|
20
|
-
"use",
|
|
21
|
-
"do",
|
|
22
|
-
"not",
|
|
23
|
-
// "don't" tokenizes to ["don", "t"] (see tokens()); the lexicon phrase
|
|
24
|
-
// ["don't","use"] is matched by tokenizing the phrase the same way, so
|
|
25
|
-
// both fragments must be excluded from subjects() too (K7-014).
|
|
26
|
-
"don",
|
|
27
|
-
"t",
|
|
28
|
-
"never",
|
|
29
|
-
"always",
|
|
30
|
-
"required",
|
|
31
|
-
"forbidden",
|
|
32
|
-
"enable",
|
|
33
|
-
"disable",
|
|
34
|
-
"prefer",
|
|
35
|
-
"avoid",
|
|
36
|
-
]);
|
|
37
|
-
export class ConflictDetector {
|
|
38
|
-
store;
|
|
39
|
-
projectId;
|
|
40
|
-
repoTruth;
|
|
41
|
-
memoryService;
|
|
42
|
-
metrics;
|
|
43
|
-
constructor(store, projectId, metrics, repoTruth, memoryService) {
|
|
44
|
-
this.store = store;
|
|
45
|
-
this.projectId = projectId;
|
|
46
|
-
this.repoTruth = repoTruth;
|
|
47
|
-
this.memoryService = memoryService;
|
|
48
|
-
this.metrics = metrics ?? null;
|
|
49
|
-
}
|
|
50
|
-
detect(repoTruthInput) {
|
|
51
|
-
const detected = [];
|
|
52
|
-
detected.push(...this.detectRepoTruth(repoTruthInput));
|
|
53
|
-
detected.push(...this.detectDecisionPairs());
|
|
54
|
-
detected.push(...this.detectTemporal());
|
|
55
|
-
return detected;
|
|
56
|
-
}
|
|
57
|
-
acknowledge(id) {
|
|
58
|
-
this.store
|
|
59
|
-
.prepare("UPDATE memory_conflicts SET status = 'acknowledged' WHERE id = ? AND project_id = ? AND status = 'open'")
|
|
60
|
-
.run(id, this.projectId);
|
|
61
|
-
}
|
|
62
|
-
/** Human-only operation. It resolves the conflict row, never a memory. */
|
|
63
|
-
resolve(id, keepMemoryId) {
|
|
64
|
-
const row = this.store
|
|
65
|
-
.prepare(`SELECT memory_a, memory_b FROM memory_conflicts
|
|
66
|
-
WHERE id = ? AND project_id = ? AND status <> 'resolved'`)
|
|
67
|
-
.get(id, this.projectId);
|
|
68
|
-
if (!row)
|
|
69
|
-
throw new Error(`conflict not found: ${id}`);
|
|
70
|
-
if (keepMemoryId !== row.memory_a && keepMemoryId !== row.memory_b) {
|
|
71
|
-
throw new Error("keep must identify one of the conflict memories");
|
|
72
|
-
}
|
|
73
|
-
this.store
|
|
74
|
-
.prepare("UPDATE memory_conflicts SET status = 'resolved', resolved_at = datetime('now') WHERE id = ? AND project_id = ?")
|
|
75
|
-
.run(id, this.projectId);
|
|
76
|
-
}
|
|
77
|
-
detectRepoTruth(input) {
|
|
78
|
-
const items = input ?? this.repoTruthInputs();
|
|
79
|
-
const out = [];
|
|
80
|
-
for (const item of items) {
|
|
81
|
-
for (const reason of item.reasons) {
|
|
82
|
-
this.memoryService?.applyTruthPenalty(item.memoryId, 0.5, reason);
|
|
83
|
-
const id = this.insertIfNew({
|
|
84
|
-
kind: "repo_truth",
|
|
85
|
-
memoryA: item.memoryId,
|
|
86
|
-
memoryB: null,
|
|
87
|
-
factId: item.factId ?? null,
|
|
88
|
-
detail: reason,
|
|
89
|
-
});
|
|
90
|
-
if (id)
|
|
91
|
-
out.push({
|
|
92
|
-
id,
|
|
93
|
-
kind: "repo_truth",
|
|
94
|
-
memoryA: item.memoryId,
|
|
95
|
-
factId: item.factId ?? undefined,
|
|
96
|
-
detail: reason,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
// v0.7.0 (D7-03) — "recoverable by re-scanning": a memory whose
|
|
101
|
-
// contradiction has lapsed (the repo no longer contradicts it) is
|
|
102
|
-
// de-ranked no longer. The conflict row stays open — resolution is
|
|
103
|
-
// human-initiated only (D7-06) — but the score penalty lifts, so
|
|
104
|
-
// fixing the repo genuinely recovers the memory. Runs only on the
|
|
105
|
-
// production path (no explicit input): explicit-input callers control
|
|
106
|
-
// every write themselves.
|
|
107
|
-
if (!input && this.repoTruth && this.memoryService) {
|
|
108
|
-
const penalized = this.store
|
|
109
|
-
.prepare("SELECT id FROM memories WHERE project_id = ? AND truth_penalty > 0 AND status = 'active'")
|
|
110
|
-
.all(this.projectId);
|
|
111
|
-
for (const row of penalized) {
|
|
112
|
-
const memory = this.memoryService.getById(row.id);
|
|
113
|
-
if (!memory)
|
|
114
|
-
continue;
|
|
115
|
-
if ((this.repoTruth.contradictions(memory) ?? []).length === 0) {
|
|
116
|
-
this.memoryService.applyTruthPenalty(row.id, 0, "");
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return out;
|
|
121
|
-
}
|
|
122
|
-
repoTruthInputs() {
|
|
123
|
-
if (!this.repoTruth)
|
|
124
|
-
return [];
|
|
125
|
-
const rows = this.store
|
|
126
|
-
.prepare(`SELECT id, type, content, scope, relevance_score, source_tool,
|
|
127
|
-
source_session, metadata, created_at, updated_at, expires_at,
|
|
128
|
-
project_id, fingerprint, origin, evidence_count, last_verified_at,
|
|
129
|
-
status, fix_args, ignored, superseded_by, feedback_positive,
|
|
130
|
-
feedback_negative, curated, curated_at, inferable,
|
|
131
|
-
truth_penalty, contradicted_at
|
|
132
|
-
FROM memories WHERE project_id = ? AND status = 'active'`)
|
|
133
|
-
.all(this.projectId);
|
|
134
|
-
return rows.flatMap((row) => {
|
|
135
|
-
// v1.1.0 (K11-014) — route through mapRow so metadata is parsed and confidence derived
|
|
136
|
-
const memory = mapRow(row);
|
|
137
|
-
const reasons = this.repoTruth?.contradictions(memory) ?? [];
|
|
138
|
-
return reasons.length > 0
|
|
139
|
-
? [{ memoryId: row.id, reasons }]
|
|
140
|
-
: [];
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
detectDecisionPairs() {
|
|
144
|
-
const rows = this.store
|
|
145
|
-
.prepare(`SELECT id, type, content, fingerprint FROM memories
|
|
146
|
-
WHERE project_id = ? AND status = 'active'
|
|
147
|
-
AND type IN ('decision', 'rule') ORDER BY id`)
|
|
148
|
-
.all(this.projectId);
|
|
149
|
-
const out = [];
|
|
150
|
-
for (let i = 0; i < rows.length; i++) {
|
|
151
|
-
for (let j = i + 1; j < rows.length; j++) {
|
|
152
|
-
const a = rows[i];
|
|
153
|
-
const b = rows[j];
|
|
154
|
-
if (!a || !b || a.fingerprint === b.fingerprint)
|
|
155
|
-
continue;
|
|
156
|
-
const pair = oppositePair(a.content, b.content);
|
|
157
|
-
if (!pair)
|
|
158
|
-
continue;
|
|
159
|
-
const id = this.insertIfNew({
|
|
160
|
-
kind: "decision_pair",
|
|
161
|
-
memoryA: a.id,
|
|
162
|
-
memoryB: b.id,
|
|
163
|
-
factId: null,
|
|
164
|
-
detail: pair,
|
|
165
|
-
});
|
|
166
|
-
if (id)
|
|
167
|
-
out.push({
|
|
168
|
-
id,
|
|
169
|
-
kind: "decision_pair",
|
|
170
|
-
memoryA: a.id,
|
|
171
|
-
memoryB: b.id,
|
|
172
|
-
detail: pair,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return out;
|
|
177
|
-
}
|
|
178
|
-
detectTemporal() {
|
|
179
|
-
const rows = this.store
|
|
180
|
-
.prepare(`SELECT m.id,
|
|
181
|
-
MAX(CASE WHEN i.outcome = 'effective' THEN i.injected_at END) AS effective_at,
|
|
182
|
-
MAX(CASE WHEN i.outcome = 'ineffective' THEN i.injected_at END) AS ineffective_at
|
|
183
|
-
FROM memories m JOIN kevin_injections i ON i.memory_id = m.id
|
|
184
|
-
WHERE m.project_id = ? AND m.status = 'active'
|
|
185
|
-
GROUP BY m.id
|
|
186
|
-
HAVING effective_at IS NOT NULL AND ineffective_at IS NOT NULL
|
|
187
|
-
AND ineffective_at > effective_at`)
|
|
188
|
-
.all(this.projectId);
|
|
189
|
-
const out = [];
|
|
190
|
-
for (const row of rows) {
|
|
191
|
-
const detail = `memory was effective at ${row.effective_at} and became ineffective at ${row.ineffective_at}`;
|
|
192
|
-
const id = this.insertIfNew({
|
|
193
|
-
kind: "temporal",
|
|
194
|
-
memoryA: row.id,
|
|
195
|
-
memoryB: null,
|
|
196
|
-
factId: null,
|
|
197
|
-
detail,
|
|
198
|
-
});
|
|
199
|
-
if (id)
|
|
200
|
-
out.push({ id, kind: "temporal", memoryA: row.id, detail });
|
|
201
|
-
}
|
|
202
|
-
return out;
|
|
203
|
-
}
|
|
204
|
-
insertIfNew(input) {
|
|
205
|
-
const existing = this.store
|
|
206
|
-
.prepare(`SELECT id FROM memory_conflicts
|
|
207
|
-
WHERE project_id = ? AND kind = ? AND memory_a = ?
|
|
208
|
-
AND (memory_b IS ? OR (memory_b IS NULL AND ? IS NULL))
|
|
209
|
-
AND (fact_id IS ? OR (fact_id IS NULL AND ? IS NULL))
|
|
210
|
-
AND status = 'open' LIMIT 1`)
|
|
211
|
-
.get(this.projectId, input.kind, input.memoryA, input.memoryB, input.memoryB, input.factId, input.factId);
|
|
212
|
-
if (existing)
|
|
213
|
-
return null;
|
|
214
|
-
const id = uuidv7();
|
|
215
|
-
this.store
|
|
216
|
-
.prepare(`INSERT INTO memory_conflicts
|
|
217
|
-
(id, project_id, memory_a, memory_b, fact_id, kind, detail)
|
|
218
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`)
|
|
219
|
-
.run(id, this.projectId, input.memoryA, input.memoryB, input.factId, input.kind, input.detail);
|
|
220
|
-
this.metrics?.incr("conflicts_detected", 1);
|
|
221
|
-
return id;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
function tokens(text) {
|
|
225
|
-
return normalize(text).match(/[a-z0-9@._/-]+/g) ?? [];
|
|
226
|
-
}
|
|
227
|
-
function hasPhrase(words, phrase) {
|
|
228
|
-
if (phrase.length === 1)
|
|
229
|
-
return words.includes(phrase[0] ?? "");
|
|
230
|
-
for (let i = 0; i <= words.length - phrase.length; i++) {
|
|
231
|
-
if (phrase.every((word, offset) => words[i + offset] === word))
|
|
232
|
-
return true;
|
|
233
|
-
}
|
|
234
|
-
return false;
|
|
235
|
-
}
|
|
236
|
-
function polarity(text, rule) {
|
|
237
|
-
const words = tokens(text);
|
|
238
|
-
// Lexicon phrases are run through the same tokenizer as the text: "don't
|
|
239
|
-
// use" → ["don","t","use"], which is exactly how the text's own apostrophe
|
|
240
|
-
// is split, so a phrase never becomes unreachable (K7-014).
|
|
241
|
-
const match = (phrase) => hasPhrase(words, tokens(phrase.join(" ")));
|
|
242
|
-
const negative = rule.negative.some(match);
|
|
243
|
-
return { positive: !negative && match(rule.positive), negative };
|
|
244
|
-
}
|
|
245
|
-
function subjects(text) {
|
|
246
|
-
return new Set(tokens(text).filter((token) => !STOP_WORDS.has(token) && !POLARITY_WORDS.has(token)));
|
|
247
|
-
}
|
|
248
|
-
function oppositePair(a, b) {
|
|
249
|
-
const shared = [...subjects(a)].some((token) => subjects(b).has(token));
|
|
250
|
-
if (!shared)
|
|
251
|
-
return null;
|
|
252
|
-
for (const rule of POLARITY_RULES) {
|
|
253
|
-
const pa = polarity(a, rule);
|
|
254
|
-
const pb = polarity(b, rule);
|
|
255
|
-
if ((pa.positive && pb.negative) || (pa.negative && pb.positive)) {
|
|
256
|
-
return `statements carry opposite ${rule.positive.join(" ")} polarity for shared subject ${shared}`;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
return null;
|
|
260
|
-
}
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
import type { InjectionLedger } from "./InjectionLedger.js";
|
|
2
|
-
import type { MemoryService } from "./MemoryService.js";
|
|
3
|
-
import { type GateReason } from "./QualityGate.js";
|
|
4
|
-
import { type Metrics } from "./metrics.js";
|
|
5
|
-
export declare const QUALITY_GATE_SETTING = "quality_gate_enabled";
|
|
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;
|
|
15
|
-
export interface ChatMessage {
|
|
16
|
-
role: string;
|
|
17
|
-
content: string;
|
|
18
|
-
}
|
|
19
|
-
export interface SystemTransformInput {
|
|
20
|
-
sessionID?: string;
|
|
21
|
-
messages: ChatMessage[];
|
|
22
|
-
}
|
|
23
|
-
export interface SystemTransformOutput {
|
|
24
|
-
system: string[];
|
|
25
|
-
}
|
|
26
|
-
export interface CompactingInput {
|
|
27
|
-
sessionID: string;
|
|
28
|
-
messages: ChatMessage[];
|
|
29
|
-
}
|
|
30
|
-
export interface CompactingOutput {
|
|
31
|
-
context: string[];
|
|
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
|
-
}
|
|
53
|
-
export declare class ContextInjector {
|
|
54
|
-
private memoryService;
|
|
55
|
-
private metrics;
|
|
56
|
-
private ledger;
|
|
57
|
-
private lastRecurrenceCount;
|
|
58
|
-
/** v0.4.0 (K4-016) — session that produced the last recurrence set. */
|
|
59
|
-
private lastRecurredSession;
|
|
60
|
-
/** v0.4.0 (K4-017) — per-session seen-set (plan §5.1 rule 3). */
|
|
61
|
-
private readonly seenBySession;
|
|
62
|
-
constructor(memoryService: MemoryService, metrics?: Metrics | null, ledger?: InjectionLedger | null);
|
|
63
|
-
/**
|
|
64
|
-
* v0.4.0 (K4-017) — reset the per-session seen-set when a session is
|
|
65
|
-
* created (plan §5.1 rule 3). Wired from the `session.created` event.
|
|
66
|
-
*/
|
|
67
|
-
onSessionCreated(sessionId: string): void;
|
|
68
|
-
/**
|
|
69
|
-
* v0.3.0 (K3-020) — notify the injector that the negative feedback half
|
|
70
|
-
* fired N times in the last session.idle. The next system.transform or
|
|
71
|
-
* compacting hook will prepend a HITL suggestion block.
|
|
72
|
-
* v0.4.0 (K4-016) — the session id enables the concrete suggestion:
|
|
73
|
-
* the most-recurred fingerprint's pattern + its fix_args.
|
|
74
|
-
*/
|
|
75
|
-
setRecurrences(count: number, sessionId?: string): void;
|
|
76
|
-
/**
|
|
77
|
-
* v0.4.0 (K4-016) — generate a CONCRETE HITL suggestion block when
|
|
78
|
-
* recurrences occurred (plan §5.5): names the most-recurred pattern,
|
|
79
|
-
* its exact recurrence count, observed fix_args and confidence. The
|
|
80
|
-
* AGENTS.md draft line derives from the lesson's `Suggestion:` text —
|
|
81
|
-
* never a canned string, and never the anonymous "the same error
|
|
82
|
-
* pattern" without naming the pattern and count.
|
|
83
|
-
*
|
|
84
|
-
* BUG-012 — emits AT MOST ONCE per session: calling it resets the
|
|
85
|
-
* pending recurrence signal, so whichever hook (system.transform or
|
|
86
|
-
* compacting) runs first in a session consumes the block. Documented
|
|
87
|
-
* behavior; wire index.ts to match.
|
|
88
|
-
*/
|
|
89
|
-
generateSuggestion(): string;
|
|
90
|
-
/**
|
|
91
|
-
* v0.4.0 (K4-016) — the AGENTS.md draft line derives from the lesson's
|
|
92
|
-
* `Suggestion:` text (kept verbatim in the pattern's `Original:` block).
|
|
93
|
-
*/
|
|
94
|
-
private agentsDraftLine;
|
|
95
|
-
/**
|
|
96
|
-
* v0.2.0 (K2-024): origin-aware ranking at injection time is delegated
|
|
97
|
-
* to `MemoryService.getRelevant()` (K2-023, D2-13). The injector does
|
|
98
|
-
* NOT re-rank on its own — it consumes the already-ranked slice and
|
|
99
|
-
* applies the conditional-budget guard for opt-out (`protect: false`)
|
|
100
|
-
* callers. Plan §B6.5: "apply the same multiplier as
|
|
101
|
-
* MemoryService.recall so reflector lessons outrank agent-saved notes
|
|
102
|
-
* at injection time" — satisfied transitively via the getRelevant call.
|
|
103
|
-
*
|
|
104
|
-
* v0.4.0 (K4-017): after retrieval, every memory is filtered through
|
|
105
|
-
* `QualityGate.canInject` (session seen-set + recurrence + strength),
|
|
106
|
-
* and each admitted memory is recorded in the `InjectionLedger`
|
|
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).
|
|
113
|
-
*/
|
|
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;
|
|
162
|
-
/**
|
|
163
|
-
* v0.4.0 (K4-017) — QualityGate admission: filters the ranked slice to
|
|
164
|
-
* memories that may be injected this session, updating the session
|
|
165
|
-
* seen-set.
|
|
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
|
-
*
|
|
173
|
-
* BUG-005 — strength/actionability now go through the REAL
|
|
174
|
-
* `QualityGate.evaluate` semantics (plan §5.1 rules 1-2), which this
|
|
175
|
-
* class had only re-derived from `metadata.dispatch`:
|
|
176
|
-
* - dispatched code → strong + actionable (rescued errorType);
|
|
177
|
-
* - generic fallback suggestion + no code → weak, NOT actionable
|
|
178
|
-
* (the generic-suggestion ban is now enforced even for legacy
|
|
179
|
-
* lessons without dispatch metadata, as long as the `Suggestion:`
|
|
180
|
-
* text is available);
|
|
181
|
-
* - specific suggestion without code → strong + actionable;
|
|
182
|
-
* - no `Suggestion:` text at all (agent-saved note) → strong +
|
|
183
|
-
* actionable — the agent explicitly asked to remember it;
|
|
184
|
-
* - causal patterns (type='pattern') → always strong + actionable:
|
|
185
|
-
* they are the FIXED form of a fingerprint (K4-025).
|
|
186
|
-
*/
|
|
187
|
-
private admit;
|
|
188
|
-
/**
|
|
189
|
-
* BUG-005 — the single source of truth for a memory's lesson quality,
|
|
190
|
-
* shared by `admit` (the gate) and `format` (the K4-023 `(low
|
|
191
|
-
* confidence)` marker). Routes lessons through `QualityGate.evaluate`
|
|
192
|
-
* — the production call site that was previously missing.
|
|
193
|
-
*/
|
|
194
|
-
private lessonQuality;
|
|
195
|
-
/**
|
|
196
|
-
* v0.4.0 (K4-017) — one ledger row per admitted memory (plan §5.2).
|
|
197
|
-
* Token attribution uses the memory's share of the final block.
|
|
198
|
-
*
|
|
199
|
-
* v0.8.0 (K8-024 / plan §5.7) — shared projections are recorded even
|
|
200
|
-
* though they carry NO fingerprint by design (K8-017 — it is a
|
|
201
|
-
* different identity dimension): the memory id is their identity in
|
|
202
|
-
* the ledger, and recording them is what makes `injections_from_shared`
|
|
203
|
-
* observable at all. No tool call can ever match a memory id, so
|
|
204
|
-
* settle() marks the row inconclusive — excluded from the precision
|
|
205
|
-
* denominator (K5-005) — and the BUG-015 skip is unchanged for local
|
|
206
|
-
* notes without a fingerprint.
|
|
207
|
-
*/
|
|
208
|
-
private recordInjections;
|
|
209
|
-
/**
|
|
210
|
-
* v0.4.0 (K4-012) — snippet injection payload (plan §5.1 rule 5,
|
|
211
|
-
* D4-05): rows show `id:` + first 2 non-empty lines + `<protect>`
|
|
212
|
-
* instead of the full body. Gated by the `lesson_snippet_injection`
|
|
213
|
-
* setting (default `'1'`); when `'0'`, full content is restored.
|
|
214
|
-
* `escapeInjectedText` is applied to snippet content by the formatter.
|
|
215
|
-
* v0.4.0 (K4-023) — weak lessons admitted in debug mode are flagged so
|
|
216
|
-
* the formatter renders the `(low confidence)` marker.
|
|
217
|
-
*/
|
|
218
|
-
private format;
|
|
219
|
-
deriveQuery(messages: ChatMessage[]): string;
|
|
220
|
-
onSystemTransform(input: SystemTransformInput, output: SystemTransformOutput): void;
|
|
221
|
-
onCompacting(input: CompactingInput, output: CompactingOutput): void;
|
|
222
|
-
}
|